How does a time-based one-time password (TOTP), as in Google Authenticator, work?
App and server share a secret seed; both compute an HMAC of the seed and the current 30-second time step, truncate it to a short code, and the server accepts the login if the code the user types matches its own.
* Both sides compute the same code independently: only the code travels, never the seed. *
- Enrollment: the server generates a random secret seed and shows it as a QR code; the app stores it. From now on both sides know the seed.
- Code generation: the app takes the current Unix time, divides it into steps (usually 30 seconds), and computes HMAC(seed, time step). A few bytes of the result are turned into a 6-digit code (dynamic truncation).
- Verification: the server does the same calculation, usually also allowing the neighbouring time step for clock drift, and compares.
In the authenticator schema: the seed is the secret value, the time step is the input value, HMAC plus truncation is the function, and the code is the output. Because the input changes every 30 seconds, an observed code is useless soon after. The method is standardised as TOTP (RFC 6238), built on the counter-based HOTP (RFC 4226).
Two limits remain: the seed is a shared secret stored on the server too, and a phishing site can relay a fresh code in real time, so TOTP is not phishing-resistant.
Go deeper:
RFC 6238: TOTP — the algorithm, time-step size and resynchronisation.
Time-based one-time password (Wikipedia) — a compact explanation with the formula and its weaknesses.