HMAC is a way to attach a secret-keyed fingerprint to a message so the recipient can verify both that the message was not altered and that it came from someone who knows the shared key. The name stands for Hash-based Message Authentication Code. It answers a question a plain hash cannot: not merely “was this data changed in transit?” but “was this data produced by someone holding the secret?” That combination of integrity and authenticity is why HMAC sits underneath JWT signing, webhook verification, and API request signatures across the web.
What problem does HMAC solve that a plain hash does not?
A plain hash detects accidental corruption but cannot prove authorship, because anyone can compute it. If you send a message alongside its SHA-256 digest, an attacker who changes the message can simply recompute the digest to match. The hash travels in the open and the function is public, so the check only catches a garbled transmission, not a deliberate forgery. There is no secret involved, and without a secret there is nothing an attacker cannot reproduce.
HMAC closes that gap by folding a secret key into the computation. The sender and receiver share a key that no one else knows. The sender computes a tag from the message and the key; the receiver recomputes the tag using the same key and checks that it matches. An attacker who tampers with the message cannot produce a valid tag, because they do not have the key. The tag therefore proves two things at once: the message is intact, and it was authenticated by a holder of the key.
It is worth being precise about what HMAC does not do. It does not encrypt. The message content is fully visible; HMAC only appends a tag that vouches for it. If you also need to keep the content secret, you encrypt separately or use an authenticated-encryption construction. HMAC is about trust in the message, not concealment of it.
How is HMAC actually constructed?
HMAC wraps the chosen hash function in a specific two-pass structure using two derived, padded versions of the key. Rather than simply concatenating the key and the message and hashing once, which is vulnerable to length-extension attacks against certain hashes, HMAC hashes the message with an inner key pad and then hashes that result again with an outer key pad. The definition looks like this:
HMAC(K, m) = H( (K' XOR opad) || H( (K' XOR ipad) || m ) )
where:
H = a hash function, e.g. SHA-256
K' = the key, padded with zero bytes to the hash block size
(or hashed down first if it is longer than the block)
ipad = the byte 0x36 repeated to the block size
opad = the byte 0x5C repeated to the block size
|| = concatenation
XOR = bitwise exclusive-or
The two distinct pads, ipad and opad, are what give HMAC its strength. They produce two different key-dependent values, and the nested hashing defeats the length-extension weakness that would otherwise let an attacker append data to a naive keyed hash. This structure is the reason HMAC has a formal security proof: it remains a strong message authentication code even if the underlying hash has certain imperfections, which is why HMAC-SHA1 held up in practice long after SHA-1 collisions were found for other uses.
You do not implement this by hand in production; every mainstream language exposes it. The important part is understanding that the output is a fixed-size tag whose size follows the hash: HMAC-SHA256 yields a 256-bit (32-byte) tag, usually shown as 64 hexadecimal characters.
How does HMAC differ from a plain hash and from a digital signature?
HMAC sits between a plain hash and a public-key signature, sharing the speed of the former and the authenticity of the latter, but with a crucial difference in key model. The table below lays out where each fits.
| Property | Plain hash | HMAC | Digital signature (RSA/ECDSA) |
|---|---|---|---|
| Detects accidental changes | Yes | Yes | Yes |
| Detects deliberate forgery | No | Yes | Yes |
| Requires a secret | No | Yes, one shared key | Yes, a private key |
| Who can verify | Anyone | Anyone holding the shared key | Anyone with the public key |
| Non-repudiation | No | No | Yes |
| Relative speed | Fast | Fast | Slower |
The key difference from a signature is symmetry. HMAC uses one shared secret, so anyone able to verify a tag is equally able to create one. That is fine when the same party or trusted parties do both, such as a server signing and later checking its own tokens. It does not give non-repudiation: because the verifier holds the same key, they could have produced the tag themselves, so HMAC cannot prove to a third party which side created it. A public-key signature can, because only the private-key holder can sign while everyone can verify. Choose HMAC when both ends share a secret and you want speed; choose signatures when verifiers must not be able to forge.
Where do you meet HMAC in real systems?
You meet HMAC anywhere a system needs to trust a message that traveled over an untrusted channel using a pre-shared secret. A few common places make it concrete:
- JWTs with HS256. A JSON Web Token signed with the HS256 algorithm is signed with HMAC-SHA256. The server computes the tag over the token’s header and payload using its secret; on the next request it recomputes and compares. Tamper with the payload and the tag no longer matches. You can inspect a token’s parts, without the secret, using our JWT decoder.
- Webhook verification. Providers that POST events to your server often include an HMAC of the request body in a header, computed with a signing secret you both hold. Your endpoint recomputes the HMAC over the raw body and rejects the request if it does not match, which stops attackers from forging fake events.
- API request signing. Signing schemes derive an HMAC over a canonical form of the request so the server can confirm the caller holds the secret and that nothing was altered in flight.
- Key derivation. HKDF, a standard way to expand a shared secret into multiple keys, is built directly on HMAC.
If you want to see the underlying hash step for yourself, our hash generator computes hashes such as SHA-256 entirely in your browser, so nothing you type is transmitted. It is a useful way to build intuition for what the inner and outer hashing passes produce, though remember that HMAC additionally mixes in the secret key that a bare hash tool does not.
What mistakes should you avoid when using HMAC?
The most damaging HMAC mistakes are operational rather than mathematical, and they cluster around comparison, key handling, and what you sign. First, always compare tags with a constant-time function. A normal equality check can return early at the first differing byte, and the tiny timing difference can, over many attempts, let an attacker recover a valid tag byte by byte. Every crypto library ships a constant-time comparison for exactly this reason; use it instead of ==.
Second, treat the key like the secret it is. Use a random key at least as long as the hash output, keep it out of source control and logs, and rotate it if it may have leaked. Because HMAC hashes over-long keys down to block size, an enormous key adds no strength; randomness and secrecy are what count. Never paste a real production signing secret into an online tool, since the value is the entire basis of trust.
Third, be exact about what you authenticate. Sign the precise bytes that matter, verify over the same canonical representation, and remember that HMAC protects integrity and authenticity but not freshness. On its own it does not stop a captured-and-replayed valid message, so where replay matters, include a timestamp or nonce inside the signed data. Get those three things right and HMAC gives you a small, fast, and dependable guarantee that a message is both untampered and from a holder of the key.