Ignoring SMS, which is vulnerable to SIM-swapping attacks, TOTP (Time-based One-Time Passwords) is probably the most popular second factor authentication method at the moment. While reviewing a pull request adding support for TOTP, I decided to investigate the current state of authenticators in 2025 with regards to their support for the various security parameters.
A previous analysis from 2019 found that many popular authenticators were happy to accept parameters they didn't actually support and then generate the wrong codes. At the time, a service wanting to offer TOTP to its users had to stick to the default security parameters or face major interoperability issues with common authenticator clients. Has the landscape changed or are we still stuck with security decisions made 15 years ago?
As an aside: yes, everybody is linking to a wiki page for an archived Google repo because there is no formal spec for the URI format.
Test results
I tested a number of Android authenticators against the oathtool client:
/usr/bin/oathtool --totp=SHA1 --base32 JVRWCZDTMVZWK5BAMJSSAZLOMVZGK5TJMVXGIZLDN5SGKZBAOVZI
/usr/bin/oathtool --totp=SHA256 --base32 JVRWCZDTMVZWK5BAMJSSAZLOMVZGK5TJMVXGIZLDN5SGKZBAOVZI
1Password:
- SHA1 (52 chars): yes
- SHA256: not available
Authy (Twillio):
- SHA1 (32 chars): yes
- SHA1 (52 chars): yes
- SHA256: no (treats it as SHA1)
- Note: they also pick random logos to attach to your brand.
Bitwarden Authenticator:
- SHA1 (32 chars): yes
- SHA1 (52 chars): yes
- SHA256: yes
Duo Security:
- SHA1 (32 chars): yes
- SHA1 (52 chars): yes
- SHA256: no (treats it as SHA1)
- SHA1 (32 chars): yes
- SHA1 (52 chars): yes
- SHA256: yes
Google Authenticator:
- SHA1 (32 chars): yes
- SHA1 (52 chars): yes
- SHA256: yes
LastPass Authenticator:
- SHA1 (32 chars): yes
- SHA1 (52 chars): yes
- SHA256: yes
Microsoft Authenticator:
- SHA1 (32 chars): yes
- SHA1 (52 chars): yes
- SHA256: no (treats it as SHA1)
I also tested the infamous Google Authenticator on iOS:
otpauth://totp/francois+1%40brave.com?secret=JVRWCZDTMVZWK5BAMJSSAZLOMVZGK5TJ&issuer=Brave%20Account&algorithm=SHA1&image=https://account.brave.com/images/email/brave-41x40.png
otpauth://totp/francois+1%40brave.com?secret=JVRWCZDTMVZWK5BAMJSSAZLOMVZGK5TJMVXGIZLDN5SGKZBAOVZI&issuer=Brave%20Account&algorithm=SHA1&image=https://account.brave.com/images/email/brave-41x40.png
- SHA1 (32 chars): yes
- SHA1 (52 chars): no (rejects it)
- SHA256 (32 chars): yes
Recommendations to site owners
So unfortunately, the 2019 recommendations still stand:
- Algorithm: SHA1
- Key size: 32 characters (equivalent to 20 bytes / 160 bits)
- Period: 30 seconds
- Digits: 6
You should also avoid putting the secret
parameter
last in the URI to avoid breaking
some versions of Google Authenticator which parse these URIs incorrectly.
Other security and user experience considerations:
- Keep track of codes that are already used for as long they are valid since these codes are meant to be one-time credentials.
- Avoid storing the TOTP secret directly in plaintext inside the main app database and instead store it in some kind of secrets manager. Note: it cannot be hashed because the application needs the secret to generate the expected codes.
- Provide a recovery mechanism since users will lose their authenticators. This is often done through the use of one-time "scratch codes".
- Consider including in generated URIs two parameters introduced by the best Android client:
image
andcolor
. Most clients will ignore them, but they also don't hurt.