What is HMAC, and why was it designed instead of using a hash directly?
HMAC = Hash-based MAC = a standardised way to turn a cryptographic hash function (SHA-256, etc.) into a secure MAC by mixing in a key. Also called a "keyed hash."
Why not just H(key || message)? Naive constructions are vulnerable:
H(key || message): vulnerable to length-extension attacks on Merkle-Damgård hashes (MD5, SHA-1, SHA-2). Attacker can extend the message without knowing the key.H(message || key): vulnerable if the hash has collisions — findm₁ ≠ m₂withH(m₁) = H(m₂)and your MAC is forged.H(key || message || key): better, but the security proof is messy.
HMAC's construction (RFC 2104):
HMAC(K, m) = H( (K ⊕ opad) || H( (K ⊕ ipad) || m ) )
opad = 0x5C repeated to block size
ipad = 0x36 repeated to block size
Two nested hashes, two key-dependent constants. Provably secure as long as the underlying hash is a PRF (pseudo-random function). It's even immune to MD5's collisions — HMAC-MD5 is still considered secure as a MAC (though everyone uses HMAC-SHA-256 now).
Where HMAC is used everywhere:
- TLS — HMAC-SHA-256 in HKDF (key derivation) and the older cipher suites.
- JWT —
HS256algorithm is HMAC-SHA-256; signs the token payload. - OAuth 1.0, AWS API signatures, Stripe webhooks — all use HMAC for request authentication.
- TOTP (Google Authenticator codes) — uses HMAC-SHA-1 internally.
Tip: When implementing webhook verification or API signing, use a constant-time comparison (hmac.compare_digest, crypto_memcmp) instead of == on the MAC. Naive comparison leaks the prefix via timing → attacker can forge MACs byte by byte.