Skip to content
pwnsy
cryptographybeginner#hmac#cryptography#message-authentication#hashing#integrity

What Is HMAC: Keyed-Hash Message Authentication Explained

HMAC proves a message is intact and from who you expect. How keyed hashing works, why a plain hash is not enough, and where HMAC fits.

You send a message across a network you do not control. Two things can go wrong. Someone changes the bytes in transit, or someone forges a message that never came from you. A plain hash catches accidental corruption, and nothing more. HMAC is the small, well-studied tool that catches both, using nothing more exotic than a hash function and a shared secret.

HMAC stands for Hash-based Message Authentication Code. It is defined in RFC 2104 and standardised by NIST in FIPS 198-1. Its job is narrow: take a message and a secret key, and produce a short tag that proves the message is intact and was produced by someone who holds that key. The receiver, holding the same key, recomputes the tag and checks it matches.

Why a plain hash is not enough

A hash function like SHA-256 maps any input to a fixed-length digest. Change one bit of the input and the digest changes completely. That property gives you integrity: attach SHA-256(message) and the receiver can detect corruption.

The gap is authenticity. A hash function is public. Anyone can compute SHA-256(message) for any message they like. If an attacker rewrites your message, they simply recompute the hash and attach the new one. The receiver sees a message and a matching digest and has no way to tell it apart from the real thing. A bare hash answers "were these bytes changed by accident", and stays silent on "did this come from who I think".

The fix is a secret. If the tag depends on a key that only the two legitimate parties know, then an attacker cannot produce a valid tag for a forged message. That is a Message Authentication Code, and HMAC is the standard way to build one from a hash.

The naive attempt and why it fails

The obvious idea is hash(key || message), prepending the secret to the message before hashing. For many hash functions this is broken.

Hashes in the SHA-2 family use the Merkle-Damgard construction, which processes input in blocks and carries an internal state forward. This makes them vulnerable to a length-extension attack. Given hash(key || message) and the length of the input, an attacker can compute hash(key || message || padding || extra) for attacker-chosen extra, without ever knowing the key. They extend your authenticated message with new content and produce a valid tag for the result. That defeats the whole purpose.

Do not roll your own MAC

The simple hash(secret || message) pattern looks reasonable and is exploitable against Merkle-Damgard hashes like SHA-1 and SHA-256. HMAC exists precisely so you never have to reason about length extension yourself. Use the standard construction.

How HMAC is built

HMAC wraps the hash in a specific nested structure that closes the length-extension hole. In shorthand, with hash function H:

HMAC(K, m) = H( (K XOR opad) || H( (K XOR ipad) || m ) )

Two constants do the work. ipad is the byte 0x36 repeated, opad is the byte 0x5c repeated, each as long as the hash's block size. The key K is padded to the block length first (and hashed down first if it is longer than a block).

Read it inside out. The inner hash mixes one key-derived pad with the message. The outer hash mixes the other key-derived pad with that inner result. Because the final output is a hash of a fresh, fixed-length value, an attacker cannot extend it. The two different pads mean the inner and outer stages use effectively different keys, which is what the security proof leans on.

You never implement this by hand. Every language ships it.

EnvironmentCall
Pythonhmac.new(key, msg, hashlib.sha256)
Node.jscrypto.createHmac('sha256', key)
Gohmac.New(sha256.New, key)
OpenSSL CLIopenssl dgst -sha256 -hmac <key>

The hash you plug in decides the tag length and strength. HMAC-SHA256 produces a 256-bit tag and is a sound default. HMAC-SHA1 is still cryptographically usable for authentication despite SHA-1 being broken for collisions, though new systems should prefer SHA-256 or SHA-384.

Where HMAC shows up

Once you know the shape, you see it everywhere.

  • API request signing. Cloud providers sign each request with an HMAC over the method, path, headers, and body, keyed on your secret. The server recomputes it to confirm the request is authentic and unmodified.
  • JSON Web Tokens. The HS256 algorithm is HMAC-SHA256 over the token's header and payload. See What Is a JWT.
  • TLS. The record protocol uses HMAC (or AEAD constructions) to authenticate each record. See How the TLS Handshake Works.
  • Cookie and session integrity. Web frameworks HMAC-sign session cookies so a client cannot tamper with the contents.
  • Key derivation. HKDF, the standard HMAC-based key derivation function, is built directly on HMAC.

What HMAC does not give you

HMAC is symmetric. Both the sender and receiver hold the same secret key. That has a consequence worth stating plainly: HMAC cannot prove to a third party which of the two parties produced a message, because either one could have. Both can generate valid tags.

When you need a sender to be uniquely bound to a message in a way a judge or an outside verifier can check, you need a digital signature, where the signer holds a private key nobody else has. See How Digital Signatures Work. HMAC gives integrity and authenticity between parties who already share a secret. Signatures add non-repudiation and work without a shared secret.

How to use HMAC safely

  1. Compare tags in constant time. A normal byte-by-byte comparison returns early on the first mismatch, and the timing leaks how many leading bytes were correct. Use hmac.compare_digest, crypto.timingSafeEqual, or your platform's equivalent.
  2. Use a full-entropy random key. HMAC's strength rests on the key being secret and unpredictable. Generate it from a secure random source, not from a password or a guessable string. See Secure Random Number Generation.
  3. Authenticate everything that matters. Sign the full set of fields an attacker could change, including the parts that carry meaning like timestamps and resource paths. Anything outside the tag is unprotected.
  4. Include a timestamp or nonce to stop replay. HMAC proves a message is authentic; it does not stop an attacker from resending a captured valid message. Bind a timestamp into the signed data and reject stale requests.
  5. Keep separate keys for separate purposes. Reusing one HMAC key across unrelated systems widens the blast radius if it leaks.
See authentication signals in context

Weak or missing message authentication turns up repeatedly in real advisories. Our Exploit Intelligence dashboard tracks live CVEs by severity and exploitation probability, so you can see which authentication and integrity flaws are actually being used rather than just theoretically dangerous.

HMAC is one of the most dependable primitives in applied cryptography. It is simple to call, cheap to compute, and backed by a clean security proof. Get the key handling and the constant-time comparison right, and it does exactly one job well: it lets a receiver trust that a message is whole and came from a holder of the shared secret.

A worked example, step by step

Walking through the construction once makes the two-pass shape concrete. Suppose you want HMAC-SHA256(K, m) where the message is the string transfer 500 to account 12345 and the key is a random 32-byte secret. SHA-256 has a block size of 64 bytes, so every step below is sized to that block.

First the key is brought to block length. If the key is shorter than 64 bytes, it is padded on the right with zero bytes until it is exactly 64 bytes long. If the key were longer than 64 bytes, it would first be hashed with SHA-256 down to a 32-byte value, then zero-padded to 64. This normalisation step is why HMAC accepts a key of any length and still has a well-defined internal shape.

Next the two pads are formed. The inner pad ipad is the byte 0x36 repeated 64 times. The outer pad opad is the byte 0x5c repeated 64 times. The padded key is combined with each pad using XOR, giving two 64-byte block-sized values. Call them the inner key block and the outer key block. They are different from each other because ipad and opad differ, and that difference is deliberate.

The inner hash runs first. You concatenate the inner key block with the message and hash the whole thing: H(inner_key_block || m). For SHA-256 this yields a 32-byte digest. This inner digest already binds the key to the message, but on its own it would still be exposed to length extension, so a second pass is required.

The outer hash finishes the job. You concatenate the outer key block with the inner digest and hash again: H(outer_key_block || inner_digest). The output of this second hash is the HMAC tag, 32 bytes for SHA-256. Because that tag is the hash of a short, fixed-length value that an attacker cannot reconstruct without the key, there is nothing to extend.

On the receiving side the process is identical. The receiver holds the same secret, recomputes the tag over the message it received, and compares its result against the tag that arrived. If they match, the message is intact and was produced by a key holder. If a single byte of the message changed in transit, the inner hash diverges, the outer hash diverges, and the tags will not match.

Two passes, two different keys

The security argument for HMAC leans on the inner and outer stages behaving like two independent keyed operations. XOR-ing the same padded key with two distinct constants, ipad and opad, produces two effectively different keys from one secret. That is why you cannot simplify HMAC down to a single hash pass and keep its guarantees.

The length-extension attack in more detail

The reason HMAC exists at all is worth understanding through the attack it defeats. Hash functions in the SHA-1 and SHA-2 families follow the Merkle-Damgard design. They start from a fixed initial state, break the input into fixed-size blocks, and feed each block through a compression function that updates a running internal state. When the last block is processed, the final internal state is the digest.

The problem is that the digest is the internal state. If you know H(secret || message), you know the exact state the hash machine was in after it finished processing secret || message plus its padding. You can load that state back into a fresh hash computation and keep feeding it new blocks, as if the original input had continued. The result is a valid digest for secret || message || glue_padding || attacker_data, and you produced it without ever knowing the secret.

For a naive hash(key || message) MAC this is fatal. Imagine an API that authorises user=alice&role=guest with a trailing digest. An attacker who captures one valid request can append &role=admin and compute a matching digest through length extension, because appending to the end of the signed data is exactly what the attack allows. The server recomputes the digest, it matches, and the forged elevation is accepted.

HMAC closes this because the value an attacker sees is the output of the outer hash, H(outer_key_block || inner_digest). To extend that, the attacker would need the internal state after the outer hash, which is the tag itself, but extending it would produce H(outer_key_block || inner_digest || glue || data), a value that no legitimate verifier ever computes. The verifier always hashes the outer key block with a fresh inner digest of the full message. The extended value corresponds to no valid message, so the forgery fails the check.

HMAC compared to other message authentication codes

HMAC is the most widely deployed MAC, and it is far from the only one. The alternatives make different trade-offs, and knowing where each fits helps you read a protocol specification correctly.

MACBuilt onNotes
HMACAny secure hash (SHA-256, SHA-384, SHA-3)Immune to length extension, ubiquitous, simple to deploy, works with hardware and software hashes
CMACA block cipher (usually AES)Standardised in NIST SP 800-38B, useful where a device already has AES and no hash
GMACAES in Galois Counter modeThe authentication half of AES-GCM, very fast with hardware acceleration, sensitive to nonce reuse
Poly1305A one-time key derived per messagePaired with ChaCha20 in modern TLS, extremely fast in software, requires a fresh key per message
KMACKeccak (SHA-3)A native keyed mode of SHA-3 that needs no HMAC wrapper because SHA-3 is not vulnerable to length extension

One point on that last row is worth stating plainly. SHA-3 uses a sponge construction, not Merkle-Damgard, so it is not vulnerable to length extension in the first place. That is why SHA-3 has KMAC as a direct keyed mode. The HMAC wrapper is a fix for a Merkle-Damgard weakness, and a hash that never had the weakness does not need the wrapper. HMAC-SHA3 is still defined and secure, it is simply redundant work.

The practical guidance for most systems stays the same. If you have a hash and a shared secret and you need a MAC, HMAC-SHA256 is a correct, well-understood default. Reach for GMAC or Poly1305 when they come bundled inside an authenticated encryption mode you are already using, and reach for CMAC when the platform is a constrained device that has a block cipher but no hash.

Detection signals: spotting a broken HMAC deployment

HMAC almost never fails because the algorithm is weak. It fails because of how it was wired into a system. When you review an integration, these are the concrete signs of trouble.

The tag is compared with an ordinary equality operator. A comparison that returns as soon as it finds a mismatched byte leaks, through its running time, how many leading bytes were correct. Given enough attempts an attacker can recover a valid tag byte by byte. The fix is a constant-time comparison, and its absence in code review is a real finding.

The signed data does not cover a field that carries meaning. If a request includes a timestamp, an amount, or a resource path that is not inside the HMAC input, an attacker can change that field freely. Look for any security-relevant value that travels alongside the tag but outside it.

There is no replay protection. A valid signed request that can be captured and resent unchanged is still valid every time. If the signed data has no timestamp, nonce, or sequence number that the server checks for freshness, replay is open.

The key came from a low-entropy source. An HMAC key derived from a password, a hostname, or a short constant is guessable, and a guessable key defeats the whole scheme. Keys should come from a cryptographically secure random generator.

One key is reused across unrelated purposes. Using the same HMAC key to sign cookies, API requests, and internal tokens means a leak anywhere compromises everything. Separate keys per purpose contain the damage.

Common mistakes and misconceptions

A few beliefs about HMAC come up repeatedly and are worth correcting directly.

HMAC encrypts the message. It does not. HMAC produces a tag that proves integrity and authenticity, and the message travels in the clear unless you also encrypt it. If you need confidentiality, use authenticated encryption or encrypt separately and then authenticate.

A longer key always means more security. HMAC keys longer than the hash block size are hashed down to the digest length before use, so a 200-byte key gives no more strength than a well-chosen key at the block size. The right target is a full-entropy key around the hash output length, commonly 32 bytes for SHA-256.

SHA-1 being broken means HMAC-SHA1 is broken. The collision weaknesses in SHA-1 do not translate into a practical break of HMAC-SHA1, because HMAC does not rely on collision resistance the way a signature over a raw hash does. HMAC-SHA1 remains usable for authentication, though new designs should still prefer SHA-256 or SHA-384 for margin.

HMAC gives non-repudiation. It does not. Because both parties share the same key, either one could have produced any valid tag, so a tag cannot prove to an outsider which party created a message. Non-repudiation requires a digital signature with a private key only one party holds.

Verifying the tag is enough. Verifying proves the message is intact and from a key holder. It says nothing about whether the message is fresh, in order, or authorised. Those properties come from the surrounding protocol, through nonces, timestamps, and access checks.

How HMAC came to be

HMAC was published in 1996 by Mihir Bellare, Ran Canetti, and Hugo Krawczyk, and formalised as RFC 2104 in 1997. Its arrival answered a real and pressing question of the era: designers kept inventing home-grown ways to key a hash, and several of those schemes were quietly broken by length extension and related attacks. HMAC offered a single construction with a security proof, so protocol designers could stop improvising.

The proof matters to its longevity. HMAC comes with a reduction showing that if the underlying compression function is a secure pseudorandom function, then HMAC is a secure MAC. That means HMAC's safety does not hinge on the hash being collision resistant, which is exactly why HMAC-SHA1 survived the collapse of SHA-1's collision resistance. NIST standardised the construction as FIPS 198 in 2002, updated to FIPS 198-1, cementing it for government and regulated use.

From there HMAC spread into the foundations of modern protocols. TLS used HMAC in its record authentication and in the pseudorandom function that derives keys. IPsec uses HMAC to authenticate packets. HKDF, the standard key derivation function specified in RFC 5869, is built entirely from HMAC, using it first to extract entropy and then to expand it into as many keys as a protocol needs. JSON Web Tokens adopted HMAC as their symmetric signing option. The pattern repeated because the primitive is small, fast, provably sound, and available in every cryptographic library.

Frequently asked questions

Is HMAC encryption? No. HMAC provides integrity and authenticity, not confidentiality. It produces a tag that lets a receiver detect tampering and confirm the sender holds the shared key, and the message itself is not hidden. To keep a message secret you need encryption, ideally an authenticated encryption mode that also provides the integrity guarantee.

Which hash should I use with HMAC? HMAC-SHA256 is a sound default for new systems. Use HMAC-SHA384 or HMAC-SHA512 when you want a larger security margin or a longer tag. Avoid MD5 for new work, and prefer to move existing HMAC-SHA1 usage to SHA-256 over time, even though HMAC-SHA1 is not practically broken.

How long should an HMAC key be? Aim for a full-entropy random key roughly the length of the hash output, so 32 bytes for SHA-256. Keys longer than the hash block size are hashed down internally and gain nothing, and keys much shorter than the output reduce your security margin. The key must come from a secure random source.

Can HMAC be forged without the key? Not for a well-chosen key and a sound hash. Forging a valid tag for a new message requires either the secret key or a break of the underlying construction, neither of which is feasible with HMAC-SHA256 and a random key. The realistic failure modes are implementation errors, a leaked key, or a low-entropy key, not the algorithm itself.

Why do I need constant-time comparison? A normal comparison stops at the first differing byte, so its running time reveals how many leading bytes of a guessed tag were correct. An attacker can exploit that timing to recover a valid tag incrementally. A constant-time comparison always examines the full length, removing the signal.

Does HMAC stop replay attacks? No. HMAC confirms a message is authentic and unaltered, and a captured valid message stays valid if it is resent. To block replay you must bind a timestamp, nonce, or sequence number into the signed data and have the receiver reject stale or repeated values.

What is the difference between HMAC and a digital signature? HMAC is symmetric: both parties share one secret, and either can generate or verify a tag. A digital signature is asymmetric: the signer holds a private key and everyone else verifies with the public key. HMAC gives integrity and authenticity between parties who already share a secret, while signatures add non-repudiation and work without any shared secret.

Sources & further reading

Sharetwitterlinkedin

Related guides