SPID is the Italian Public Digital Identity System, which enables citizens to access online services of the public administration. Citizens can choose among several identity providers. Most of them support two-factor authentication with proprietary authenticator apps, which are not interchangeable nor compatible with “universal” apps such as Google Authenticator. It turns out that all apps actually use the same algorithm, and the incompatibility is purely artificial.
In this post I look into Android apps of some SPID providers. I reverse engineer them to obtain the required parameters for OTP code generation, and I use them to set up a universal authenticator app such as Google Authenticator, Yubico Authenticator or Authy.
Motivations
- Flexibility: different people have different needs. Some have an old smartphone, some prefer to keep Google Play Services out of their devices, some have rooted or jailbroken smartphones1. In all of these cases using apps by SPID providers may not be possible.
- Security: some users may want to use a high-security device such as a YubiKey2 rather than a smartphone, especially if they don’t have one with a secure element3 to securely store credentials.
- FOSS: none of the apps by SPID providers is released as free and open source software. On the other hand, countless FOSS authenticator apps exist.
To these motivations I’ll also add a subjective one: convenience. If every website using two-factor authentication had its own app, our smartphones would be bloated with many almost identical – but incompatible – applications.
Background: the TOTP algorithm
Tens or hundreds authenticator apps exist on iOS and Android app stores. Google Authenticator is by far the most popular, but it’s certainly not alone and everyone can find their favorite one. What all of these universal apps have in common is the algorithm they’re based on: TOTP (Time-based One-Time Password)4, which is an extension of the more generic HOTP (HMAC-based One-Time Password)5.
I won’t go into the details of the TOTP algorithm, since understanding this post only requires to know that it involves four “ingredients”:
- a secret;
- a hash function (SHA-1 by default);
- a validity interval for each OTP (30 seconds by default);
- the number of digits for each OTP (6 by default).
All of these parameters must be shared between apps and their back-end. They are usually chosen server-side and communicated to the app using a QR code. Users may also manually type in the secret, but some apps (such as Google Authenticator) do not allow customization of other parameters when this fallback method is used.
The QR code just encodes an URI with the following format:
otpauth://totp/?secret=SECRET&algorithm=HASH&period=INTERVAL&digits=DIGITS
where the four parameters just described are clearly visible. Optionally, other information can also be included such as the name of the service and the username. I want to give a shout-out to this web page which allows to generate QR codes from TOTP parameters and which I found extremely useful during my analysis.
One last note concerns the encoding of the secret (which is usually a random sequence of bytes): the Base32 representation is used, which is less common than Base64 but optimized for human readability. It includes the 26 alphabetic characters (uppercase only) and numbers 2-7 (0 and 1 are omitted as they may be confused with letters O and I).
Case studies
Having covered the basic concepts of the TOTP algorithm, let’s dive in authenticator apps from SPID providers. What follows can be summed up in one sentence: all providers use the TOTP algorithm for OTP generation, but they adopt more or less complex techniques to prevent users from using third-party authenticator apps.
InfoCert ID
The InfoCert ID Android app6 requires an enrollment phase during which users just have to log in using their credentials. This makes it a reasonable guess that the TOTP secret is sent by the back-end server to the app during this process, and that it can be obtained by capturing the app’s traffic.
A first obstacle arises from the fact that the app does not transmit its own traffic through any system-level proxy (i.e. proxy set up in Android settings). This means that for a MITM7 attack we must resort to a VPN-based solution, such as the Packet Capture app8. In order to properly capture HTTPS traffic we need to configure our device so that it trusts the certification authority included in Packet Capture, and this requires root privileges.
The InfoCert ID app does not launch if it detects a rooted device. Furthermore, it does certificate pinning: it rejects connections if the certificate returned by the server is signed by a certification authority that is not on the developers’ whitelist. In order to workaround both of these issues, patching the app is required.
A quick look at the extracted .apk file reveals that the app is not Java-native and was written in C++ using Qt9; the whole application logic resides in the libinfocertID.so
library. This makes it much more difficult to proceed with a runtime patch using tools such as Frida10. Luckily tho, statically patching the native library is easy and can be done with a hex editor.
In case of root detection the app closes itself. This is done by the front-end, which is written in QML11. Since QML code can be easily located inside the library file as plain text, we can just remove the call to Qt.quit()
.
In order to bypass certificate pinning, instead, we can export the certificate used by Packet Capture as a PEM file and copy it into the native library replacing one of the hard-coded ones. This way the app will consider the Packet Capture certificate whitelisted and will not reject connections.
At this point, we can replace our patched library inside the original .apk file. Once re-signed and installed, it will enable us to use the application and capture its traffic without any problem. Traffic analysis confirms our initial guess: after a successful login, the app receives the TOTP secret from the back-end as a hexadecimal string.
In order to use the secret in apps other than the official one, we just need to encode it in Base32. We also need to set the number of digits of OTPs to 8 and the hash function to SHA-512 (which I determined just by trial-and-error).
It’s interesting to note that the TOTP secret seems to be permanently tied to each user account and unchangeable. It cannot be revoked from the personal area on the InfoCert website, and does not change upon password modification. This means that using the application on multiple devices results in all of them generating the same OTPs. It also means that there is no easy solution if someone leaks its own secret.
Security features seen so far make it unlikely for secrets to be stolen during normal service operation. You must be particularly careful, instead, should you ever decide to experiment following my example. I’d suggest not to, unless you really know what the risks are and you’re willing to take them.
Aruba ID
The Aruba ID app12 adopts a very similar enrollment procedure. It requires typing an “activation code” instead of your credentials, but even in this case we can guess that the secret is transmitted by the server and can be captured.
Fortunately, Aruba’s app is structured in a more conventional way and written in Java. It does not perform any anti-root check, but it does certificate pinning. In this case we can resort to Frida and patch the app at runtime using an open source script, such as masbog/frida-android-unpinning-ssl
13.
Traffic analysis and a later decompilation of the app using JD14 reveal an unusual protection mechanism: data exchanged between client and server is encrypted at the application level using AES-256. Considering that (as we’ll see shortly) the key is static and that during normal operation traffic is already encrypted using SSL (with certificate pinning!), this measure looks superfluous and is probably more of a way to discourage reverse engineering rather than an actual security measure.
I accepted the challenge and analyzed how encryption was performed by static code analysis. The main encryption key, used for traffic encryption, is located among the resources of the .apk file. This key is in turn encrypted with another key, derived at runtime using the PBKDF215 algorithm (implemented from scratch by the developers) from data that is in part hard-coded and in part present in the app configuration files.
Of course as long as these initial parameters don’t change the resulting key will be static, and it can easily be extracted with Frida by placing a hook on the decryption method. Obtaining it that way does not require investigating details about key derivation. On the other hand, it’s a bit less fun.
Once the main key is found, the TOTP secret sent by the server can be decrypted and encoded to Base32. It is then ready for use in any universal authenticator app. As before the number of digits needs to be set to 8, while the hash algorithm is SHA-256. Differently from InfoCert, Aruba allows changing TOTP secret from the personal area of their website.
March 11, 2021 update
A reader of this post, whom I would like to thank, wrote some Python code to retrieve the TOTP secret from the ArubaOTP app. The code is available at this GitHub repository.
He also pointed out that Google Authenticator for Android, differently from the iOS version, does not support 8-digit OTP generation or hash functions other than SHA-1. I suggest to use one of the many alternative authenticator apps.
LepidaID
The LepidaID app16 uses a different approach from the previous ones for what concerns the enrollment phase. It works in a very similar way to Google Authenticator: it’s completely offline and it requires the secret to be manually typed or scanned though a QR code. The secret is already provided in Base32, so it may look directly usable on third party authenticator apps. The QR code, on the other hand, does not follow the standard format.
If we manually type the secret into Google Authenticator, we notice that it is indeed accepted, but it results in the generation of wrong OTPs.
A quick look at the decompiled code of the app reveals the reason, which is pretty amusing. The provided TOTP secret is “encrypted” with a monoalphabetic substitution cipher. This basically means that each character X from the Base32 alphabet is replaced with a character Y from the same alphabet; the app just performs the inverse operation.
Of everything seen so far, this is undoubtedly the laziest technique to create an incompatibility between two perfectly interoperable systems.
In order to use the TOTP secret in authenticator apps other than the official one, we just need to perform the same substitution the app does, using a table that can easily be found in the decompiled app. Other parameters, differently from previous cases, are kept at their default values: 6-digit OTPs and SHA-1 hash algorithm.
I would like to thank Fred for the analysis of this app.
October 26, 2024 update
A reader of this post, whom I would like to thank, wrote some Python code to automatically convert the LepidaID secret and the related QR code in standard format. The code is available at this GitHub repository.
Other providers
I haven’t had a chance to analyze Namirial ID and SielteID, which I leave to future occasions. Nevertheless, some colleagues who did analyze them told me they don’t bring any new concept: they use the TOTP algorithm and the secret can be extracted with traffic capture. Playing around with those apps could be a nice exercise for those wanting to challenge themselves.
The following providers, at the time of writing, do not use offline OTPs but SMS-based ones.
(PosteID also allows to log in by scanning a QR code or by receiving a push notification).
October 10, 2022 update
After this post was published, several readers got in touch to report that the PosteID application was updated, introducing an OTP code generator similar to the ones seen in other apps. However, I did not have enough spare time to reverse engineer the app and examine how it works.
I would like to report that Lorenzo, one of the readers of this post, developed an open source implementation of the app’s functionalities, and he made it available at the following address: https://projects.lilik.it/zolfa/pyjod. Implemented functionalities include: OTP code generation, log in via QR codes and push notifications.
Lorenzo’s work reveals that PosteID also uses, behind the scenes, the same TOTP protocol found in all other apps analyzed in this post.
I would like to thank Lorenzo for sharing his work and for keeping me up to date while performing his analyses.
Conclusions
SPID providers use the standard TOTP algorithm for OTP generation, but try to prevent users form using third-party authenticator apps. This destroys the interoperability of the TOTP standard and almost certainly degrades user experience.
I hope this post was useful to anyone wanting to bypass this useless restriction, and interesting for anybody else just wanting to see apps “taken apart” in order to see how they work.
-
Some of the apps analyzed in the following paragraphs do not work if they detect any “tampering” with the device operating system. ↩︎
-
https://play.google.com/store/apps/details?id=it.infocert.infocertid ↩︎
-
Man In The Middle ↩︎
-
https://play.google.com/store/apps/details?id=app.greyshirts.sslcapture ↩︎
-
https://play.google.com/store/apps/details?id=it.aruba.pec.mobile.otp ↩︎
-
https://codeshare.frida.re/@masbog/frida-android-unpinning-ssl/ ↩︎
-
https://play.google.com/store/apps/details?id=it.lepida.id.authenticator ↩︎
-
https://www.agid.gov.it/sites/default/files/repository_files/gu-spid_guida_utente_spid_ver03.pdf ↩︎
-
https://www.agid.gov.it/sites/default/files/repository_files/guida_utente_spid_1.pdf ↩︎
-
https://www.agid.gov.it/sites/default/files/repository_files/spidprin.tt_.dpmu15000.03_-_guida_utente_al_servizio_tim_id.pdf ↩︎
-
https://www.agid.gov.it/sites/default/files/repository_files/dto_spid_pi_004-allegato_guidautente_v31.pdf ↩︎