The guide
A six-digit code is only as private as the square of dots that delivered its secret
Time-based one-time passwords are simple arithmetic on a shared secret and a clock. The arithmetic is sound. The moment that deserves care is the one when the secret travels to the phone as a QR code.
- December 2005 HOTP, the counter-based parent of TOTP, published as RFC 4226
- May 2011 TOTP published as RFC 6238
- 30 seconds the default time step RFC 6238 recommends
- 160 bits shared secret length RFC 4226 recommends (128 is the minimum)
- 1 in 10,000 chance that 100 guesses hit one six-digit code
Read the full guide Fold the guide
Six digits appear on a phone, last half a minute, and are replaced by six more. The scheme looks like magic and is closer to long division. A phone and a server hold the same secret, both read a clock, and both run the same short computation. If the results agree, the person holding the phone is let in.
The computation has been public for two decades, and the security analysis published with it concludes that the best attack on it is plain guessing. What goes wrong with time-based codes goes wrong around them: at the moment the secret is handed over, at the server that must keep its copy, and at the fake sign-in page that simply asks for the digits. The setup QR code sits at the first of those weak points, which is why it deserves more attention than it usually gets.
From a counter to a clock
The parent of the scheme is HOTP, published as RFC 4226 in December 2005. Token and server share a secret key and a counter. Each time a code is wanted, the token feeds the counter through HMAC, a keyed hash defined in RFC 2104 and standardized by NIST as FIPS 198-1, and turns the result into a short decimal number. Then it adds one to the counter.
Counters drift. A button pressed in a pocket advances the token and not the server, so RFC 4226 has the server look ahead through a window of future values to find its place again. The fix that followed was to stop counting presses and count time instead. RFC 6238, published in May 2011, defines TOTP as HOTP with the counter replaced by the number of time steps since the start of 1970.
The default step is 30 seconds, chosen in the RFC’s words as a balance between security and usability. A longer step gives a slow typist more room and gives a thief who glimpsed the code more room too. NIST sets an outer limit from the other side: a clock-based value must change at least once every two minutes.
What the phone actually computes
The step number is written as eight bytes, most significant first. The RFC insists on room for values larger than 32 bits, which it says the time value needs beyond the year 2038. HMAC of the secret and those eight bytes gives 20 bytes with SHA-1, and more with SHA-256 or SHA-512, members of the hash family described in RFC 6234 that RFC 6238 also allows.
Then comes the only clever step, called dynamic truncation. The low four bits of the last byte are read as a number from 0 to 15. Four bytes are taken starting at that position, the top bit is cleared so that no processor mistakes the value for a negative number, and the 31-bit result is reduced modulo one million for six digits or one hundred million for eight.
The specification’s own test case can be followed by hand and reproduced in the tool above. The secret is the ASCII text 12345678901234567890 and the time is 59 seconds after the epoch, so the step is 1. HMAC-SHA-1 gives 75a48a19d4cbe100644e8ac1397eea747a2d33ab. The last byte is ab, so the offset is 11. The four bytes there are c1397eea; with the top bit cleared they read 41397eea, which is 1,094,287,082. The last eight digits are 94287082, the value printed in the RFC, and the last six are 287082.
Nothing in that chain is secret except the key. The analysis in RFC 4226 treats the truncated outputs for different inputs as uniformly and independently distributed, so codes already seen do not predict the next.
The square of dots
The key has to reach the phone somehow, and RFC 6238 declines to say how; provisioning is out of its scope. Practice filled the gap with a link scheme, otpauth, that an authenticator app can read from a QR code. An Internet-Draft now describes it: the word totp, a label naming the account, and parameters for the secret, the issuer, the algorithm, the number of digits and the period.
The secret travels in Base32, the alphabet of 26 letters and the digits 2 to 7 from RFC 4648, with its padding left off. Names are percent-encoded under the rules of RFC 3986, so a space becomes %20 and a letter outside ASCII becomes its UTF-8 octets. The original description of the format puts the issuer in front of the account name in the label, separated by a colon; the draft records that habit while preferring the issuer as a parameter of its own. Writing it in both places serves a reader of either kind. A colon inside a name is asking for trouble, because the colon is the separator.
The draft is candid about one thing a developer should know before choosing options: at the time of writing, some authenticators ignore the algorithm, digits and period parameters. An app that quietly falls back to SHA-1, six digits and 30 seconds will show codes the server rejects. The defaults are the safe choice, and any other setting should be tested with the real app before a single user depends on it.
The QR code itself is only packaging. It is a grid of dark and light modules, from 21 to 177 along a side, carrying bytes protected by Reed-Solomon error correction at one of four strengths, so a smudged or partly covered symbol can still be read. It hides nothing. Any camera reads it, and what it reads here is the whole secret.
That makes the setup screen the most sensitive moment in the life of the credential. A screenshot, a screen share, a photo over a shoulder or a printout left on a desk each produce a perfect clone, and the clone is silent: the server cannot tell two holders of one secret apart. Show the code once, to one camera. Then close it.
The secret’s quality matters as well. RFC 4226 requires at least 128 bits and recommends 160 bits, and RFC 6238 asks for keys chosen at random or from a properly seeded cryptographic generator, pointing to RFC 4086 for what that means. In a browser the suitable source is the platform’s cryptographic generator, which MDN documents as returning cryptographically strong values.
Clocks, windows and guesses
Two clocks never agree exactly, and a person takes seconds to read and type. RFC 6238 expects the verifier to accept a code from a neighbouring step and recommends allowing at most one step for network delay. It also suggests a limit on how far a token may drift: with 30-second steps and two steps of backward tolerance, a code can be nearly 89 seconds old and still pass.
Every extra window is another guess the server makes on an attacker’s behalf. A single six-digit guess succeeds 1 in 1,000,000 times, and accepting three steps triples that. RFC 4226 says plainly that truncation makes brute force possible and that the server must throttle. NIST caps consecutive failures at 100, noting that the figure was chosen with a six-digit code in mind; a hundred guesses at one code succeed at most 1 in 10,000 times.
So six digits are enough only where wrong tries are counted. A used code must also be refused a second time within its step, which RFC 6238 requires so that an overheard code cannot be replayed.
What a time-based code cannot do
It cannot tell a real sign-in page from a copy. A person who types the digits into a convincing fake has handed them to someone who can relay them within the same 30 seconds. NIST says outright that authenticators relying on manual entry are not phishing-resistant, and the American cyber agency CISA ranks app-generated codes below the phishing-resistant kind for the same reason, while still counting them among the best options for organizations that cannot yet adopt the stronger kind.
It cannot protect the server’s copy either. The secret is symmetric, so a breach of the verifier’s key store yields every user’s key in usable form, and NIST requires those keys to be strongly protected. Nor does it survive a lost phone unless the secret was backed up somewhere, at which point the backup becomes a second thing to guard.
Passkeys answer the first two weaknesses differently. The device creates a key pair for one website, keeps the private key, and gives the site only the public key. The WebAuthn standard scopes each credential to the site that created it, so the browser will not offer it to a look-alike domain, and a stolen database of public keys signs nothing. The person unlocks the key with the device’s own screen lock, so there is nothing to read aloud or retype, and nothing for a fake page to ask for. Many passkeys are copied between a person’s devices by a sync service, which NIST accepts at its middle assurance level and rules out at the highest.
None of that retires the six digits. They ask for no hardware beyond a phone and an app, and the agencies that rank them below passkeys still rank them well above a password alone. Treat the code as a good lock with a known gap, and treat the square of dots as the key itself.
Sources
Every source consulted for this guide. Links checked October 7, 2026.
- HOTP: An HMAC-Based One-Time Password Algorithm RFC 4226, Internet Engineering Task Force
- TOTP: Time-Based One-Time Password Algorithm RFC 6238, Internet Engineering Task Force
- HMAC: Keyed-Hashing for Message Authentication RFC 2104, Internet Engineering Task Force
- The Base16, Base32, and Base64 Data Encodings RFC 4648, Internet Engineering Task Force
- Uniform Resource Identifier (URI): Generic Syntax RFC 3986, Internet Engineering Task Force
- Randomness Requirements for Security RFC 4086, Internet Engineering Task Force
- US Secure Hash Algorithms (SHA and SHA-based HMAC and HKDF) RFC 6234, Internet Engineering Task Force
- Usage specification of the otpauth URI format for TOTP and HOTP token generators Internet-Draft draft-linuxgemini-otpauth-uri-02, IETF (work in progress)
- NIST Special Publication 800-63B National Institute of Standards and Technology (SP 800-63B-4, the current revision)
- FIPS 198-1, The Keyed-Hash Message Authentication Code (HMAC) NIST Computer Security Resource Center
- Implementing Phishing-Resistant MFA Cybersecurity and Infrastructure Security Agency (CISA), October 2022
- Web Authentication: An API for accessing Public Key Credentials - Level 3 World Wide Web Consortium (W3C)
- Create a passkey for passwordless logins web.dev
- Crypto: getRandomValues() method MDN Web Docs, Mozilla
- QR code Wikipedia