What is HMAC?
HMAC explained: why a keyed hash beats a plain one, the ipad/opad construction, and where HS256 shows up in JWTs and webhooks.
HMAC (Hash-based Message Authentication Code) combines a hash function with a secret key to produce a signature that proves two things at once: the message hasn't been altered, and it was signed by someone who holds the key. It's defined in RFC 2104 and works with any underlying hash function, SHA-1, SHA-256, SHA-384, or SHA-512 are the common choices today.
A plain hash answers "did this data change?" — anyone can compute SHA-256(message) and check it matches. HMAC answers a stronger question: "did someone who knows the secret produce this?" Without the key, an attacker can't forge a valid signature even if they know exactly which hash function is in use.
The construction: two hash passes, not one
HMAC doesn't just hash the key and message together. It nests two hash operations, mixing the key into each with a different padding constant:
HMAC(key, message) = H( (key' XOR opad) || H( (key' XOR ipad) || message ) )
ipad = 0x36 repeated to the hash's block size
opad = 0x5c repeated to the hash's block size
key' = key, hashed down or zero-padded to exactly one blockThe inner hash binds the key to the message; the outer hash binds the key to that result. This two-pass structure is what makes HMAC resistant to length-extension attacks, a real weakness in the naive approach of hashing a secret and message concatenated together (see the FAQ below).
Plain hash vs HMAC
| Plain hash | HMAC | |
|---|---|---|
| Inputs | Message only | Message + secret key |
| Anyone can compute it | Yes | Only with the key |
| Proves | Data integrity | Integrity + authenticity |
| Typical use | Checksums, deduplication | Webhook signatures, API request signing, JWT HS256/384/512 |
This site's Hash Generator computes the plain version; the HMAC Generator adds the secret-key input and shows how the same message produces a completely different signature once a key is involved.
Where HMAC shows up
Webhook verification: Stripe, GitHub, and most webhook providers sign the request body with HMAC-SHA256 and send the signature in a header (Stripe-Signature, X-Hub-Signature-256). The receiving server recomputes the HMAC with its copy of the shared secret and compares, no request is trusted unless the signature matches.
===, ==, or strcmp. Those bail out at the first mismatched byte, so the comparison takes measurably longer the more leading bytes match, an attacker who can time enough requests can recover a valid signature one byte at a time. Use a constant-time function instead: crypto.timingSafeEqual() in Node, hmac.compare_digest() in Python, hash_equals() in PHP, hmac.Equal() in Go. Node's version throws if the two inputs aren't the same byte length, so check that first, the digest's length is fixed by the algorithm and isn't a secret worth protecting. AWS SigV4 chains HMAC-SHA256 through several derivation steps to sign API requests without ever putting the raw secret key on the wire. JWTs signed with the HS256, HS384, or HS512 algorithm are literally the token's header and payload run through HMAC with the API's shared secret, that's what makes a tampered JWT fail verification.
Try it now
Frequently asked questions
No. HMAC produces a fixed-size signature, not ciphertext, and there is no way to recover the original message from it, the same one-way property a plain hash has.
What HMAC adds over a plain hash is a secret key: only someone who knows the key can produce or verify a valid signature. It proves authenticity and integrity, not confidentiality, the message itself still travels in the clear unless you encrypt it separately.