HMAC (Hash-based Message Authentication Code) is everywhere in modern software, even if you have never called it by name. Every time AWS authenticates an API request, Stripe sends a webhook, or a service issues a signed JWT, HMAC is doing the work behind the scenes. This guide explains what HMAC is, how it differs from a plain hash, how HMAC-SHA256 works step by step, and how to generate and verify HMACs in real code.
You can experiment with HMAC right now using the Hash Generator: enable HMAC mode, paste a secret key, and watch the digest change as you edit the message.
HMAC in One Sentence
HMAC is a way to prove two things at once: that a message has not been altered (integrity) and that it came from someone who knows a shared secret key (authenticity). A plain hash like SHA-256 only proves integrity — anyone can recompute it. HMAC adds a secret key, so only parties who hold that key can produce or verify a valid code.
Why a Plain Hash Is Not Enough
Suppose you send a message and its SHA-256 hash to a server so it can detect tampering:
message = "amount=100&to=alice"
digest = SHA256(message)
The problem: an attacker who intercepts the request can change the message and recompute the hash, because SHA-256 is public and keyless. The server has no way to tell the forged pair apart from the real one.
HMAC fixes this by mixing in a secret key that the attacker does not have:
digest = HMAC-SHA256(key, message)
Now, without the key, an attacker cannot generate a digest that the server will accept. Changing the message invalidates the code.
How HMAC Works
HMAC is defined in RFC 2104 and works with any cryptographic hash function — HMAC-SHA256, HMAC-SHA512, HMAC-SHA1, and so on. The construction is deliberately simple but carefully designed:
HMAC(key, message) = H( (key ⊕ opad) || H( (key ⊕ ipad) || message ) )
Breaking that down:
- The key is padded (or hashed first, if it is longer than the block size) to the hash's block length.
- Two constants are XORed with the key: the inner pad
ipad(the byte0x36repeated) and the outer padopad(the byte0x5crepeated). - The message is hashed together with the inner-padded key.
- That result is hashed again together with the outer-padded key.
The double-hashing with two different key-derived values is what protects HMAC against length-extension attacks, which affect a naive H(key || message) design built on SHA-256 or MD5.
A Worked Example
With the message Hello, World! and the key secret, HMAC-SHA256 produces:
HMAC-SHA256("secret", "Hello, World!")
= fb6d8d7efc4a3b... (a 64-character hex string)
The output is always the same length as the underlying hash — 256 bits (64 hex characters) for SHA-256, regardless of how long the message is. Try it yourself in the Hash Generator with HMAC mode enabled.
HMAC-SHA256 vs Other Variants
The hash function inside HMAC determines its output size and security margin:
| Variant | Output | Common Uses |
|---|---|---|
| HMAC-MD5 | 128 bits | Legacy systems only — avoid for new designs |
| HMAC-SHA1 | 160 bits | Older TLS, some webhooks (being phased out) |
| HMAC-SHA256 | 256 bits | AWS, Stripe, GitHub, JWT — the modern default |
| HMAC-SHA512 | 512 bits | BIP-32 wallets, high-security key derivation |
Interestingly, HMAC is more resilient than its underlying hash. Even HMAC-MD5 has no practical break, because HMAC does not rely on collision resistance the way a bare hash does. Still, HMAC-SHA256 is the right default for new systems — it is universally supported and future-proof. For a deeper look at the trade-offs between the two SHA-2 sizes, see SHA-256 vs SHA-512.
Real-World Uses of HMAC
Signing API Requests
AWS Signature Version 4 derives a signing key through a chain of HMAC-SHA256 operations and signs every request with it. The server repeats the calculation; if the signatures match, the request is authentic and untampered.
Verifying Webhooks
When Stripe, GitHub, or Shopify send you a webhook, they include an HMAC signature header computed over the raw request body using a secret you both share. Your endpoint must recompute the HMAC and compare:
import hmac, hashlib
def verify(secret: bytes, body: bytes, signature: str) -> bool:
expected = hmac.new(secret, body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
Using hmac.compare_digest (a constant-time comparison) is essential — a naive == can leak the correct signature through timing differences.
Stateless Sessions and JWTs
A JSON Web Token signed with HS256 is just HMAC-SHA256(secret, header.payload). The server can verify the token without a database lookup, because only it holds the secret needed to reproduce the signature.
Generating HMAC in Code
Node.js:
const crypto = require('crypto');
const sig = crypto
.createHmac('sha256', 'secret')
.update('Hello, World!')
.digest('hex');
Python:
import hmac, hashlib
sig = hmac.new(b'secret', b'Hello, World!', hashlib.sha256).hexdigest()
PHP:
$sig = hash_hmac('sha256', 'Hello, World!', 'secret');
All three produce the identical 64-character hex string — and so does the Hash Generator with HMAC mode on, which is handy for debugging when a signature mismatch is driving you up the wall.
Common Mistakes
- Comparing signatures with
==. Always use a constant-time comparison (hmac.compare_digest,crypto.timingSafeEqual, or PHP'shash_equals) to avoid timing attacks. - Hashing the parsed body instead of the raw bytes. Webhook signatures are computed over the exact raw payload. Re-serializing JSON can change whitespace and break verification.
- Reusing the same secret everywhere. Use distinct keys per integration so a single leak has limited blast radius.
- Treating HMAC as encryption. HMAC authenticates a message; it does not hide it. To learn the difference between authentication, hashing, and encryption, read hashing vs encryption.
FAQ
What does HMAC stand for?
HMAC stands for Hash-based Message Authentication Code. It combines a cryptographic hash function (such as SHA-256) with a secret key to produce a code that verifies both the integrity and the authenticity of a message.
What is the difference between a hash and an HMAC?
A plain hash like SHA-256 uses no key, so anyone can compute it — it only proves data has not changed. An HMAC mixes in a secret key, so only parties holding that key can generate or verify a valid code. HMAC therefore proves both integrity and authenticity.
Is HMAC-SHA256 secure?
Yes. HMAC-SHA256 has no known practical attacks and is the recommended default for API signing, webhooks, and token authentication. Its security depends mainly on keeping the secret key private and using a long, random key.
Can HMAC be reversed or decrypted?
No. HMAC is built on a one-way hash function, so there is no way to recover the original message or the key from the code. It is used for verification, not for hiding data. If you need confidentiality, use encryption alongside HMAC.
How long should an HMAC secret key be?
Use a random key at least as long as the hash output — 32 bytes (256 bits) for HMAC-SHA256 is a good baseline. Longer keys do not hurt, but shorter or low-entropy keys (like dictionary words) weaken the whole scheme.
Why does my webhook signature check keep failing?
The most common cause is hashing the re-serialized body instead of the raw request bytes. Compute the HMAC over the exact payload you received, ensure you are using the correct secret, and confirm the algorithm (usually SHA-256) and encoding (hex vs base64) match what the provider documents.
Related Tools:
- Hash Generator — Generate hashes with HMAC mode
- SHA-256 Generator — Generate SHA-256 hashes
- SHA-512 Generator — Generate SHA-512 hashes
- Hash Identifier — Identify a hash by its format