Skip to main content OTP Authentication: What Is Really Hard About HOTP and TOTP | IoT Worker

OTP Authentication: What Is Really Hard About HOTP and TOTP

OTP is often treated as “a better SMS code” or “the 6-digit number in an authenticator app.” Once you start implementing server-side verification, handling drift, debugging reuse, and migrating tokens, it becomes clear that the hard part is not the six digits. The hard part is how the client and server stay in the same state without sending the secret directly.

HOTP and TOTP both solve the same basic problem: the server and the token share one secret, but every login must produce a proof that is short-lived and ideally usable only once. The difference is where synchronization lives. HOTP uses an event counter. TOTP uses time.

Where the Problem Comes From

Static passwords are hard not because they are hard to transmit, but because they are reusable. Once a secret is reused, phishing, credential stuffing, database leaks, and device compromise all get worse.

OTP tries to reduce that reuse without falling back to a fully interactive online challenge model. In many early deployments, the authenticator was just a small hardware token with a screen and a button, while the server still wanted the familiar “type a code and verify it” experience.

The design goals were:

  • The client and server share a secret in advance
  • Each proof can be derived from that secret
  • The result is not valid for long
  • The server does not need to store a large history of plaintext codes

That is the common starting point for RFC 4226 and RFC 6238.

What OTP Actually Solves

In one sentence:

OTP uses a shared key and a synchronized moving factor to generate a short code that proves the user currently holds that key.

OTP has three distinct pieces:

  • Shared key K
  • Moving factor: a counter for HOTP, a time step for TOTP
  • Short code: the truncated and reduced HMAC output, usually 6 to 8 digits

OTP solves short-lived proof, not the whole authentication system. It does not handle device binding, session management, risk policy, or phishing resistance by itself.

Why It Was Designed This Way

The Main Path Is Short

The core flow has only three steps:

  1. The client uses the shared key K and the moving factor to compute an HMAC
  2. The client compresses the result into a short code that a human can type
  3. The server recomputes with the same key and a candidate state, then accepts on a match

The key formulas are:

HOTP(K, C) = Truncate(HMAC-SHA-1(K, C)) mod 10^d
TOTP(K, T) = HOTP(K, floor(unix_time / step))

The real complexity is not the math. It is the synchronization model.

Why HMAC Is the Base

HOTP and TOTP use HMAC because they need a mature, low-cost pseudorandom construction over a shared secret. It gives both sides a stable result without exposing the secret.

Why the Result Is Only 6 or 8 Digits

The output has to be short enough for a human to type, while the underlying value still needs enough entropy to be useful. That tradeoff is why rate limiting and lockout are mandatory.

The dynamic truncation model is:

offset = hmac_result[last_byte] & 0x0f
binary = hmac_result[offset : offset+4]
otp = (binary & 0x7fffffff) mod 10^d

That matters because different implementations often fail on truncation, integer handling, or encoding details.

HOTP: Sync by Event

HOTP uses a counter. The token advances the counter when it generates a code, and the server searches a look-ahead window for a matching value.

That solves:

  • No clock dependency
  • Works well for button-based tokens and offline devices

The cost is state drift. If the token is pressed but the login never happens, the counters can move apart. The server must maintain a look-ahead window or it will reject valid tokens as out of sync.

TOTP: Sync by Time

TOTP replaces the counter with a time step:

T = floor(unix_time / step)

That removes explicit counter management, which is easier for mobile authenticators. The cost is clock dependence and a tolerance window on the server.

What Needs Separate Explanation

Tolerance Windows Are Part of the Protocol Reality

HOTP look-ahead windows and TOTP time windows are not optional compatibility knobs. They are an admission that the client and server cannot stay perfectly synchronized forever.

The window gives real systems room to survive drift and mistakes, but it also expands the range of values the server must accept.

“30-Second Valid” Is Only an Approximation

People often say a TOTP is valid for 30 seconds, but a server often accepts several neighboring steps. The actual acceptance logic is usually a time window around the current step.

That affects:

  • Replay handling
  • Logging and drift analysis
  • User experience near step boundaries

otpauth:// Is Not the Protocol Core, but It Matters

Most users first meet OTP through a QR code. The QR usually carries an otpauth:// URI, which is an ecosystem format for importing the shared secret and parameters into an authenticator app.

Engineering Use

What to Implement First

When implementing, first make sure:

  • HMAC, dynamic truncation, and zero padding are correct
  • The server state model is explicit
  • Failure handling, rate limiting, and audit logging are defined
  • Base32 decoding and QR import match the client behavior

What to Check First When Debugging

When debugging, check:

  • Whether the shared secret changed during import or encoding
  • Whether the HOTP counter drifted
  • Whether the server and client clocks are close enough for TOTP
  • Whether the same OTP is being replayed by retry logic or by users

The Most Dangerous Default Assumption

OTP is not a complete answer to modern authentication. It is a short-lived proof layer on top of older password systems.

References