Beyond the password · 5 min read
How authenticator apps make 6-digit codes
Step by step through TOTP (RFC 6238) and HOTP (RFC 4226): the shared secret, the 30-second counter, the HMAC, the truncation, and an official test vector.
An authenticator app shows a 6-digit number that changes every 30 seconds. The phone does not receive these codes from anywhere. It computes them, and the website computes the same number independently. If the two match, the site knows you hold the secret that was set up between them.
The algorithm is TOTP, the Time-Based One-Time Password algorithm, published by the IETF as RFC 6238 in 2011. It is a small extension of an older algorithm, HOTP (RFC 4226, 2005), which works on a counter instead of a clock. App-based 2FA is built on this pair of open standards, which is why an app that follows them works with any site that follows them.
Setup: the shared secret
When you turn on app-based 2FA, the site generates a random secret key and shows it to you, usually as a QR code with the same key printed underneath as text. Your app scans it and stores the key. The site stores a copy too.
That is the only time the secret travels. From then on, both sides hold the same key and the same clock, and that is all they need. RFC 4226 says the shared secret “MUST be at least 128 bits” and recommends 160 bits; NIST SP 800-63B-4 requires at least 112 bits of security strength for the key.
Because the site has to keep the secret to check codes, TOTP is a shared-secret scheme. NIST requires verifiers to protect these keys strictly, since anyone who copies the secret can generate valid codes forever.
Step 1: turn the time into a counter
TOTP counts 30-second steps since the Unix epoch (1 January 1970, 00:00:00 UTC):
T = floor(current Unix time / 30)
RFC 6238 gives the example: “with T0 = 0 and Time Step X = 30, T = 1 if the current Unix time is 59 seconds, and T = 2 if the current Unix time is 60 seconds.” Every second inside the same 30-second window gives the same T, and so the same code. The 30-second step is the RFC’s recommended default, chosen “as a balance between security and usability.”
T is written as an 8-byte big-endian integer before it goes into the next step.
Step 2: HMAC the counter with the key
The app computes HMAC-SHA-1(key, T). HMAC is a standard way of mixing a secret key into a hash, so that nobody without the key can predict or forge the output. SHA-1 produces 20 bytes (160 bits). This is the HOTP algorithm from RFC 4226, with the time step T in place of HOTP’s event counter.
(SHA-1’s collision weaknesses are a separate matter. RFC 4226 explains that “HMAC was designed so that collisions in the hash function (here SHA-1) do not yield forgeries for HMAC.” RFC 6238 also defines SHA-256 and SHA-512 variants.)
Step 3: dynamic truncation
Twenty bytes is far too long to type, so HOTP picks four of them in a way that depends on the hash itself:
- Take the low 4 bits of the last byte (byte 19). That gives an offset from 0 to 15.
- Read the 4 bytes starting at that offset as a big-endian number.
- Clear the top bit, leaving a 31-bit number. (RFC 4226 does this to avoid “confusion about signed vs. unsigned modulo computations” on different processors.)
- Take that number modulo 10^6 to get 6 digits, padding with leading zeros if needed.
Worked example: the official test vector
RFC 4226 Appendix D publishes test values so implementers can check their work. The secret is the ASCII string 12345678901234567890, which is 0x3132333435363738393031323334353637383930 in hex. For counters 0 and 1, the RFC lists:
| Count | HMAC-SHA-1(secret, count) | Truncated (hex) | Truncated (decimal) | HOTP |
|---|---|---|---|---|
| 0 | cc93cf18508d94934c64b65d8ba7667fb7cde4b0 |
4c93cf18 |
1284755224 | 755224 |
| 1 | 75a48a19d4cbe100644e8ac1397eea747a2d33ab |
41397eea |
1094287082 | 287082 |
Following the steps by hand:
- The last byte is
b0. Its low 4 bits are0, so the offset is 0. - Bytes 0 to 3 are
cc 93 cf 18, giving0xcc93cf18. - Clearing the top bit turns
ccinto4c:0x4c93cf18= 1,284,755,224. - 1,284,755,224 mod 1,000,000 = 755224.
That matches the RFC’s HOTP value. Counter 1 works the same way. The last byte is ab, low 4 bits b = 11, bytes 11 to 14 are c1 39 7e ea, clearing the top bit gives 0x41397eea = 1,094,287,082, and the code is 287082, again as listed.
For TOTP, RFC 6238 Appendix B lists 8-digit outputs with the same secret. At Unix time 59, T = floor(59 / 30) = 1, and the SHA-1 TOTP is 94287082. Its last six digits are 287082, the same as HOTP counter 1 above, because TOTP is just HOTP with T as the counter. Both checks were reproduced with Python’s standard hmac and hashlib modules before being written here.
Step 4: checking the code
The site runs the same computation for the current T and compares. Because phone clocks drift and typing takes time, a verifier usually also accepts the code for an adjacent step. RFC 6238 recommends “that at most one time step is allowed as the network delay.” NIST requires each TOTP deployment to have a defined lifetime based on expected clock drift “plus an allowance for network delay and claimant entry of the OTP,” and that each code SHALL be accepted “only once while it is valid,” which stops a code that was seen over your shoulder from being reused.
What this protects against, and what it does not
A 6-digit code has a million possible values. That is small, so TOTP depends on rate limiting. NIST requires that failed attempts be limited (no more than 100 consecutive failures before the authenticator is disabled), which keeps the chance of a blind guess within those 100 tries at about 1 in 10,000 when one code is valid at a time.
TOTP stops attackers who only have your password, such as from a breach at another site. It does not stop a real-time phishing page, which can ask for the code and relay it to the real site within the same 30 seconds. NIST is explicit: “OTP authentication is not phishing-resistant.” Passkeys close that gap by binding the login to the site’s domain.
Moving and backing up
Since the secret is just a key, losing the phone means losing the codes, unless the key was backed up or exported. NIST allows two paths when changing devices: bind the app on the new device as a new authenticator, or export the key into a sync service that meets its requirements. Sites that offer TOTP usually also issue recovery codes for the case where the app is gone. For how 2FA methods compare overall, see two-factor authentication explained.
Sources
General explanation, not advice for a particular situation. Updated 2026-10-31.