A time-based one-time password (TOTP) is the six-digit code an authenticator app shows for an account, refreshing every thirty seconds. It is defined by RFC 6238, which builds on the HMAC-based one-time password of RFC 4226. The design is deliberately simple: a shared secret plus the current time produce a code that both your device and the server can compute independently, with no network round-trip and nothing to intercept.
The Shared Secret
Enrolment starts when a service generates a random secret β typically sixteen to twenty bytes β and shows it to you, usually encoded as base32 text and packaged inside a QR code. Your authenticator stores that secret; the server stores the same secret against your account. From then on both sides hold identical key material and never need to exchange it again. Base32 (RFC 4648) is used because its alphabet avoids ambiguous characters and survives being typed by hand.
The Time Step
Instead of a counter that both sides must keep in sync, TOTP derives the counter from the clock. It takes the number of seconds since the Unix epoch and divides by the period β thirty seconds by default β discarding the remainder. Everyone with a roughly correct clock computes the same counter for the same thirty-second window, which is why the code is the same on your phone and on the server without either one talking to the other.
HMAC and Dynamic Truncation
That eight-byte counter is signed with HMAC under the shared secret, using SHA-1 by default (some services choose SHA-256 or SHA-512). The result is a twenty-byte value, far too long to type. RFC 4226 defines a "dynamic truncation" to shrink it: take the low four bits of the last byte as an offset, read the four bytes starting at that offset, mask off the top bit to stay positive, and reduce the resulting number modulo ten to the power of the digit count. The leftover is your six-, seven-, or eight-digit code, zero-padded on the left.
Why Both Sides Must Agree
Because the code is fully determined by the secret, the algorithm, the digit count, and the period, both sides must agree on all four. An authenticator that assumes SHA-1 and six digits will silently produce wrong codes for a service that expects SHA-256 or eight digits β the maths runs fine, it just yields a different number. This is why the otpauth:// URI carries the algorithm, digits, and period alongside the secret.
Clock Drift and Validation Windows
Since the counter comes from wall-clock time, a device whose clock is more than a step out of sync computes the counter for the wrong window and produces a code the server rejects. To absorb small drift, servers usually accept the codes for the adjacent windows β one step before and after β rather than only the exact current one. Large skew still fails, which is the single most common reason a code "does not work" despite the secret being correct.
What the Server Stores
The server keeps only the shared secret and the parameters, and recomputes the expected code on each login to compare against what you typed. It records nothing predictive and, ideally, remembers the last accepted step so a code cannot be replayed within its own window. There is no code database and no reversible transformation: the same one-way HMAC runs on both ends, which is what makes TOTP both offline-capable and hard to phish through interception alone.
The QR Code Is Just Carrying Text
The QR you scan during enrolment is not doing anything cryptographic β it is a convenient way to move a long string of text from a screen into a phone camera without typing it. That string is the otpauth:// URI: it names the type as totp, carries an issuer and account so the app can label the entry, and includes the secret plus any non-default algorithm, digits, and period. Because it is only text, a QR you cannot scan can always be entered by hand from its underlying URI, and a secret you have as text can always be turned back into a QR. This tool exploits that symmetry: give it any one representation β secret, URI, or QR image β and it reconstructs the others. It is also why you should treat a 2FA QR like a password: anyone who photographs it gains the same code-generating ability you have.
TOTP, HOTP, and Why Time Won
TOTP's predecessor, HOTP, used a plain incrementing counter instead of the clock. The trouble was keeping the two counters aligned: if your device generated codes you never submitted, its counter ran ahead of the server's, and logins failed until a resynchronization step. Anchoring the counter to wall-clock time removes that bookkeeping entirely β the clock advances on its own, in lockstep, on every device on Earth. The price is a new dependency on having a roughly accurate clock, which modern phones satisfy automatically through network time. That trade β a shared, self-advancing counter in exchange for a mild time-sync requirement β is why nearly every authenticator app you meet today implements TOTP rather than HOTP.