Generating tokens for my health insurance
January 31, 2025
Intro
I had this idea for a long time and its finally time to do something about it. OSDE, my health insurance works in the following way whenever you want to make an appointment:
- Login into the APP if you havent before.
- Provide your affiliate number
- Give a temporary token provided by the app as a security measure
Whats interesting about this token along that its temporary (5 minutes) is that it could also be generated if you are offline. What makes me think that its a TOTP. Being a KeePassXC user i thought, Could i reverse engineer the process to remove another app from my phone?. Does it uses some standard algorithm?.
TOTP
Before we start our journey, let me explain as simple as i could what TOTP means. For this i will quote the real source of truth, Wikipedia:
Time-based one-time password (TOTP) is a computer algorithm that generates a one-time password (OTP) using the current time as a source of uniqueness. To establish TOTP authentication, the authenticatee and authenticator must pre-establish both the HOTP parameters and the following TOTP parameters […]
Given a shared secret between both parts, we could generate a one-time password using some fancy math with that and the current time. This will guarantee that only people knowing the secret could generate valid passwords.
Lets make a super simple example. Our shared secret will be the number “42”, our fancy math will be addition, and the current time its “1300”. If i want a TOTP i will get the code “1342”. You may think that you could get “1342” with another secret and time, and you are absolutely correct. Thats why TOTP tends to be valid for a very short period of time, and the strength its in how hard is to guess the secret and reverse the math.
Intercepting traffic
The first thing is to intercept all the traffic between the app and the server as i suspect the shared secret its returned whenever you login. To do this i will use Waydroid as an android emulator and mitmproxy to intercept the traffic. At first i tried to follow this guide but couldnt make it work. Later i found out that everything was easier than i thought. At least for this case.
waydroid init
adb shell settings put global http_proxy "192.168.0.X:8080"
After some incantations in the terminal, we have our first problem. We cant proxy https traffic as android doesnt trust the proxy certificate. To solve this we can use this amazing collection of scripts that solved almost all the problems that i have along the way. I also needed to install libhoudini an ARM translation layer as our app doesnt have an x86 version.
sudo venv/bin/python3 main.py install libhoudini
sudo venv/bin/python3 main.py install mitm --ca-cert ~/.mitmproxy/mitmproxy-ca-cert.pem
Installing the app
As waydroid uses LineageOS we dont have the Google Play services. We need to get our apps from another sources. I tried the usual ones, APKMirror which didnt have it, and APKPure which did. Are they sketchy websites? I dont know, time will tell. At first i tried with the last two versions but they wouldnt launch. The screen will turn black and nothing happened. Here i thought that everything was lost and that i would need to use a real device. Then i tried the other way, i started with the first version and IT WORKED!!!.
The Key
After doing a login and some network dancing, i finally have a promising call with my secret key. Now i need to found out how the token is generated.
I have the shared key and a url that returns it. At first i tried some online totp generator but it didnt work. Our only way forward is to decompile the APK and do some investigation. For this i used jadx.
After extracting everything and poking around, i found out that its a Ionic Angular app. Which is great news. I havent programmed with this before but i feel confortable navigating javascript.
The next step was to grep around for the url, the property “semilla”, the word “totp”, “otp” and the likes and to my surprise a file stood out: resources/assets/www/main.js
. The main entry point with all the code.
Some reading and i found where tokens are generated:
// ...
otplib_otplib_browser__WEBPACK_IMPORTED_MODULE_4__["totp"].options = {
digits: 3,
step: _environments_environment__WEBPACK_IMPORTED_MODULE_5__["ENV"].OTP_TIEMPO_VALIDO,
algorithm: 'sha256',
createHmacSecret: function (secret, params) {
return secret;
}
};
// ...
OTPService.prototype.generateToken = function (semilla) {
var nowDate = new Date();
if (this.otpDelta) {
otplib_otplib_browser__WEBPACK_IMPORTED_MODULE_4__["totp"].options = tslib__WEBPACK_IMPORTED_MODULE_0__["__assign"]({}, otplib_otplib_browser__WEBPACK_IMPORTED_MODULE_4__["totp"].options, { epoch: (nowDate.getTime() / 1000) - this.otpDelta });
}
else if (nowDate.getTimezoneOffset() !== 180) {
// const phoneDateNowMiliseconds = nowDate.getTime() - (180 * 60 * 1000)
// const fecha = new Date(phoneDateNowMiliseconds);
// const phoneDate = fecha.toISOString();
// console.log('OTP Service - Sincronización automática - Hora de Argentina segun la zona horaria del dispositivo: ', phoneDate);
otplib_otplib_browser__WEBPACK_IMPORTED_MODULE_4__["totp"].options = tslib__WEBPACK_IMPORTED_MODULE_0__["__assign"]({}, otplib_otplib_browser__WEBPACK_IMPORTED_MODULE_4__["totp"].options, { epoch: ((nowDate.getTime() / 1000) - ((nowDate.getTimezoneOffset() - 180) * 60)) });
}
else {
console.log('OTP Service - Zona horaria correcta');
}
var token = otplib_otplib_browser__WEBPACK_IMPORTED_MODULE_4__["totp"].generate(semilla);
return {
token: otplib_otplib_browser__WEBPACK_IMPORTED_MODULE_4__["totp"].generate(semilla),
remaining: otplib_otplib_browser__WEBPACK_IMPORTED_MODULE_4__["totp"].timeRemaining()
};
};
And here is everything i need, now i only need to know what library otplib_otplib_browser__WEBPACK_IMPORTED_MODULE_4__["totp"]
represents. To my luck searching for otplib
yields an npm package that seems to have the same api.
Time to clean the code a little:
var otpDelta = -11;
window.otplib.totp.options = {
digits: 3,
step: 300,
algorithm: 'sha256',
createHmacSecret: function (secret, params) {
return secret;
}
};
var nowDate = new Date();
if (otpDelta) {
window.otplib.totp.options.epoch= (nowDate.getTime() / 1000) - otpDelta;
}else if (nowDate.getTimezoneOffset() !== 180) {
const phoneDateNowMiliseconds = nowDate.getTime() - (180 * 60 * 1000)
const fecha = new Date(phoneDateNowMiliseconds);
const phoneDate = fecha.toISOString();
console.log('OTP Service - Sincronización automática - Hora de Argentina segun la zona horaria del dispositivo: ', phoneDate);
window.otplib.totp.options.epoch=((nowDate.getTime() / 1000) - ((nowDate.getTimezoneOffset() - 180) * 60));
}else{
console.log("ZONA HORARIA CORRECTA")
}
const token = window.otplib.totp.generate("SECRET_KEY");
output.innerHTML = JSON.stringify(token);
var remaining = window.otplib.totp.timeRemaining();
output.innerHTML += `${Math.floor(remaining/60)}:${remaining%60}`;
AAND… It doesnt work. I lost a few hours here, i was getting the same codes as the online generator i used before. Then i thought, maybe its using some old version of the library. Thats when searching throught the license comments i found out they were using version 11.0.1. And finally, i have my tokens generated from outside the app.
Putting it on KeePassXC
Now i have everything in my power to start generating this tokens with KeePassXC. My secret key, the number of digits (3), the algorithm (sha256) and the time to live (300s). I have to paste my key as base32 (maybe this is why the previous tools didnt work, but its too late to test it) and whats interesting its that KeePassXC doesnt allow me to setup 3 digits, the minimum its 6, but from that, the last three are the token.
My KeePassXC Code
The Official one
Outro
Now i can finally generate tokens without needing to open the slow and tedious app that they provide. It was also an interesting journey where i learn a little more about how to intercept traffic and reverse engineer apps. This time luck was on my side as the app wasnt too obfuscated and old versions still worked fine. Nevertheless another interesting journey of learning.
Leave your comment on the github issue, sending me an email or DMing me on twitter