TOTP Explained: How Authenticator Apps Make Codes Without a Network
Six digits, refreshed every thirty seconds, generated with no internet connection and verified without the code ever being transmitted. This guide walks through RFC 6238, why the shared secret is Base32, how HMAC-SHA1 truncation works, why clock drift rejects your code, and why SMS is a weaker second factor.
TOTP is the quiet success story of practical security. Hundreds of millions of people carry an implementation in their pocket, it works on a plane with no signal, and it defends against the single most common account-takeover path: a stolen password. Yet the scheme confuses users constantly — codes rejected for no visible reason, "do I lose two-factor if my phone dies?", setup strings that look like alphabet soup. All of it follows from a design that fits in one page, so it is worth reading the page.
The trick: compute, never transmit
Two-factor authentication wants proof of a second thing you have, not just something you know. The obvious design — the server generates a code and sends it to you — creates a channel that can be intercepted, delayed or phished, and requires the server to remember every outstanding code. TOTP takes the opposite route: during setup, the server and your authenticator app agree on a shared secret, once. From then on, both sides independently compute the current code from that secret plus the current time. Verification is not "did I send this code?" but "does the code you present match what I would compute right now?" Nothing is transmitted, nothing is stored per-code, and generation works entirely offline.
The shared secret: Base32 is the actual credential
Setup hands you a secret — typically 160 random bits — encoded as a string like JBSWY3DPEHPK3PXP. That encoding is Base32 (RFC 4648), which uses only A–Z and digits 2–7. The alphabet is chosen for human survival: no 0 versus O, no 1 versus I versus L, no case sensitivity, so the secret can be read aloud over the phone, transcribed by hand, or typed into a hardware token without corruption. The QR code you scan is just the same secret inside an otpauth:// URI so phones do not have to type it.
Now the part that trips almost everyone up: the secret is the second factor. Not the phone. The phone is a carrier for the secret. Anyone who has copied those 160 bits can generate every future code, forever, from anywhere in the world — silently, because the legitimate device keeps working normally. Which means losing your phone does not lose your two-factor if you recorded the secret or the recovery codes; and a service that emails you the secret "for backup" has just downgraded two-factor to one-factor plus email.
The algorithm, step by step
RFC 6238 is a thin wrapper over RFC 4226 (HOTP), which turns a counter into a code. TOTP just replaces the counter with the current time step. Both sides run the same four lines:
T = floor(unix_time / 30) # 8-byte big-endian counter
H = HMAC-SHA1(secret, T) # 20-byte digest
offset = H[19] & 0x0F # last nibble picks a window
code = (4 bytes from H[offset] & 0x7FFFFFFF) mod 10^6The security lives entirely in the HMAC: without the secret, you cannot compute the right digest, so you cannot produce the code. The truncation step is not crypto at all — it is just squeezing a 160-bit digest into six human-friendly digits, using a byte of the hash itself to pick which four bytes to read.
This is also why the SHA-1 in TOTP is not the scandal it sounds like. SHA-1 is broken for collision resistance — that is what "broken" means — but HMAC's security does not depend on collision resistance, and the threat here is forging a code without the key, which is a preimage-style problem that SHA-1 handles fine. RFC 6238 permits SHA-256 and SHA-512 too; most implementations stick with SHA-1 for ecosystem compatibility, and cryptographers generally consider that acceptable for this use.
The 30-second window and ±1 tolerance
Thirty seconds is a compromise: long enough to read, type and transmit a code from a lock screen; short enough that a guessed, glimpsed or intercepted code expires quickly. But time is measured independently on two devices that never sync, so the server does not check only its own step T — it typically accepts T−1, T and T+1, giving roughly a 90-second overlap. That tolerance is a dial with costs on both ends: widen it and you forgive badly-set clocks while handing an attacker a longer replay window and more guesses per code; narrow it and support tickets multiply. The right fix for drift is never "accept everything from the last five minutes".
And the six-digit space is small — one million codes — so verification endpoints must be rate-limited and lockout-aware. Without throttling, a window of a few steps is guessable at trivial cost; with a handful of attempts per code, brute force dies. TOTP assumes this; check that your own implementation does.
Clock drift: why your code was rejected
The most common TOTP failure is not a wrong code — it is a wrong clock. If your phone's time is off by more than the tolerance, your "current" code is the server's past or future code, and it fails. A cheap real-time clock can drift seconds per day; airplane mode stretches, manual time settings and dead-coincidence timezone edits do the rest. Two clarifications that kill most of the folklore: timezones are irrelevant, because the algorithm uses absolute Unix time, not local time; and the fix is device-side — turn on automatic date-and-time (NTP sync) and re-check before re-enrolling anything.
Server-side, the honest fix is skew learning: when a user's code verifies at step T+4, some services remember that offset for that user rather than rejecting them forever. That is a far better answer than widening the global window, because it forgives one known-bad clock instead of every attacker on earth.
What TOTP does not protect you from
- Real-time phishing. The code proves you have the secret, not which site asked for it. An adversary-in-the-middle proxy can relay your code to the real login within the same 30 seconds — the classic way authenticator-app users still get pwned.
- Secret extraction. A compromised, backed-up or malware-infected phone can leak the secret itself, which is worse than a leaked code because it never expires.
- Shoulder surfing and notification leaks. Six digits visible over your shoulder are valid for half a minute.
These gaps are exactly what newer factors attack: push confirmations and WebAuthn bind the response to the specific origin and challenge, so the code cannot be relayed. TOTP remains a strong, universal, works-everywhere second step — just not the ceiling.
Why SMS is the weaker second factor
SMS one-time codes share the "one-time" idea but bind the factor to your phone number, and that turns out to be a soft target. Numbers can be ported or SIM-swapped — socially engineering the carrier is a documented billion-dollar fraud path. The signaling network (SS7) has decades-old interception weaknesses. Malware can forward messages silently. And most damaging: the number is usually also the account-recovery channel, so the same target both proves and resets your identity. NIST's digital identity guidelines accordingly classify SMS as a restricted out-of-band method. TOTP sidesteps all of it by putting the factor in an app on a device you control, with no carrier in the loop — at the cost of setup friction and clocks, which is precisely the trade the design makes.
You can generate live TOTP codes from a Base32 secret, watch the HMAC and truncation step by step, and encode or decode secrets yourself right here, entirely in your browser.
TOTP Authenticator Code Generator2FA codes in your browser — open the toolBase32 Encode / DecodeText to Base32 — open the tool