Most people treat the rotating six-digit code as a small magic trick. It isn't magic, and the design is genuinely elegant — a standardized algorithm that turns "prove you have this secret" into "prove you have this secret right now" without any network round-trip at code-generation time. The standard is TOTP, the Time-Based One-Time Password algorithm, defined in RFC 6238, which itself builds on the older HOTP (HMAC-Based One-Time Password) from RFC 4226.
Understanding it removes a lot of confusion about what 2FA does and doesn't protect, and why some failure modes (clock drift, the dreaded "code already used") happen.
The Shared Secret: Where It All Starts
When you scan a QR code to set up an authenticator, you are not registering an account in the usual sense. You are copying a shared secret — a random string of bytes — from the server into your app. That QR code encodes a URI that looks roughly like this:
otpauth://totp/Example:alice@example.com
?secret=JBSWY3DPEHPK3PXP
&issuer=Example
&algorithm=SHA1
&digits=6
&period=30
The secret is the only part that matters cryptographically — it's a Base32-encoded random key. After setup, both your phone and the server hold a copy of this same secret. From that moment on, no further communication is needed for code generation. Both sides can independently derive the same sequence of codes forever, because they share the key and they share the clock.
The secret living on your device is the second factor. The codes are just disposable proofs that the device holding the secret is present. If an attacker never gets the secret, they can never produce valid future codes — even after watching you enter ten of them.
Step One: Turn Time Into a Counter
TOTP is really HOTP with the counter replaced by time. HOTP increments a counter every use; TOTP derives the counter from the clock so both sides stay in sync automatically. The formula is:
T = floor((current_unix_time - T0) / period)
With the usual defaults — T0 = 0 (the Unix epoch) and period = 30 seconds — this just means: take the current Unix timestamp, divide by 30, drop the remainder. The result T is a counter that increases by one every 30 seconds and is identical on any two devices with a roughly correct clock. That shared, time-derived counter is the synchronization trick at the heart of the whole scheme.
Step Two: HMAC the Counter With the Secret
Now both sides compute a keyed hash. They take the counter T (encoded as an 8-byte value) and run it through HMAC using the shared secret as the key:
hash = HMAC-SHA1(secret, T) // 20 bytes of output
HMAC is a construction that combines a secret key with a message through a hash function in a way that's secure against forgery — you cannot produce a valid HMAC for a new input without knowing the key. The default hash is SHA-1, which is considered broken for collision resistance but remains safe in the HMAC construction, which is why RFC 6238 still permits it (SHA-256 and SHA-512 are also allowed). The output is a 20-byte string that looks completely random and changes entirely whenever the counter ticks over.
Step Three: Squeeze 20 Bytes Into 6 Digits
A 20-byte hash isn't a human-friendly code. TOTP uses a clever step called dynamic truncation to extract a number deterministically:
- Take the last byte of the hash and read its low 4 bits as a number from 0–15. Call this the offset.
- Starting at that offset, read the next 4 bytes from the hash.
- Mask off the top bit (to avoid signed-integer issues) to get a 31-bit number.
- Take that number modulo 106 to get a 6-digit code (pad with leading zeros if needed).
Using the hash itself to choose where to read from is what makes it "dynamic" — it spreads which bytes get used, so the output draws on the whole hash rather than a fixed window. The result is a 6-digit code that both your phone and the server compute identically, with no communication between them.
The server doesn't store your codes or send them. It runs the exact same calculation you do and checks whether its answer matches what you typed. Two machines, one secret, one clock, same math.
Why Verification Is a Little Forgiving
If TOTP required your phone's clock to match the server's to the second, it would fail constantly. Two real-world accommodations make it usable:
- Validation windows. The server typically accepts the code for the current 30-second step and one step on either side — a ±1 tolerance — to absorb small clock drift and the lag between you reading the code and pressing submit. This is why a code near a boundary sometimes still works a few seconds after it "expired."
- Replay protection. A well-built server remembers the last code it accepted and refuses to accept the same one twice within its window. That's the source of the "this code has already been used" error — a feature, not a bug, that stops someone from reusing a code they shoplifted off your screen.
The trade-off is real: a wider window is more forgiving of drift but gives an attacker slightly more time to use a phished code. Most implementations keep it tight for that reason.
What TOTP Protects — and What It Doesn't
TOTP is a major upgrade over passwords alone, and a clear improvement over SMS codes, which can be intercepted via SIM swapping or SS7 attacks. But it has a specific, important weakness:
| Threat | Does TOTP stop it? |
|---|---|
| Reused/leaked password (credential stuffing) | Yes — the password alone is useless without the code. |
| SMS interception / SIM swap | Yes — codes are generated on-device, never sent over the network. |
| Database breach of the service | Partly — if the server stores secrets in plaintext, a breach exposes them. Secrets should be encrypted at rest. |
| Real-time phishing (attacker relays your code instantly) | No — you can be tricked into typing a live code into a fake site that forwards it. |
That last row is why TOTP is not the end of the road. Because the code is just a number a human can be tricked into typing elsewhere, a convincing phishing page can relay it in real time. The defense is phishing resistance: cryptographic challenge-response bound to the real site's origin. That's exactly what passkeys and hardware security keys provide — the browser will only complete the handshake with the legitimate domain, so there's no code to phish.
Practical Takeaways
- Use TOTP over SMS wherever both are offered — it removes the network as an attack surface.
- Back up your secrets. Because the secret lives only on your device, losing the device without a backup or recovery codes can lock you out. Save the recovery codes a service gives you.
- Treat the QR/secret as sensitive. Anyone who copies it can clone your codes. Don't screenshot it into a synced photo library you don't control.
- Step up to passkeys for high-value accounts when available — they close the real-time phishing gap TOTP can't.
TOTP is a beautiful example of cryptographic minimalism: one shared secret, one shared clock, and a hash function turn into a continuously rotating proof of possession, with no server round-trip and no network dependency. Knowing what it does well — and the one thing it doesn't — lets you layer it correctly. For a broader comparison of factor types, see our guide to two-factor authentication methods compared.