symmetric · level 4

Authenticated Encryption

AEAD, tags, AAD, and picking GCM vs ChaCha20-Poly1305.

190 XP

Authenticated Encryption

Encryption alone hides data. It doesn't tell you whether someone changed the data in transit. Every early-2000s protocol that "just used AES" and then discovered a bit-flipping or padding-oracle attack had the same bug: no authentication.

Modern practice replaces "encryption + hope" with AEAD: Authenticated Encryption with Associated Data.

Analogy

Look at a pill bottle: the cap screws on, hiding the contents, but there's also a tamper-evident foil seal across the neck. If anyone slips the cap off and swaps the pills, the foil is torn, and a pharmacist knows never to dispense the contents. AEAD is exactly that foil seal bolted onto encryption — the ciphertext hides the message, and a short authentication tag tears visibly if anyone edits a single bit in transit. The prescription label on the outside (AAD) travels unhidden so the pharmacy can still route the bottle, but the seal covers the label too: swap labels between bottles and the seal breaks.

What AEAD gives you

An AEAD mode takes:

  • a key (symmetric, e.g. 256 bits)
  • a nonce (unique per message, not secret)
  • a plaintext
  • optional AAD (Additional Authenticated Data — authenticated but not encrypted)

and produces:

  • a ciphertext of the same length as the plaintext
  • a short authentication tag (typically 16 bytes)

On decrypt, you pass everything back. The tag is verified before any plaintext is returned. If verification fails — tampered ciphertext, wrong nonce, wrong AAD — the call fails with no output.

Encrypt(K, nonce, AAD, plaintext)  →  (ciphertext, tag)
Decrypt(K, nonce, AAD, ciphertext, tag)  →  plaintext | ERROR

No more "decrypt and see what happens" — AEAD is atomic.

AAD — what it's for

AAD is data that travels in the clear but is covered by the tag. You use it for headers, IDs, version bytes, or routing info that needs to be readable by intermediate systems but must not be swapped between messages.

A canonical example: encrypt a file, bind the AAD to path=/etc/passwd. Now an attacker who swaps this ciphertext into a different file's slot will fail verification — the AAD won't match.

AES-GCM

  • NIST-approved (SP 800-38D).
  • 96-bit nonce is standard. Never reuse (key, nonce).
  • Fast on CPUs with AES-NI (all modern x86, Apple Silicon, recent ARM).
  • Used by TLS 1.2 & 1.3, HTTPS, JWE, SSH, SRTP.

ChaCha20-Poly1305

  • IETF standard (RFC 8439).
  • 256-bit key, 96-bit nonce, 16-byte Poly1305 tag.
  • Doesn't need AES hardware — often faster than AES-GCM in pure software.
  • TLS 1.3 supports it as a peer to AES-GCM; it's the preferred AEAD on older mobile hardware.

When to pick which

Situation Pick
Modern server with AES-NI AES-GCM
Mobile / embedded without AES-NI ChaCha20-Poly1305
You don't know where it'll run Whichever your library defaults to — both are fine. TLS 1.3 negotiates automatically.

Don't roll your own "encrypt-then-MAC" unless you really know what you're doing. AEAD already is encrypt-then-MAC, done carefully, with a tested design and hardware-accelerated primitives. libsodium's secretbox (XSalsa20-Poly1305) and aead_chacha20poly1305_ietf are particularly hard to misuse.

The cardinal sin: nonce reuse

Both GCM and ChaCha20-Poly1305 derive a keystream from (key, nonce). Encrypt two different messages with the same keystream and their XOR cancels the keystream entirely — the attacker can recover plaintext structure and, in GCM's case, also recover the authentication key, meaning they can forge further tagged messages.

Use a random 12-byte nonce (~2⁴⁸ messages before collision birthday-bounds bite), or use a counter you guarantee is monotonic. If neither fits, look at AES-GCM-SIV (RFC 8452), a misuse-resistant variant.

Takeaways

  1. AEAD = encryption + tag over ciphertext + AAD.
  2. Verify before returning plaintext. Tag failure → no output.
  3. Never reuse a nonce under the same key.
  4. Default to AES-GCM; use ChaCha20-Poly1305 where AES is slow.