toolhq.io

All posts
4 min readby Jameel Haider

TOTP vs HOTP: how one-time codes work

Every authenticator app shows a six-digit code that changes on a timer, but the standard underneath it comes in two flavors. HOTP and TOTP share the same cryptographic core yet differ in one decision: what the moving value is. That difference is why your phone and a server can agree on a code without ever talking to each other.

The shared core

Both algorithms compute an HMAC over a secret key and a moving counter, then truncate the result to a short number. In short:

code = truncate(HMAC-SHA1(secret, movingValue)) mod 10^digits

The secret is shared once, usually through a QR code when you enroll. After that, the client and server each run the same function locally. Nothing about the code itself is transmitted during generation, which is what makes the scheme a second factor rather than a shared password.

HOTP is counter-based

HOTP, defined in RFC 4226, uses an incrementing counter as the moving value. Each time a code is used, both sides increment the counter. The code is valid until you use it, which sounds convenient but creates a synchronization problem: if the client generates codes that never reach the server, for example when a user taps the button repeatedly, the counters drift apart. Servers handle this with a look-ahead window, accepting the next several counter values. HOTP shows up in hardware tokens more than in phone apps.

TOTP is time-based

TOTP, defined in RFC 6238, replaces the counter with the current time divided into fixed steps, almost always 30 seconds:

movingValue = floor(unixTime / 30)

Because time advances on its own, there is no state to keep in sync and no button to press. Every 30 seconds both sides compute a new step number and therefore a new code. This is why authenticator apps standardized on TOTP: it just works as long as the clocks roughly agree.

The 30-second window and clock drift

Clocks are never perfectly aligned, so a strict comparison would reject valid codes near a boundary. Servers accept a small window of adjacent time steps, typically the previous and next one, giving roughly a 90-second window of tolerance. That absorbs normal phone clock drift and the few seconds a user takes to type the code.

The lesson for users: keep your device clock set to automatic network time. If codes are consistently rejected, a wrong clock is the first thing to check.

Try it locally

You can see this in action with the OTP generator, which takes a Base32 secret and produces live TOTP codes right in your browser, so the secret never leaves your device. If you want the lower-level walkthrough of the truncation step, read how TOTP authenticator codes work.

In practice, choose TOTP unless you are working with a specific hardware token that mandates HOTP. The time-based variant removes the counter synchronization headache entirely, and the 30-second window with its small tolerance is almost always the right default.