Terence Eden’s Blog<p><strong>Towards a test-suite for TOTP codes</strong></p><p><a href="https://shkspr.mobi/blog/2025/03/towards-a-test-suite-for-totp-codes/" rel="nofollow noopener noreferrer" translate="no" target="_blank"><span class="invisible">https://</span><span class="ellipsis">shkspr.mobi/blog/2025/03/towar</span><span class="invisible">ds-a-test-suite-for-totp-codes/</span></a></p><p>Because I'm a massive nerd, I <em>actually try to read</em> specification documents. As I've ranted <i>ad nauseam</i> about the current TOTP0 spec being <a href="https://shkspr.mobi/blog/2025/02/the-least-secure-totp-code-possible/" rel="nofollow noopener noreferrer" target="_blank">irresponsibly obsolete</a>.</p><p>The three major implementations of the spec - <a href="https://github.com/google/google-authenticator/wiki/Key-Uri-Format" rel="nofollow noopener noreferrer" target="_blank">Google</a>, <a href="https://developer.apple.com/documentation/authenticationservices/securing-logins-with-icloud-keychain-verification-codes#3795996" rel="nofollow noopener noreferrer" target="_blank">Apple</a>, and <a href="https://docs.yubico.com/yesdk/users-manual/application-oath/uri-string-format.html" rel="nofollow noopener noreferrer" target="_blank">Yubico</a> - all subtly disagree on how it should be implemented. Every other MFA app has their own idiosyncratic variants. The <a href="https://datatracker.ietf.org/doc/html/rfc6238" rel="nofollow noopener noreferrer" target="_blank">official RFC is infuriatingly vague</a>. That's no good for a security specification. Multiple implementations are great, multiple interpretations are not.</p><p>So I've <a href="https://edent.codeberg.page/TOTP_Test_Suite/" rel="nofollow noopener noreferrer" target="_blank">built a nascent test suite</a> - you can use it to see if your favourite app can correctly implement the TOTP standard.</p><p><a href="https://edent.codeberg.page/TOTP_Test_Suite/" rel="nofollow noopener noreferrer" target="_blank"></a></p><p>Please do contribute tests and / or feedback.</p><p>Here's what the standard <em>actually</em> says - see if you can find apps which don't implement it correctly.</p><p><strong>Background</strong></p><p>Time-based One Time Passwords are based on HOTP - HMAC-Based One-Time Password.</p><p>HOTP uses counters; a new password is regularly generated. TOTP uses time as the counter. At the time of writing this post, there have been about 1,740,800,000 seconds since the UNIX Epoc. So a TOTP with an period of 30 seconds is on counter (1,740,800,000 ➗ 30) = 58,026,666. Every 30 seconds, that counter increments by one.</p><p><strong>Number of digits</strong></p><p>How many digits should your 2FA token have? Google says 6 or 8. YubiCo graciously allows 7. Why those limits? Who knows!?</p><p><a href="https://datatracker.ietf.org/doc/html/rfc4226#section-5.4" rel="nofollow noopener noreferrer" target="_blank">The HOTP specification gives an <em>example</em> of 6 digits</a>. The example generates a code of <code>0x50ef7f19</code> which, in decimal, is <code>1357872921</code>. It then takes the last 6 digits to produce the code <code>872921</code>.</p><p>The TOTP RFC say:</p><blockquote><p>Basically, the output of the HMAC-SHA-1 calculation is truncated to obtain user-friendly values <a href="https://datatracker.ietf.org/doc/html/rfc6238#section-1.2" rel="nofollow noopener noreferrer" target="_blank">1.2. Background</a></p></blockquote><p>But doesn't say how far to truncate.</p><p>There's nothing I can see in the spec that <em>prevents</em> an implementer using all 10. The HOTP spec, however, <em>does</em> place a minimum requirement - but no maximum:</p><blockquote><p>Implementations MUST extract a 6-digit code at a minimum and possibly 7 and 8-digit code. Depending on security requirements, Digit = 7 or more SHOULD be considered in order to extract a longer HOTP value. <a href="https://datatracker.ietf.org/doc/html/rfc4226#section-5.3" rel="nofollow noopener noreferrer" target="_blank">RFC 4226 - 5.3. Generating an HOTP Value</a></p></blockquote><p>(As a minor point, the first digit is restricted to 0-2, so being 10 digits long isn't significantly stronger than 9 digits.)</p><p>Is a 4 digit code acceptable? The security might be weaker, but the usability is greater. Most apps will allow a <em>one</em> digit code to be returned. If no digits are specified, what should the default be?</p><p><strong>Algorithm</strong></p><p>The given algorithm in the HOTP spec is SHA-1.</p><blockquote><p>In order to create the HOTP value, we will use the HMAC-SHA-1 algorithm <a href="https://datatracker.ietf.org/doc/html/rfc4226#section-5.2" rel="nofollow noopener noreferrer" target="_blank">RFC 4226 - 5.2. Description</a></p></blockquote><p>As we now know, SHA-1 has some fundamental weaknesses. The spec comments (perhaps somewhat naïvely) about SHA-1:</p><blockquote><p>The new attacks on SHA-1 have no impact on the security of HMAC-SHA-1. <a href="https://datatracker.ietf.org/doc/html/rfc4226#appendix-B.2" rel="nofollow noopener noreferrer" target="_blank">RFC 4226 - B.2. HMAC-SHA-1 Status</a></p></blockquote><p>I daresay that's accurate. But the TOTP authors disagree and allow a for some different algorithms to be used. The specification for HMAC says:</p><blockquote><p>HMAC can be used with <em>any</em> iterative cryptographic hash function, e.g., MD5, SHA-1 [Emphasis added] <a href="https://datatracker.ietf.org/doc/html/rfc2104" rel="nofollow noopener noreferrer" target="_blank">RFC 2104 - HMAC: Keyed-Hashing for Message Authentication</a></p></blockquote><p>So most TOTP implementation allow SHA-1, SHA-256, and SHA-512.</p><blockquote><p>TOTP implementations MAY use HMAC-SHA-256 or HMAC-SHA-512 functions […] instead of the HMAC-SHA-1 function that has been specified for the HOTP computation <a href="https://datatracker.ietf.org/doc/html/rfc6238#section-1.2" rel="nofollow noopener noreferrer" target="_blank">RFC 6238 - TOTP: Time-Based One-Time Password Algorithm</a></p></blockquote><p>But the HOTP spec goes on to say:</p><blockquote><p>Current candidates for such hash functions include SHA-1, MD5, RIPEMD-128/160. These different realizations of HMAC will be denoted by HMAC-SHA1, HMAC-MD5, HMAC-RIPEMD <a href="https://datatracker.ietf.org/doc/html/rfc2104#section-1" rel="nofollow noopener noreferrer" target="_blank">RFC 2104 - Introduction</a></p></blockquote><p>So, should your TOTP app be able to handle an MD5 HMAC, or even SHA3-384? Will it? If no algorithm is specified, what should the default be?</p><p><strong>Period</strong></p><p>As discussed, this is what increments the counter for HOTP. The <a href="https://github.com/google/google-authenticator/wiki/Key-Uri-Format" rel="nofollow noopener noreferrer" target="_blank">Google Spec</a> says:</p><blockquote><p>The period parameter defines a period that a TOTP code will be valid for, in seconds. The default value is 30.</p></blockquote><p>The TOTP RFC says:</p><blockquote><p>We RECOMMEND a default time-step size of 30 seconds <a href="https://datatracker.ietf.org/doc/html/rfc6238#section-5.2" rel="nofollow noopener noreferrer" target="_blank">5.2. Validation and Time-Step Size</a></p></blockquote><p>It doesn't make sense to have a negative number of second. But what about one second? What about a thousand? Lots of apps artificially restrict TOTP codes to 15, 30, or 60 seconds. But there's no specification to define a maximum or minimum value.</p><p>A user with mobility difficulties or on a high-latency connection probably wants a 5 minute validity period. Conversely, machine-to-machine communication can probably be done with a single-second (or lower) time period.</p><p><strong>Secret</strong></p><p>Google says the secret is</p><blockquote><p>an arbitrary key value encoded in Base32 according to RFC 3548. The padding specified in RFC 3548 section 2.2 is not required and should be omitted.</p></blockquote><p>Whereas Apple says it is:</p><blockquote><p>An arbitrary key value encoded in Base32. Secrets should be at least 160 bits.</p></blockquote><p>Can a shared secret be a single character? What about a thousand? Will padding characters cause a secret to be rejected or can they be safely stripped?</p><p><strong>Label</strong></p><p>The label allows you to have multiple codes for the same service. For example <code>Big Bank:Personal Account</code> and <code>Big Bank:Family Savings</code>. The Google spec is slightly confusing:</p><blockquote><p>The issuer prefix and account name should be separated by a literal or url-encoded colon, and optional spaces may precede the account name. Neither issuer nor account name may themselves contain a colon.</p></blockquote><p>What happens if they are <em>not</em> URl encoded? What about Matrix accounts which use a colon in their account name? Why are spaces allowed to precede the account name? Is there any practical limit to the length of these strings?</p><p>If no label is specified, what should the default be?</p><p><strong>Issuer</strong></p><p>Google says this parameter is:</p><blockquote><p><strong>Strongly Recommended</strong> The issuer parameter is a string value indicating the provider or service this account is associated with, URL-encoded according to RFC 3986. If the issuer parameter is absent, issuer information may be taken from the issuer prefix of the label. If both issuer parameter and issuer label prefix are present, they should be equal.</p></blockquote><p>Apple merely says:</p><blockquote><p>The domain of the site or app. The password manager uses this field to suggest credentials when setting up a new code generator.</p></blockquote><p>Yubico equivocates with</p><blockquote><p>The issuer parameter is recommended, but it can be absent. Also, the issuer parameter and issuer string in label should be equal.</p></blockquote><p>If it isn't a domain, will Apple reject it? What happens if the issuer and the label don't match?</p><p><strong>Next Steps</strong></p><ul><li>If you're a user, <a href="https://codeberg.org/edent/TOTP_Test_Suite" rel="nofollow noopener noreferrer" target="_blank">please contribute tests</a> or give feedback.</li><li>If you're a developer, please check your app conforms to the specification.</li><li>If you're from Google, Apple, Yubico, or another security company - wanna help me write up a proper RFC so this doesn't cause issues in the future?</li></ul> <ol start="0"><li><p>Time-based One Time Passwords. Not the TV show you remember from your youth, grandad. ↩︎</p></li></ol> <p><a rel="nofollow noopener noreferrer" class="hashtag u-tag u-category" href="https://shkspr.mobi/blog/tag/2fa/" target="_blank">#2fa</a> <a rel="nofollow noopener noreferrer" class="hashtag u-tag u-category" href="https://shkspr.mobi/blog/tag/cybersecurity/" target="_blank">#CyberSecurity</a> <a rel="nofollow noopener noreferrer" class="hashtag u-tag u-category" href="https://shkspr.mobi/blog/tag/htop/" target="_blank">#HTOP</a> <a rel="nofollow noopener noreferrer" class="hashtag u-tag u-category" href="https://shkspr.mobi/blog/tag/mfa/" target="_blank">#MFA</a> <a rel="nofollow noopener noreferrer" class="hashtag u-tag u-category" href="https://shkspr.mobi/blog/tag/open-source/" target="_blank">#OpenSource</a> <a rel="nofollow noopener noreferrer" class="hashtag u-tag u-category" href="https://shkspr.mobi/blog/tag/totp/" target="_blank">#totp</a></p>