HOTP
HOTP (HMAC-based One-Time Password, RFC 4226) is the counter-based sibling of TOTP. Where TOTP advances on a clock, HOTP advances on a shared counter that both the token and the server increment with each code used. HOTP is the canonical mode for YubiKey OATH, legacy hardware tokens, and a handful of banking flows where the server can't rely on clock sync.
URI scheme: Google Authenticator Key URI Format — the
otpauth:// scheme every authenticator agrees on.Sibling spec: TOTP (RFC 6238) — time-based variant that builds on HOTP.
What it is
An HOTP enrolment QR is a URL in the otpauth:// scheme with hotp as the type. It carries the shared HMAC secret plus a counter value that both sides must keep in sync. Format:
otpauth://hotp/LABEL?secret=SECRET&issuer=ISSUER&counter=N&digits=N&algorithm=ALG- LABEL —
Issuer:Account, URL-encoded. Example:YubiKey:alice@example.com. - secret — the shared HMAC key, Base32-encoded (RFC 4648 §6: A-Z, 2-7).
- counter — starting counter value. Usually
0, but can be resumed from a known value when re-enrolling a token. - issuer — the service name shown in the authenticator app.
- digits —
6(default) or8. RFC 4226 §5.3 specifies 6; 8 is a common extension. - algorithm —
SHA1in practice. RFC 4226 only defines SHA1; SHA256/512 variants are RFC 6238 extensions bolted on after-the-fact.
HOTP vs TOTP — when to use which
| Property | HOTP (RFC 4226) | TOTP (RFC 6238) |
|---|---|---|
| Advances on | Counter (each successful use) | Clock (every 30 s) |
| Requires clock sync | No | Yes — within ~30 s drift |
| Resync on drift | Server accepts next N counter values | Server accepts ±1 window |
| Typical deployment | YubiKey, legacy hardware tokens, offline banking | Every consumer 2FA — Google, Microsoft, 1Password, Authy |
| Counter reuse | Catastrophic — leaks the secret over time | N/A |
| Vulnerability if secret leaks | All future codes predictable | All past + future codes predictable |
Canonical test vectors
RFC 4226 Appendix D provides reference HOTP values for the secret 12345678901234567890 (20 bytes, Base32 GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ) with 6-digit output:
| Counter | HOTP (6-digit) |
|---|---|
0 | 755224 |
1 | 287082 |
2 | 359152 |
3 | 969429 |
4 | 338314 |
5 | 254676 |
6 | 287922 |
7 | 162583 |
8 | 399871 |
9 | 520489 |
Minimal QR payload for enrolment at counter 0:
otpauth://hotp/Example:test?secret=GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ&issuer=Example&counter=0Live Base32 secret validator
Same validator as the TOTP page — HOTP secrets use the identical Base32 alphabet (RFC 4648 §6: A-Z, 2-7). Runs in your browser, no server round-trip.
Common pitfalls
- Counter drift. If the user presses the token button but the server doesn't record the code (network glitch, double-click), the token counter runs ahead of the server counter. Servers handle this with a "resync window" — try the next N counter values before rejecting. YubiKey's default window is ~3; high-security deployments narrow it to 1.
- Counter reuse is catastrophic. Never accept the same counter value twice. If the server does, an attacker who sniffs one code can replay it later. RFC 4226 §7.3 mandates that the server refuse any code at or below the last-accepted counter.
- Counter export during backup. Authenticator apps that export HOTP entries sometimes export the secret but not the current counter. Importing into a new device restarts the counter at 0 — mismatched with the server, enrolment effectively broken. TOTP is immune; HOTP is fragile here.
- Base32 not Base64. Same rule as TOTP: secrets with
0,1,8,9, lowercase letters,+, or/are Base64 and will be rejected by every RFC-compliant authenticator. - SHA1 only, in practice. RFC 4226 defines SHA1 as the HMAC function. SHA256 / SHA512 are RFC 6238 TOTP-era extensions; most HOTP hardware tokens (YubiKey OATH included) do not implement them.
- Digits parameter often ignored. Many hardware tokens are fixed at 6 digits and silently truncate if you ask for 8. Verify against your target hardware before the QR hits printed cards.
- Don't mix HOTP and TOTP in the same QR. The URI scheme uses the
TYPEsegment (hotpvstotp) to switch modes. A single secret can power both, but that means both flows share the secret — if one is compromised, both fail.
Authenticator compatibility
| App / token | HOTP | SHA256/512 | 8-digit | Notes |
|---|---|---|---|---|
| YubiKey Authenticator (iOS/Android/desktop) | Yes | Yes | Yes | HOTP is the canonical YubiKey OATH mode. Full RFC 4226 support. |
| Google Authenticator | Yes | Ignored (SHA1 only) | Ignored (6 only) | The de-facto baseline. Safe for HOTP but with 6-digit SHA1 only. |
| 1Password | Yes | Yes | Yes | Full RFC support. Counter is stored and exported with the entry. |
| Bitwarden | Yes | Yes | Yes | Full RFC support. |
| Microsoft Authenticator | Yes | Yes | Yes | Full RFC support. |
| Authy | No | — | — | Dropped HOTP in recent versions. TOTP-only. |
| Duo Mobile | No | — | — | Uses its own push flow; TOTP-only fallback. |
| OATH hardware tokens (Feitian, Token2, etc.) | Yes | Varies | Varies | Canonical hardware HOTP market; always 6-digit SHA1 unless the datasheet says otherwise. |
See also
- /hotp-qr-code/ — the HOTP generator with live counter + Base32 validation.
- /standards/totp/ — the time-based sibling (RFC 6238).
- /totp-2fa-qr-code/ — the TOTP generator.
- /standards/ — back to the standards index.