← All modules
Symmetric-key·26 min

Message authentication codes: HMAC, CMAC, GMAC & Poly1305

Four ways to prove a message came from someone holding a shared key and wasn't changed on the way: one built from a hash, one from a block cipher, two from polynomial arithmetic.

View as slides: 10 visual slides, ready to present
Developer / EngineerSecurity ArchitectResearcher / AcademicGRC / Risk & Compliance
HMACFrom a hash functionCMACFrom a block cipherGMACPolynomial inGF(2¹²⁸)Poly1305Polynomial mod 2¹³⁰− 5
Proving a message came from someone with the shared key, and wasn't changed on the wayOpen the slides →

What a MAC proves, and what it doesn't

A message authentication code (MAC) is a short tag computed from a secret key and a message. Anyone who holds the same key can recompute the tag and compare: if it matches, the message came from someone holding the key (authenticity) and wasn't modified on the way (integrity). Without the key, producing a valid tag for any message the attacker hasn't already seen tagged should be infeasible, even after watching as many genuine message/tag pairs as they like. That property has a name, existential unforgeability under chosen-message attack, and it's the bar every construction in this module is measured against.

Two things a MAC does not do. It doesn't hide the message: the tag travels next to readable data unless you also encrypt. And it doesn't give non-repudiation: the verifier holds the same key as the sender, so either of them could have produced the tag, and a third party can't tell which. When you need 'only the sender could have made this', you need a digital signature instead.

The four MACs in this module come from three different building blocks. HMAC is built from a hash function. CMAC is built from a block cipher. GMAC and Poly1305 are built from a universal hash (polynomial evaluation over a finite field) masked with a one-time value, a design that goes back to Wegman and Carter in 1981. That last family is very fast, and it comes with a hard rule the other two don't have: never reuse the nonce or one-time key.

Three families of MAC

Hash-based: HMAC

Two nested calls to SHA-256 (or another hash). Deterministic, no nonce, no block cipher needed.

Block-cipher-based: CMAC

CBC-MAC with a fix for variable-length messages. Deterministic, no nonce. One AES call per 16-byte block.

Universal hash + one-time mask: GMAC, Poly1305

Polynomial evaluation in a finite field, masked with a value that must be fresh for every message. The fastest of the three, and the least forgiving.

t=MAC(K,m)Verify(K,m,t′)=[ MAC(K,m)=?t′ ]t = \mathrm{MAC}(K, m) \qquad \mathrm{Verify}(K, m, t') = \big[\, \mathrm{MAC}(K, m) \overset{?}{=} t' \,\big]

Verification is recompute-and-compare. The comparison itself has to run in constant time (see the last section).

HMAC: a hash function, used twice

HMAC (RFC 2104, standardized by NIST as FIPS 198-1) wraps a hash function H around the key and message in two nested passes. The key is first brought to the hash's block size (64 bytes for SHA-256): a key longer than that is hashed down first, and a shorter one is padded with zero bytes, giving K′. K′ is XORed with two fixed constants, ipad (0x36 repeated) and opad (0x5c repeated), to make an inner key and an outer key. The inner hash covers the message; the outer hash covers the inner hash's output.

The nesting is what defeats the length-extension attack covered in the hash functions module. A naive H(K ‖ m) with SHA-256 leaks its internal state as the tag, so an attacker can append data and keep hashing. In HMAC the tag is the output of a second, separately keyed hash, so there's no exposed state to continue from. HMAC's security proof also needs less from the hash than you might expect: it relies on the compression function behaving like a pseudorandom function, not on collision resistance. That's why HMAC-SHA-1 isn't broken the way SHA-1 signatures are, though new designs should still use HMAC-SHA-256 or stronger.

HMAC is everywhere a shared secret needs to vouch for data: JWTs signed with HS256, webhook signatures, AWS Signature Version 4, TLS 1.3's Finished messages and its HKDF key schedule, and IPsec (where HMAC-SHA-256 is truncated to 128 bits as HMAC-SHA-256-128, RFC 4868). The HMAC Playground tool computes one in your browser.

Worked example, from RFC 4231's test case 2 and checked against Node's crypto module and OpenSSL: key 'Jefe', message 'what do ya want for nothing?'. Both are shorter than a block, so K′ is the four key bytes followed by 60 zero bytes. The tag is 5bdcc146 bf60754e 6a042426 089575c7 5a003f08 9d273983 9dec58b9 64ec3843.

HMAC(K,m)=H((K′⊕opad) ∥ H((K′⊕ipad) ∥ m))\mathrm{HMAC}(K, m) = H\big((K' \oplus \mathrm{opad}) \,\|\, H((K' \oplus \mathrm{ipad}) \,\|\, m)\big)

K′ is the key brought to the hash's block size; opad = 0x5c…5c and ipad = 0x36…36; ‖ is concatenation.

HMAC-SHA256 in Node, with a constant-time check
import { createHmac, timingSafeEqual } from "node:crypto";

const key = Buffer.from("Jefe");
const msg = Buffer.from("what do ya want for nothing?");
const tag = createHmac("sha256", key).update(msg).digest();
console.log(tag.toString("hex"));
// 5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843  (RFC 4231, test case 2)

function verify(received) {
  const expected = createHmac("sha256", key).update(msg).digest();
  return received.length === expected.length && timingSafeEqual(received, expected);
}

Run against Node 22 before publishing; the output matches the RFC. In the browser, crypto.subtle.sign / crypto.subtle.verify with { name: 'HMAC', hash: 'SHA-256' } does the same job, and verify does the constant-time comparison for you.

Practice

You use a 100-byte key with HMAC-SHA256. SHA-256's block size is 64 bytes and its output is 32 bytes. How many zero bytes of padding does K′ end with?

CBC-MAC, and why it breaks on variable-length messages

The oldest way to build a MAC from a block cipher is CBC-MAC: encrypt the message in CBC mode with a zero IV and keep only the last ciphertext block. Each block is XORed into the running state and encrypted, so the final block depends on every bit of the message. For messages that are all the same, fixed length, CBC-MAC is provably secure. Payments has used it for decades: the 3DES CBC-MAC behind EMV application cryptograms is the same construction (this site's DES library implements it for the ARQC/ARPC simulator), and ISO 9797-1 standardizes several variants with extra output transformations.

Once message lengths can vary, plain CBC-MAC falls apart. Suppose an attacker has seen the tag t of a one-block message m. CBC-MAC of the two-block message m ‖ (m ⊕ t) is computed as E(E(m) ⊕ m ⊕ t) = E(t ⊕ m ⊕ t) = E(m) = t. So the attacker has a valid tag for a brand-new message without ever touching the key. We checked this with AES-128 in Node: the one-block message 'PAY 100 EUR TO A' and the forged 32-byte message both get the tag 4b97ca5b740a7096c14c034d2df294b2.

CBCMACK(m ∥ (m⊕t))=EK(EK(m)⊕m⊕t)=EK(m)=t\mathrm{CBCMAC}_K(m \,\|\, (m \oplus t)) = E_K\big(E_K(m) \oplus m \oplus t\big) = E_K(m) = t

The forgery: one observed tag is enough to build a second message that carries the same tag.

CMAC: CBC-MAC with a fixed ending

CMAC (NIST SP 800-38B, with the AES version also published as RFC 4493; it's Iwata and Kurosawa's OMAC1, and ISO 9797-1:2011 lists it as MAC algorithm 5) repairs CBC-MAC by treating the last block differently. From the key it derives two subkeys: encrypt an all-zero block to get L, then 'double' L in the field GF(2¹²⁸) to get K1, and double again for K2. Doubling is a one-bit left shift, plus an XOR of the constant 0x87 into the last byte whenever the bit shifted out was a 1 (for 64-bit ciphers like 3DES the constant is 0x1B).

If the message ends on a full block, that last block is XORed with K1 before the final encryption. If it doesn't (including the empty message), the last block is padded with a single 1 bit and then zeros, and XORed with K2. Because the attacker can't know K1 or K2, they can't predict how a message will be closed off, and the m ‖ (m ⊕ t) trick stops working. Using different subkeys for the two cases is also what stops a padded message from colliding with an unpadded one.

CMAC is the MAC to reach for when you have AES (or 3DES) but no hash function, which is common in smart cards, HSM-based payment schemes, and embedded radios. TR-31 / ANSI X9.143 key blocks versions B and D use TDES-CMAC and AES-CMAC both to derive their keys and to MAC the block (see the TR-31 Playground tool). LTE and 5G protect signalling integrity with AES-CMAC (the 128-EIA2 algorithm), and IPsec has AES-CMAC-96 (RFC 4494). Its cost is one block-cipher call per 16 bytes, run strictly in sequence, so it can't be parallelized the way GMAC can.

Worked example, from RFC 4493 and checked with OpenSSL and a from-scratch implementation in Node: key 2b7e151628aed2a6abf7158809cf4f3c.

L=EK(0128),K1=dbl(L),K2=dbl(K1)L = E_K(0^{128}), \quad K_1 = \mathrm{dbl}(L), \quad K_2 = \mathrm{dbl}(K_1)
RFC 4493 values, key 2b7e151628aed2a6abf7158809cf4f3c
L   = E_K(0^128)  = 7df76b0c1ab899b33e42f047b91b546f
K1  = dbl(L)      = fbeed618357133667c85e08f7236a8de
K2  = dbl(K1)     = f7ddac306ae266ccf90bc11ee46d513b

CMAC(empty message)                     = bb1d6929e95937287fa37d129b756746   (padded, uses K2)
CMAC(6bc1bee22e409f96e93d7e117393172a)  = 070a16b46b4d4144f79bdd9dd04a287c   (one full block, uses K1)

Every value reproduced with a from-scratch CMAC in Node (AES block calls from node:crypto) and, for the tags, with OpenSSL.

Reproducing the RFC 4493 vector with OpenSSL 3
$ printf '\x6b\xc1\xbe\xe2\x2e\x40\x9f\x96\xe9\x3d\x7e\x11\x73\x93\x17\x2a' \
    | openssl mac -cipher AES-128-CBC -macopt hexkey:2b7e151628aed2a6abf7158809cf4f3c CMAC
070A16B46B4D4144F79BDD9DD04A287C

WebCrypto has no CMAC, so a browser implementation builds it from AES block calls, the way this site's TR-31 tool does.

Practice

A CMAC is computed over a 20-byte message with AES. Which subkey, K1 or K2, is XORed into the final block?

Go deeperCMAC subkeys by hand⌄

The doubling step is easy to carry out by hand on the RFC 4493 values. L starts 7d f7 6b …. Its top bit is 0 (0x7d < 0x80), so no 0x87 correction is needed and K1 is simply L shifted left by one bit across the whole 128-bit block. Each output byte is the input byte shifted left, with the top bit of the next byte carried into its lowest bit.

K1 starts fb ee …, and its top bit is 1 (0xfb ≥ 0x80). So computing K2 shifts K1 left and then XORs 0x87 into the last byte. That conditional XOR is reduction modulo the field polynomial x¹²⁸ + x⁷ + x² + x + 1, whose low terms are exactly 0x87.

dbl(X)={X≪1if msb(X)=0(X≪1)⊕012010000111if msb(X)=1\mathrm{dbl}(X) = \begin{cases} X \ll 1 & \text{if } \mathrm{msb}(X) = 0 \\ (X \ll 1) \oplus 0^{120}10000111 & \text{if } \mathrm{msb}(X) = 1 \end{cases}

Practice

L begins with the bytes 7d f7. What is the first byte of K1, in hex?

GMAC: GCM's authenticator on its own

AES-GCM (covered in the AES module) authenticates its ciphertext with GHASH, a polynomial hash in GF(2¹²⁸). The hash key H is the encryption of an all-zero block, H = E_K(0¹²⁸). The data (additional authenticated data, then ciphertext, then a block holding both lengths) is split into 128-bit blocks X₁ … Xₙ, and GHASH evaluates them as a polynomial at H. The result is masked by XOR with E_K(J₀), an encryption of the nonce-derived initial counter block, to give the tag.

GMAC (also defined in NIST SP 800-38D) is GCM with nothing to encrypt: every input goes in as additional authenticated data, and the output is just the tag. That makes GMAC a nonce-based MAC, unlike HMAC and CMAC. It's useful exactly where you want integrity without confidentiality at line rate: MACsec (IEEE 802.1AE) runs GCM-AES on Ethernet links and can operate in integrity-only mode, and IPsec defines AES-GMAC for ESP and AH (RFC 4543). GHASH's multiplications are independent of each other, and modern CPUs have a carry-less multiply instruction (PCLMULQDQ on x86, PMULL on Arm), so GMAC parallelizes and runs very fast.

The price is the nonce. If two messages are ever tagged under the same key and nonce, XORing the two tags cancels the E_K(J₀) mask and leaves a polynomial equation in H that an attacker can solve for its roots. With H in hand they can forge tags for any message under that key, not just one. This is Joux's 'forbidden attack', and it has been found in real TLS servers that reused GCM nonces. Short tags are also weaker than they look with GCM: SP 800-38D allows 96 to 128 bits generally and 32 or 64 bits only under strict limits on usage.

WebCrypto can compute a real GMAC: run AES-GCM encryption over an empty plaintext and pass the message as additionalData. As a check against the GCM specification's first test case, an all-zero key and nonce with no data at all gives the tag 58e2fccefa7e3061367f1d57a4e7455a, which Node and OpenSSL both reproduce.

GHASHH(X1,…,Xn)=X1Hn⊕X2Hn−1⊕⋯⊕XnH\mathrm{GHASH}_H(X_1, \dots, X_n) = X_1 H^{n} \oplus X_2 H^{n-1} \oplus \cdots \oplus X_n H

All arithmetic is in GF(2¹²⁸): ⊕ is XOR, and multiplication is carry-less, reduced modulo x¹²⁸ + x⁷ + x² + x + 1.

t=GHASHH(A ∥ len(A) ∥ len(C))⊕EK(J0)t = \mathrm{GHASH}_H(A \,\|\, \mathrm{len}(A) \,\|\, \mathrm{len}(C)) \oplus E_K(J_0)

For GMAC the ciphertext C is empty, so only the authenticated data A and the length block are hashed.

GMAC in the browser, through AES-GCM
const key = await crypto.subtle.importKey(
  "raw", new Uint8Array(16), { name: "AES-GCM" }, false, ["encrypt"]);
const tag = await crypto.subtle.encrypt(
  { name: "AES-GCM", iv: new Uint8Array(12), additionalData: new TextEncoder().encode("hello") },
  key,
  new Uint8Array(0), // nothing to encrypt: the output is only the 16-byte tag
);
// 0ebc710373ad2a3eb2de362dae735f14

The all-zero key and nonce are only for a reproducible example; checked with Node's WebCrypto and with OpenSSL's GMAC. A real key must be random and a nonce must never repeat under it.

Poly1305: a one-time authenticator

Poly1305, designed by Daniel J. Bernstein (published 2005 as Poly1305-AES), takes a 32-byte one-time key split into two halves, r and s. The message is cut into 16-byte chunks; each chunk gets a 0x01 byte appended (so trailing zeros still count) and is read as a little-endian number. An accumulator adds each chunk and multiplies by r, all modulo the prime 2¹³⁰ − 5, which gives the algorithm its name. At the end, s is added and the result is truncated to 128 bits to form the tag. Before use, r is 'clamped': certain bits are forced to zero so the multiplication can be done quickly with ordinary 64-bit arithmetic.

Poly1305 is secure only if each key (r, s) authenticates a single message. Tag two messages with the same key and the attacker can solve for r and forge freely. That's why it's always paired with something that hands it a fresh key per message. In ChaCha20-Poly1305 (RFC 8439, covered in the stream ciphers module), the one-time key is the first 32 bytes of ChaCha20's keystream for that nonce, block counter 0. So the rule becomes the familiar one: never reuse a nonce under the same ChaCha20 key. The construction is what TLS 1.3, QUIC, WireGuard, and OpenSSH's chacha20-poly1305@openssh.com cipher use.

Poly1305 needs no block cipher and no special CPU instructions, so it's fast in plain software. That's a big part of why ChaCha20-Poly1305 is preferred on phones and small devices without AES hardware.

Worked example, RFC 8439 section 2.5.2, checked with OpenSSL and a 20-line BigInt implementation in Node: the 34-byte message 'Cryptographic Forum Research Group' under the key 85d6be78 57556d33 7f4452fe 42d506a8 0103808a fb0db2fd 4abff6af 4149f51b gives the tag a8061dc1305136c6c22b8baf0c0127a9.

ai=(ai−1+ci)⋅r mod (2130−5),t=(an+s) mod 2128a_i = (a_{i-1} + c_i) \cdot r \bmod (2^{130} - 5), \qquad t = (a_n + s) \bmod 2^{128}

a₀ = 0, and each cᵢ is a 16-byte chunk with 0x01 appended, read little-endian.

RFC 8439 section 2.5.2, step by step
r (clamped) = 0x806d5400e52447c036d555408bed685
s           = 0x1bf54941aff6bf4afdb20dfb8a800301

chunk 1 'Cryptographic Fo'  ->  acc = 0x2c88c77849d64ae9147ddeb88e69c83fc
chunk 2 'rum Research Gro'  ->  acc = 0x2d8adaf23b0337fa7cccfb4ea344b30de
chunk 3 'up'                ->  acc = 0x28d31b7caff946c77c8844335369d03a7

tag = (acc + s) mod 2^128, written little-endian = a8061dc1305136c6c22b8baf0c0127a9

The key halves read as little-endian numbers, and the accumulator after each of the three chunks (16 + 16 + 2 bytes). Computed with the BigInt implementation mentioned above; the final tag matches the RFC and OpenSSL.

Poly1305 with OpenSSL 3
$ printf 'Cryptographic Forum Research Group' \
    | openssl mac -macopt hexkey:85d6be7857556d337f4452fe42d506a80103808afb0db2fd4abff6af4149f51b Poly1305
A8061DC1305136C6C22B8BAF0C0127A9

Fine for a test vector. In an application, use ChaCha20-Poly1305 as a whole (Node: createCipheriv('chacha20-poly1305', …)) rather than managing Poly1305 keys yourself. WebCrypto doesn't offer it at all.

Practice

Poly1305 processes a 100-byte message. How many accumulator steps (multiplications by r) does it perform?

Choosing between them

In practice the choice is usually made for you by the primitive you already have. If you're using an AEAD (AES-GCM or ChaCha20-Poly1305), the MAC is built in; don't add another. If you need a standalone MAC and have a hash function, HMAC-SHA-256 is the default: no nonce to manage, hard to misuse, and available in every library including WebCrypto. If you have only a block cipher, as in a smart card or a payment HSM's TDES/AES scheme, use CMAC. Reach for standalone GMAC when you need integrity-only protection at very high data rates and can guarantee unique nonces. The modes & AEAD module covers how a cipher and a MAC are combined safely, and the AEADs that do it for you.

The four side by side

HMAC-SHA-256

Built on SHA-256. Key of any length. No nonce. 32-byte tag (often truncated to 16). Used in JWT HS256, webhooks, AWS SigV4, TLS 1.3, IPsec.

AES-CMAC

Built on AES (TDES-CMAC on 3DES). 128/192/256-bit key. No nonce. 16-byte tag (8 for TDES). Used in TR-31/X9.143, LTE/5G 128-EIA2, smart cards.

GMAC

Built on AES and GHASH. Needs a unique 96-bit nonce per message. 16-byte tag. Used in MACsec, IPsec AES-GMAC; it's the authenticator inside every AES-GCM.

Poly1305

Built on arithmetic mod 2¹³⁰ − 5. One-time 32-byte key per message, normally derived from ChaCha20 and a nonce. 16-byte tag. Used in TLS 1.3, QUIC, WireGuard, SSH.

Getting verification right

Compare tags in constant time. A byte-by-byte comparison that stops at the first mismatch leaks, through timing, how many leading bytes of a guess were right, which lets an attacker build a valid tag one byte at a time. Use crypto.timingSafeEqual in Node, hmac.compare_digest in Python, or let crypto.subtle.verify do the comparison. The side-channel module covers why.

Encrypt, then MAC. When you combine a separate cipher and MAC yourself, compute the MAC over the ciphertext (and nonce and any header), and check it before decrypting anything. TLS's older MAC-then-encrypt order is the root of the Lucky Thirteen and padding-oracle attacks, and it's why RFC 7366 added an encrypt-then-MAC extension for TLS 1.2. Better still, use an AEAD and the order is handled for you.

Use separate keys for separate jobs. Don't use the same key for encryption and for the MAC, or the same MAC key in two different protocols. Derive distinct keys with HKDF, or with the CMAC-based KDF of SP 800-108 as TR-31 does. And make what you MAC unambiguous: if you MAC a concatenation of fields, include their lengths, or 'ab' ‖ 'c' and 'a' ‖ 'bc' get the same tag.

Size tags for online guessing. An attacker with no key can only guess, and each guess at a t-bit tag succeeds with probability 2⁻ᵗ. Truncating a tag is allowed and common (HMAC-SHA-256-128 in IPsec, 8-byte MACs in payments), but every bit you drop doubles the attacker's odds per attempt.

Pr⁡[forgery after q guesses]≈q⋅2−t\Pr[\text{forgery after } q \text{ guesses}] \approx q \cdot 2^{-t}

For a well-designed MAC with a t-bit tag, as long as q is much smaller than 2ᵗ.

Practice

A system accepts 32-bit truncated tags and doesn't rate-limit failures. An attacker makes 2²⁰ (about a million) forgery attempts. The chance at least one succeeds is roughly one in how many?

Knowledge check

Test what you just learned →

10 quick questions, with an explanation for every answer.

Up next

Modes of operation & AEAD: ECB, CBC, CTR, GCM & ChaCha20-Poly1305 →

A block cipher encrypts 16 bytes. Everything else (long messages, randomness, tamper detection, even keeping a card number a card number) comes from the mode wrapped around it, and that's where most real-world breaks happen.

Questions & feedback

Discuss this page

Ask a question or comment with your GitHub account. New questions get a first answer from Claude, an AI model, clearly marked as AI; people can correct or add to it. How this works

Showing the discussion loads a widget from giscus.app and GitHub, which then see your IP address and browser details.