MACs and Authenticated Encryption

Encryption hides data. But hiding data is not enough – there must also be a way to know it was not tampered with and that it came from the expected sender. A Message Authentication Code proves integrity and authenticity in a single operation.

Mental Model: The Sealed Envelope

Encryption is the envelope – it hides the contents. A MAC is the wax seal – it proves the envelope has not been opened or modified since the sender sealed it. Without the seal, an attacker can alter the ciphertext, and the recipient has no way to detect the tampering.

This is not hypothetical. Unauthenticated encryption is vulnerable to bit-flipping attacks, padding oracle attacks, and chosen-ciphertext attacks. Ciphertext malleability means an attacker can modify encrypted data in predictable ways without decrypting it. Encryption without authentication is a vulnerability, not a feature.

Why Hashing Alone Is Not Enough

A plain hash provides integrity (detect changes) but not authenticity (prove who sent it). An attacker who modifies a message can recompute the hash and send both. A MAC uses a shared secret key – only parties who know the key can produce or verify the tag. An attacker who does not know the key cannot forge a valid MAC, even if they can intercept and modify the message.

HMAC: MACs from Hash Functions

HMAC (Hash-based Message Authentication Code) constructs a MAC from any cryptographic hash function. HMAC-SHA256 is the most common instantiation.

The construction: HMAC(key, message) = hash(key XOR opad || hash(key XOR ipad || message)). The double hashing with different padding constants prevents length extension attacks that plague naive constructions like hash(key || message).

HMAC’s security reduces to the security of the underlying hash function. If SHA-256 is secure, HMAC-SHA256 is secure.

Length Extension Attacks

For hash(secret || message) computed with a Merkle-Damgard hash (SHA-256, SHA-1, MD5), an attacker who knows the hash output and the message length – but not the secret – can compute hash(secret || message || padding || attacker_data) without knowing the secret. This is a length extension attack.

HMAC’s nested hashing construction prevents it entirely. This is why HMAC exists instead of simply prepending a key to the message before hashing. Never build a MAC by concatenating a key with a message and hashing – use HMAC.

Why Encryption Alone Is Not Enough

AES in CTR mode encrypts by XORing plaintext with a keystream. An attacker who knows the plaintext at a specific position can flip bits in the ciphertext to produce a predictable change in the decrypted output. This is ciphertext malleability.

Consider a bank transfer message encrypted with unauthenticated AES-CTR. If an attacker knows the field “amount=1000” appears at byte offset 40, they can XOR the ciphertext at that position to change it to “amount=9999” without ever decrypting the message. The recipient decrypts it, sees a valid-looking plaintext, and processes the fraudulent transfer.

This is not a weakness of AES. It is a fundamental property of any cipher that does not include authentication. Encryption guarantees confidentiality. It does not guarantee integrity.

Padding Oracle Attacks

CBC mode encryption requires padding. If a server reveals whether decryption produced valid padding – through an error message, a timing difference, or a different HTTP status code – an attacker can exploit this oracle to decrypt the entire ciphertext without knowing the key. The POODLE attack against SSL 3.0 and the Lucky13 attack against TLS used exactly this technique. Authentication prevents these attacks because tampered ciphertexts are rejected before decryption even begins.

The Composition Problem

Given a cipher and a MAC separately, there are three ways to combine them:

Encrypt-then-MAC: encrypt the plaintext, then MAC the ciphertext. The recipient verifies the MAC first, and only decrypts if it is valid. This is the safe composition. Invalid ciphertexts are rejected without decryption, preventing entire classes of attacks that exploit the decryption process.

MAC-then-encrypt: MAC the plaintext, then encrypt both the plaintext and the tag. The recipient must decrypt before verifying the MAC. This enables padding oracle attacks – an attacker learns information from whether decryption succeeds or fails.

Encrypt-and-MAC: encrypt the plaintext and MAC the plaintext independently. The MAC tag may leak information about the plaintext because it is computed on the unencrypted data.

Real-World Consequences

TLS 1.0-1.2 used MAC-then-encrypt with CBC mode, enabling the BEAST and Lucky13 attacks. These were not implementation bugs – they were inherent weaknesses of the composition order. SSH’s original protocol used encrypt-and-MAC, which leaked information about plaintext through the unencrypted MAC tag. TLS 1.3 eliminated the problem entirely by mandating AEAD constructions exclusively, removing the composition choice from developers’ hands.

AEAD: The Right Answer

AEAD (Authenticated Encryption with Associated Data) combines encryption and authentication into a single, inseparable operation. Encryption cannot happen without authentication, and the tag cannot be left unverified. The composition problem disappears because there is no composition – it is one atomic operation.

AES-256-GCM: AES in counter mode with a Galois MAC. The dominant AEAD in TLS 1.3. Hardware-accelerated on modern CPUs via AES-NI instructions.

ChaCha20-Poly1305: ChaCha20 stream cipher paired with Poly1305 MAC. Faster in software on platforms without AES hardware acceleration. Used in WireGuard, TLS 1.3, and SSH.

The “associated data” part allows unencrypted metadata – like packet headers, sequence numbers, or protocol version fields – to be authenticated alongside the encrypted payload. Tampering with either the encrypted data or the associated data invalidates the authentication tag.

The Software Perspective

TLS 1.3 record layer: every record is encrypted and authenticated with AEAD. The cipher suite negotiation is simplified to AEAD-only options: AES-128-GCM, AES-256-GCM, ChaCha20-Poly1305. No more CBC modes, no more separate MAC composition. The record sequence number is included as associated data, preventing replay and reordering attacks.

API request signing: many APIs use HMAC for request authentication. AWS Signature V4 computes HMAC-SHA256 over a canonical representation of the HTTP request – method, path, headers, and body – using a secret access key. The server recomputes the HMAC and compares. This proves the request was not tampered with and that the sender possesses the secret key.

JWTs: JSON Web Tokens use HMAC-SHA256 (HS256) for symmetric integrity verification. The server signs the token payload with a secret key; any modification invalidates the signature. A critical implementation mistake is confusing the algorithm – some JWT libraries have been tricked into verifying an HMAC-signed token using RSA verification logic, bypassing authentication entirely.

Never Roll One’s Own

This lesson illustrates why. The composition of encryption and authentication has three options, and two of them are dangerous. AEAD constructions exist because even expert cryptographers got the composition wrong in deployed protocols. Use a well-vetted AEAD construction. Use a well-known library – libsodium, the Go standard library’s crypto packages, OpenSSL. Do not implement primitives yourself. Do not invent new ways to combine them.

Key Takeaways

This lesson establishes:

  • The difference between a hash and a MAC
  • Why encryption without authentication is vulnerable to ciphertext malleability
  • Why encrypt-then-MAC is safe but MAC-then-encrypt is not
  • What AEAD provides that separate encryption and MAC do not
  • How HMAC prevents length extension attacks
  • Two AEAD constructions and when each is preferred

Next: Public-Key Cryptography

← Cryptography Fundamentals MACs and Authenticated Encryption