Why Plain Hashes Are Not Enough For Authenticity
A plain hash provides integrity but not authenticity. Alice can hash a message. So can Bob. So can an attacker who intercepts the channel.
If Alice sends "Transfer $100" + SHA-256("Transfer $100") to Bob, Bob can verify integrity: nothing changed in transit. But an attacker could just as easily send "Transfer $10000" + SHA-256("Transfer $10000") and Bob would also verify it successfully. The hash proves nothing about who computed it.
What Bob needs is a function that only Alice could have computed. That requires a secret. A message authentication code (MAC) is a hash-like function with a key. Only someone who knows the key can compute or verify a MAC.
| Function | Inputs | What it proves |
|---|---|---|
| Plain hash | message | integrity (nothing changed) |
| MAC | message + secret key | integrity + authenticity (sender knew the key) |
| Digital signature | message + private key | integrity + authenticity + non-repudiation (third party can verify) |
Why You Cannot Just Prepend The Key
The naive first attempt: SHA-256(key || message). Looks fine. Only key holders know the result.
It is broken. Hash functions in the SHA-1 / SHA-2 family use the Merkle-Damgard construction, which processes message blocks sequentially and maintains an internal state. Given SHA-256(key || message) and the length of the input, an attacker can extend the hash to SHA-256(key || message || padding || extra) without knowing the key. This is the famous length-extension attack.
It is not a theoretical concern. The Flickr API in 2009 used MD5(secret || message). Attackers exploited length extension to forge authenticated API requests. The same pattern hit several other services in the same era.
SHA-3 (sponge construction) and BLAKE2 do not leak internal state in the same way. SHA3-256(key || message) would actually be safe. But HMAC was designed when SHA-1 and SHA-2 were dominant, and it works with any hash function, so it is what protocols actually use.
The HMAC Construction
Krawczyk, Bellare, and Canetti published HMAC in 1996, with a formal security proof. The construction nests two hash invocations with two derived keys:
HMAC(K, M) = H((K \u2295 opad) || H((K \u2295 ipad) || M))
Where:
Kis the secret key (padded to the hash's block size).opadis the byte 0x5C repeated.ipadis the byte 0x36 repeated.His the underlying hash function (SHA-256, SHA-512, etc.).\u2295is XOR.
The inner hash uses the key XORed with ipad to scramble it, then hashes the message. The outer hash uses the key XORed with opad to scramble it differently, then hashes the inner output. This nested structure defeats length-extension attacks: appending to the inner hash's output is useless because that output gets re-hashed under a different key.
The names follow the hash used: HMAC-SHA256, HMAC-SHA512, HMAC-SHA3-256, and so on. HMAC-SHA256 is by far the most common.
Live: Hash vs HMAC
Same message, two functions: plain SHA-256 and HMAC-SHA256 with a key. Both produce 256-bit outputs. The difference is that anyone can recompute the plain hash, while only key holders can produce or verify the HMAC.
Type a message and a key, see both outputs
The left card shows SHA-256(message): anyone can compute this from the message alone. The right card shows HMAC-SHA256(key, message): only someone with the key can produce or verify this. Try changing only the key and watch the HMAC change while the plain hash stays the same.
Try these experiments: Change one character of the message and watch both outputs change. Now change the key and watch only the HMAC change. The plain hash depends only on the message; the HMAC depends on both.
Where HMAC Lives
| System | How HMAC is used |
|---|---|
| JWT (HS256, HS384, HS512) | The token's signature is an HMAC over the header and payload with a server-side key. Used in OIDC, OAuth2, and most stateless auth systems. |
| AWS Signature Version 4 | Every AWS API request includes an HMAC-SHA256 over the canonical request, derived from your access key. Same pattern in GCP and Azure. |
| TLS 1.2 (legacy MAC-then-encrypt) | HMAC-SHA256 protected the integrity of each TLS record before AEAD modes took over. |
| IPsec / IKE | Per-packet integrity uses HMAC-SHA1 (legacy), HMAC-SHA256, or HMAC-SHA384. |
| SSH | Per-packet MAC uses HMAC-SHA256 (or newer chacha20-poly1305 AEAD). |
| HKDF (key derivation) | The standard key derivation function in modern protocols is just HMAC applied twice (extract then expand). Used in TLS 1.3, Signal, WireGuard. |
| Webhooks (GitHub, Stripe, etc.) | The provider HMACs each delivery with a shared secret. Your receiver verifies before processing. |
HMAC vs Digital Signatures
HMAC and signatures both authenticate a message, but they make different trust assumptions.
| HMAC | Digital signature | |
|---|---|---|
| Key model | Shared secret between two parties | Private key signs, public key verifies |
| Speed | Very fast (~3000 MB/s) | Slower (10-1000x depending on algorithm) |
| Output size | Hash size (32 bytes for SHA-256) | Larger (64-256+ bytes depending on algorithm) |
| Anyone can verify? | Only key holders | Anyone with the public key |
| Non-repudiation? | No (either party could have produced it) | Yes (only the private key holder could have signed) |
Pick HMAC when both parties already share a secret and only the two of them need to verify (API auth, internal microservice calls, webhooks). Pick signatures when verification needs to be public, when non-repudiation matters, or when key distribution would not scale otherwise.
Modern Alternatives
- KMAC (Keccak MAC): SHA-3 family MAC, simpler than HMAC. Defined in NIST SP 800-185. Adoption is slow but growing.
- Poly1305: A different style of MAC, used inside AEAD ciphers like ChaCha20-Poly1305 and AES-GCM. Not a hash-based MAC; uses polynomial evaluation over a finite field. Fast and provably secure.
- BLAKE2 keyed mode: BLAKE2 has a native keyed-hash mode that does not need the HMAC wrapper. Simpler and faster when you can use it.
None of these have displaced HMAC-SHA256 as the de-facto default in protocols, but they appear in newer designs where performance or simplicity is a priority.