Back to blogs

How Authenticators Work

·8 min read
How Authenticators Work

Two-factor authentication has become so normalized that most engineers barely think about it anymore. You scan a QR code, your phone starts generating six digits every 30 seconds, and somehow Google, GitHub, AWS, or your bank accepts them.

But under that tiny six-digit code sits one of the most elegant and battle-tested authentication designs ever standardized by the IETF.

This article dives deep into:

  • HOTP and TOTP internals
  • The RFCs behind modern authenticator apps
  • Dynamic truncation and HMAC details
  • Time synchronization problems
  • Real-world implementation pitfalls
  • And where authentication is heading next: Passkeys, WebAuthn, Push MFA, and phishing-resistant auth

The Core Idea Behind Authenticators

Both the client and server independently generate the same short-lived secret at the same time.

No OTP is transmitted during generation.

No network request is needed.

The server and the authenticator simply share:

  1. A secret key
  2. An algorithm
  3. A synchronized notion of time (or counter)

That’s it.

The entire ecosystem is built on this principle.

The Historical Foundation: RFC 4226 (HOTP)

The original standard behind modern authenticators is:

Published in 2005 by the IETF.

HOTP introduced the concept of generating one-time passwords using:

  • a shared secret
  • a moving counter
  • HMAC-SHA1

The RFC formula is:

HOTP(K, C) = Truncate(HMAC-SHA1(K, C))

Where:

  • K = shared secret
  • C = counter

HOTP Internals

The process looks simple on paper.

Step 1 — Shared Secret

The server generates a random secret:

JBSWY3DPEHPK3PXP

Usually Base32 encoded.

This secret gets embedded inside:

  • a QR code
  • provisioning URI
  • hardware token

Both the server and client store this secret permanently.

This is the root of trust.

Compromise this secret and the entire MFA chain collapses.


Step 2 — Counter

HOTP uses a monotonically increasing counter.

Example:

1
2
3
4
...

Each authentication increments the counter.

Unlike TOTP, time is irrelevant here.


Step 3 — HMAC-SHA1

The client computes:

HMAC-SHA1(secret, counter)

The counter is encoded as an 8-byte big-endian integer.

Example:

struct.pack(">Q", counter)

The result is a 20-byte SHA1 HMAC digest.

Example:

1f8698690e02ca16618550ef7f19da8e945b555a

Why HMAC Matters

HMAC is critical here.

A plain hash would be vulnerable to extension attacks and other manipulation techniques.

HMAC transforms the hash function into a keyed MAC (Message Authentication Code).

RFC 2104 defines HMAC formally.

Its security depends on:

  • secrecy of the key
  • cryptographic strength of SHA1 (or SHA256/SHA512 later)

Importantly:

HOTP/TOTP do NOT rely on SHA1 collision resistance.

This is often misunderstood.

Even though SHA1 collisions are broken, HMAC-SHA1 remains practically secure for TOTP usage because HMAC security properties differ from raw hashing security assumptions.


The Weirdest Part: Dynamic Truncation

RFC 4226 introduces one of the most peculiar parts of the algorithm: dynamic truncation.

The final nibble of the HMAC determines an offset:

offset = hmac_hash[-1] & 0x0F

That offset selects 4 bytes from the digest.

Those 4 bytes become a 31-bit integer:

binary =
    ((hash[offset] & 0x7F) << 24) |
    ((hash[offset+1] & 0xFF) << 16) |
    ((hash[offset+2] & 0xFF) << 8) |
    (hash[offset+3] & 0xFF)

Then:

otp = binary % 1000000

Produces:

000000 -> 999999

This modulo reduction is what creates the familiar six-digit OTP.


HOTP’s Biggest Problem

HOTP had a nasty synchronization issue.

Imagine this scenario:

  1. User presses the hardware token button accidentally
  2. Counter advances on device
  3. Server counter remains unchanged

Now the client and server disagree.

The OTPs fail.

Servers had to implement:

  • resynchronization windows
  • look-ahead counters
  • state recovery logic

This became operationally painful at scale.


RFC 6238 — The Birth of TOTP

RFC 6238 solved HOTP’s synchronization problems.

Instead of using a manually incrementing counter:

Time itself became the moving factor.

Formula:

TOTP(K, T) = HOTP(K, floor((T - T0)/X))

Where:

  • K = shared secret
  • T = Unix time
  • T0 = epoch
  • X = timestep (usually 30 seconds)

This means:

counter = floor(unix_time / 30)

The “counter” advances automatically every 30 seconds.


Why TOTP Won

TOTP solved several operational problems:

  • no persistent counters
  • no accidental desync
  • easier server-side validation
  • stateless generation
  • better UX

This is why almost every modern authenticator uses TOTP:

  • Google Authenticator
  • Microsoft Authenticator
  • Authy
  • Okta Verify
  • Duo Mobile

TOTP Validation Windows

Clock drift is unavoidable.

Phones drift. VMs drift. Containers drift. IoT devices drift horribly.

Servers therefore validate multiple windows:

previous window
current window
next window

Usually ±30 seconds.

Some systems allow larger drift windows, but that increases replay risk.


Real TOTP Verification Logic

Server-side validation typically looks like:

for offset in [-1, 0, 1]:
    counter = current_counter + offset
    if generate_totp(secret, counter) == submitted_code:
        return True

This tiny loop is what tolerates clock skew globally.


RFC Differences: HOTP vs TOTP

RFC 4226 (HOTP)RFC 6238 (TOTP)
Counter-basedTime-based
Manual moving factorAutomatic moving factor
Requires state syncRequires clock sync
Hardware token eraSmartphone authenticator era
OTP valid until counter changesOTP expires automatically
More desynchronization issuesBetter operational usability

RFC 6238 fundamentally reuses HOTP internally.

TOTP is essentially:

  • HOTP
  • with a time-derived counter

That’s all.


SHA1, SHA256, SHA512

RFC 4226 originally standardized:

  • HMAC-SHA1

RFC 6238 expanded support for:

  • SHA1
  • SHA256
  • SHA512

In practice:

  • SHA1 remains dominant
  • mainly for compatibility

This annoys cryptographers, but operational inertia wins.


QR Codes and Provisioning

When you scan a QR code, you are usually scanning:

otpauth://totp/Service:alice@example.com?
secret=JBSWY3DPEHPK3PXP
&issuer=Service
&algorithm=SHA1
&digits=6
&period=30

This URI format was popularized by Google Authenticator.

Not formally RFC-standardized initially, but effectively became the ecosystem convention.


Base32 Encoding

Secrets are typically Base32 encoded because:

  • human-readable
  • case-insensitive
  • QR-friendly
  • avoids punctuation issues

Example:

JBSWY3DPEHPK3PXP

Decodes into raw binary key material.


Quick code example

Here is a quick code snippit which mimics the TOTP archtecture

import hmac
import hashlib
import struct
import time
 
# shared secret between server and authenticator
secret = b"supersecretkey"
 
# convert current Unix time into 30-second counter
counter = int(time.time() // 30)
 
# pack counter into 8-byte big-endian binary
counter_bytes = struct.pack(">Q", counter)
 
# compute HMAC-SHA1(secret, counter)
hmac_hash = hmac.new(
    secret,
    counter_bytes,
    hashlib.sha1
).digest()
 
# dynamic truncation
offset = hmac_hash[-1] & 0x0F
 
binary_code = (
    ((hmac_hash[offset] & 0x7F) << 24) |
    ((hmac_hash[offset + 1] & 0xFF) << 16) |
    ((hmac_hash[offset + 2] & 0xFF) << 8) |
    (hmac_hash[offset + 3] & 0xFF)
)
 
# convert into 6-digit OTP
otp = binary_code % 1_000_000
 
print(f"{otp:06d}")
 

Where Authentication Is Heading Next

Authentication is rapidly moving away from shared secrets and OTP-only flows toward cryptographic, phishing-resistant identity verification. Passwords and SMS OTPs are increasingly vulnerable to phishing proxies, SIM swapping, credential stuffing, and MFA fatigue attacks.

Passkeys and WebAuthn

Modern authentication is now centered around FIDO2, which combines WebAuthn and hardware-backed authenticators. Instead of storing reusable passwords, the device generates a public-private key pair during registration. The private key remains securely stored on the device, while the server stores only the public key.

During login, the server sends a cryptographic challenge that is signed locally by the authenticator. Since the private key never leaves the device and credentials are origin-bound, WebAuthn provides strong resistance against phishing, replay attacks, and credential theft.

Passkeys extend this model by synchronizing WebAuthn credentials securely across ecosystems like iCloud Keychain and Google Password Manager, enabling passwordless authentication across devices.

Push MFA

Push-based MFA replaces manual OTP entry with approval-based authentication through trusted devices. Modern implementations include number matching, device attestation, geolocation checks, and risk-aware prompts to prevent MFA fatigue attacks where attackers spam approval notifications.

Phishing-Resistant Authentication

The industry focus is shifting from simply enabling MFA to implementing phishing-resistant MFA. Technologies such as WebAuthn passkeys, security keys, Windows Hello, Face ID, and platform biometrics cryptographically bind authentication to the legitimate domain, making reverse-proxy phishing significantly harder.

Unlike OTPs, these methods cannot be easily intercepted, replayed, or socially engineered in real time.

RBI’s Shift Beyond OTP-Based Authentication

Recent RBI authentication guidelines for digital payments also reflect this transition. While OTP-based 2FA is still supported, the RBI is increasingly encouraging stronger alternatives such as biometrics, app-based tokens, and device-native authentication methods like fingerprint and facial recognition. Mint

The newer framework emphasizes dynamic and phishing-resistant authentication mechanisms instead of relying solely on SMS OTPs, which are vulnerable to SIM-swap attacks, interception, and phishing. RBI’s broader direction aligns with global trends toward hardware-backed and biometric-first authentication models.

The Future

Authentication systems are evolving toward being:

  • Passwordless
  • Hardware-backed
  • Context-aware
  • Risk-adaptive
  • Continuous instead of session-based

Future identity systems will increasingly rely on cryptographic assertions, trusted devices, biometrics, and behavioral signals rather than static credentials and shared secrets.