This series has spent months chasing everyday numbers back to their sources, and the trail usually goes cold. Eight glasses of water a day, the 220−age heart-rate ceiling, the 90-minute sleep cycle — our health guides kept finding numbers that everyone repeats and nobody can trace. The six digits rotating in your authenticator app are different.
Every one of those digits is derivable from two short public documents: RFC 4226 (December 2005), which defines HOTP, and RFC 6238 (May 2011), which makes it time-based. Roughly thirty pages of specification, free to read, with the design trade-offs argued in the text. No folklore, no lost citation. For once, the everyday number has a pedigree — short enough to walk through before your next code expires.
From shared secret to six digits, step by step
RFC 6238 defines the whole scheme in one line: “TOTP = HOTP(K, T)”, where K is a shared secret and T counts time steps. The journey from QR code to on-screen digits has five steps, each specified in the RFCs.
- The secret. Scanning the setup QR hands your app a shared secret K, encoded in Base32 per RFC 4648 — an alphabet of A–Z plus the digits 2–7, which leaves out 0 and 1 (the RFC notes “0 and O are easily confused, as are 1, l, and I”). This is the only moment the secret travels; afterwards both sides compute independently.
- The counter. Your phone computes T = floor((current Unix time − T0) / X). RFC 6238 §5.2 sets the defaults: T0 is the Unix epoch and X is 30 seconds. The clock's entire role is to produce an integer that increments every half-minute — which is why two devices generate the same code with no network between them.
- The HMAC. The counter is encoded as an 8-byte big-endian value and fed through HMAC-SHA-1 with the secret as key, producing 20 bytes. SHA-1 is the default; RFC 6238 says implementations “MAY use HMAC-SHA-256 or HMAC-SHA-512” instead.
- Dynamic truncation. Twenty bytes must become a short number, and RFC 4226 §5.3 does it with a trick worth quoting exactly.
- The digits. The resulting 31-bit integer is reduced mod 10Digit — mod 1,000,000 for six digits — and left-padded with zeros. That leading zero you occasionally see is not a display quirk; it is arithmetic.
Step 4 is the clever part. The last byte of the HMAC output picks where to read the code from, so the extracted window moves with every code. RFC 4226 §5.3, verbatim — a specification excerpt, not commands to run:
Let OffsetBits be the low-order 4 bits of String[19]
Offset = StToNum(OffsetBits) // 0 <= OffSet <= 15
Let P = String[OffSet]...String[OffSet+3]
Return the Last 31 bits of P
The 31-bit mask (dropping the top bit of the 4-byte window) keeps the result an unambiguous positive integer on every platform. Nothing else hides in the pipeline: secret, time step, HMAC, truncate, mod. Your bank's code and your email's differ only in the secret K.
Why 30 seconds, and why six digits
Neither number is arbitrary; the standard shows its reasoning. RFC 6238 §5.2 recommends the 30-second default explicitly as a “balance between security and usability”: a longer step leaves each code valid — and attackable — for longer, while a shorter step has users racing the countdown and typing codes that expire mid-entry.
The same section handles the fact that clocks are never perfectly aligned, and a code typed at second 29 might arrive at second 31. The RFC's guidance: “We RECOMMEND that at most one time step is allowed as the network delay.” A validator following it accepts the current code plus one step either side — the commonly implemented ±1 window, three valid codes at any moment. The tolerance belongs to the RFC, not vendor lore; §6 adds that validators should cap total drift, forward and backward, before rejecting.
Six digits is a floor, not a ceiling: RFC 4226 permits 7- and 8-digit codes through the same mod 10Digit step. The arithmetic looks thin — a six-digit code is one of 106 possibilities, and with a ±1 window three codes are valid at any instant — but the RFC pairs its §7.2 brute-force analysis with the real defence in §7.3, a recommended throttling parameter capping validation attempts. The digit count buys headroom; rate-limiting does the guarding.
The QR code is a wiki page that outlived its repo
The situation is inverted for the QR code you scan. The algorithm is an open IETF standard, but the QR format is not a standard at all. That square encodes a URI in the form otpauth://TYPE/LABEL?PARAMETERS, carrying the Base32 secret plus optional parameters: algorithm (default SHA1), digits (6 or 8, default 6), and period (“The default value is 30.”).
The format's authoritative reference is the “Key Uri Format” wiki page of the original Google Authenticator open-source project — a page last edited in November 2018, in a repository Google archived in April 2021. There is no IETF RFC for otpauth://. It is a de facto convention that the entire industry adopted and still follows in 2026, quirks included: the page itself admits that “the algorithm parameter is ignored by the Google Authenticator implementations.” The beautiful mathematics ships inside plumbing maintained by nobody, and it works anyway.
What TOTP protects against — and the misquoted SMS story
The shared-secret design protects you from a password-only compromise. A password that is phished, reused, or dumped in a breach no longer opens the account by itself; the attacker also needs the current output of your secret, which lives on your device. This is the possession factor doing its job. Any second factor is a step up from none.
What it does not buy you is phishing resistance, and NIST is blunt about the mechanism. Because the user manually types the code, nothing binds it to the real site: SP 800-63B notes that “[i]n a MitM attack, an impostor verifier could replay the OTP authenticator output to the verifier and successfully authenticate.” A convincing fake login page can relay your password and your fresh code to the real site inside the 30-second window. The 2025 revision, SP 800-63B-4, states it in five words: “OTP authentication is not phishing-resistant.” The authenticators that clear that bar bind the credential to the site cryptographically — passkeys, which we cover in our passkeys explainer.
This brings us to a common misquote: “NIST banned SMS 2FA.” It never happened, and the paper trail — preserved in Wayback captures of NIST's own draft pages — is worth laying out precisely.
| Stage | NIST's own words |
|---|---|
| Draft, captured 24 Jun 2016 | “OOB using SMS is deprecated, and will no longer be allowed in future releases of this guidance.” |
| Draft, captured 29 Jul 2016 | Same sentence, softened: “…and may no longer be allowed in future releases…” |
| NIST blog, Jul 2016 | “Deprecation is standards-speak for ‘you can use this puppy for now, but it's on its way out.’” |
| Draft, captured 29 Dec 2016 | “Out-of-band authentication using the PSTN (SMS or voice) is deprecated, and is being considered for removal in future editions of this guideline.” |
| Final Rev 3, 2017 | Neither banned nor deprecated — a new class: “Use of the PSTN for out-of-band verification is RESTRICTED…” |
| SP 800-63B-4, final Jul 2025 | Restriction retained; new hard line: “Email SHALL NOT be used for out-of-band authentication” |
RESTRICTED carries real obligations — Rev 3 §5.2.10 requires offering at least one non-restricted alternative, meaningful notice of the risks, and a migration plan — but it is permission with homework, not a ban. And the two weaknesses are different: NIST's SMS concern is the telephone channel itself (Rev 3 tells verifiers to consider “device swap, SIM change, number porting, or other abnormal behavior”); app-based TOTP removes that telco risk but keeps the shared relay-phishing risk. At no point does any revision suggest turning a second factor off — SMS 2FA remains better than no 2FA.
The apps: what the vendors say about their own products
Because the algorithm is a public standard, the apps are interchangeable at the maths level — they differ in what happens around the secret. Google Authenticator added account synchronisation in April 2023; Google's security blog framed it as an answer to “the complexity in dealing with lost or stolen devices,” adding “the ability to safely backup your one-time codes…to your Google Account.” Microsoft Authenticator, per Microsoft's own support pages, is “a free app that helps you sign in to all your accounts” with one-time password codes among its modes, and documents account backup and restore.
The open-source end is healthy too. Aegis Authenticator (Android, GPL-3.0) implements both RFCs and stores secrets in a vault encrypted with AES-256-GCM, unlocked by password or biometrics. 2FAS (also GPL-3.0) supports TOTP and HOTP; we verified the Android app’s repository. All of them compute the same HOTP(K, T), so migration is possible — provided you can get your secrets out, which is exactly where people lock themselves out.
This is what backup codes are for. They even have a standards home: NIST calls them look-up secrets. The rules are strict — “a secret from a look-up secret authenticator SHALL be used successfully only once,” and servers must store them hashed. Save them when you enrol, before you ever need them, and treat any phone migration as: backup codes first, move second.
Our TOTP generator, honestly
Our TOTP generator implements the pipeline above and nothing else, and we audited it against the RFCs this week. The dynamic truncation is byte-for-byte the §5.3 procedure; the verify tab accepts exactly the ±1 window RFC 6238 recommends; new secrets are 20 random bytes — 160 bits, the RFC's RECOMMENDED length, not merely its floor. The privacy claim on the page, “your secret never leaves this page,” is true in the checkable sense: the code makes no network requests, and the HMAC runs in your browser's own WebCrypto. You can confirm it from the network tab.
The audit also caught our own prose drifting from the primary record, and we corrected it today. The tool's explanatory copy now states secret-length guidance as the RFC does (at least 128 bits required, 160 recommended) instead of repeating vendor lore; describes NIST's SMS position as RESTRICTED-with-obligations rather than a “recommendation” of apps; and attributes the ±1 tolerance to RFC 6238 instead of claiming to know vendors' undocumented validation windows. The rail stands: the tool is for learning and testing; production secrets belong in a real authenticator app.
This guide explains public standards; it is not security advice for any specific account. Follow your provider's own setup and recovery documentation.
FAQ
Does my code get sent anywhere when it is generated?
No. After setup, both sides hold the same secret and run the same arithmetic on the same clock; codes are computed locally. The only network moment is typing a code into a login form — exactly the moment a phishing page can relay it.
What if my phone's clock is wrong?
The counter is derived from Unix time, so a skewed clock produces codes from the wrong window. RFC 6238 recommends validators accept at most one adjacent time step to absorb small drift and network delay, and set an explicit out-of-sync limit beyond that. Modern phones sync time automatically; persistent code failures usually trace to a manually set clock.
Did NIST ban SMS two-factor codes?
No — at no stage. A 2016 draft said SMS “will no longer be allowed” in future guidance, softened to “may” within five weeks; the 2017 final created the RESTRICTED class instead, and the 2025 revision keeps SMS permitted-but-restricted while adding that email SHALL NOT be used for codes. If SMS is your only option, keep it: restricted beats absent.
What should I do before switching phones or authenticator apps?
Save your backup codes first — NIST treats them as look-up secrets, valid once each — and confirm your new device generates matching codes before wiping the old one. Some apps sync or export secrets, as covered above, but recovery codes are the path that works regardless of app.
- RFC 6238: TOTP — Time-Based One-Time Password Algorithm (§4–6) — IETF (accessed 22 Jul 2026)
- RFC 4226: HOTP — An HMAC-Based One-Time Password Algorithm (§4, §5.3, §7.2–7.4) — IETF (accessed 22 Jul 2026)
- RFC 4648: The Base16, Base32, and Base64 Data Encodings (§3.4, §6) — IETF (accessed 22 Jul 2026)
- NIST SP 800-63B (Rev 3): Digital Identity Guidelines — Authentication (§5.1.3.3, §5.2.5, §5.2.10) — NIST (accessed 22 Jul 2026)
- NIST SP 800-63B-4: Digital Identity Guidelines — Authentication and Authenticator Management (§2.2.2, §3.1.2, §3.1.3.3, §3.2.5) — NIST (accessed 22 Jul 2026)
- SP 800-63B-4 publication record (final, July 2025) — NIST CSRC (accessed 22 Jul 2026)
- Draft SP 800-63B, capture 24 Jun 2016 (“will no longer be allowed”) — Internet Archive Wayback Machine (accessed 22 Jul 2026)
- Draft SP 800-63B, capture 29 Jul 2016 (“may no longer be allowed”) — Internet Archive Wayback Machine (accessed 22 Jul 2026)
- Draft SP 800-63B, capture 29 Dec 2016 — Internet Archive Wayback Machine (accessed 22 Jul 2026)
- Questions…and buzz surrounding draft NIST Special Publication 800-63-3 — NIST Cybersecurity Insights blog (2016) (accessed 22 Jul 2026)
- Key Uri Format — google-authenticator wiki (archived repo; page last edited Nov 2018) (accessed 22 Jul 2026)
- Google Authenticator now supports Google Account synchronization — Google security blog (24 Apr 2023) (accessed 22 Jul 2026)
- About Microsoft Authenticator — Microsoft Support (accessed 22 Jul 2026)
- Aegis Authenticator — beemdevelopment/Aegis (GPL-3.0) — GitHub (accessed 22 Jul 2026)
- 2FAS for Android — twofas/2fas-android (GPL-3.0) — GitHub (accessed 22 Jul 2026)