Authenticated Encryption and Integrity: AES-GCM, ChaCha20-Poly1305, and AEAD
Why encryption alone is not enough — and how AEAD modes solve both confidentiality and integrity in a single operation.
Part of the complete guide to encryption, hashing, and encoding.
Only authorized parties can read the data. Provided by encryption — the ciphertext is unreadable without the key.
Examples: AES-CBC, AES-CTR
Data has not been modified in transit or at rest. Detected by authentication tags or MACs — tampering is caught on decryption.
Examples: GCM tag, Poly1305 tag
Authenticated Encryption with Associated Data. A cipher mode that provides confidentiality, integrity, and authentication in one pass.
Examples: AES-GCM, ChaCha20-Poly1305
What Is Authenticated Encryption?
Encryption hides data. But it does not guarantee that data arrives unchanged. Classic cipher modes like AES-CBC and AES-CTR provide confidentiality only — an attacker who intercepts ciphertext can modify bits, flip bytes, or substitute blocks without knowing the key, and the decryption will produce garbage silently.
Authenticated Encryption (AE) solves this by combining a cipher with a message authentication code (MAC). The result: every decryption either succeeds and produces verified plaintext, or fails loudly with an authentication error. There is no middle state where modified ciphertext decrypts to corrupted data.
AEAD extends this with Associated Data — metadata (like headers, IDs, or context) that is authenticated but not encrypted. If any associated data is tampered with, the decryption fails. This is used to bind ciphertext to its context: a packet for user A cannot be replayed to user B even if the key is the same.
The core rule
If you encrypt data, always use an authenticated mode. Never use AES-CBC or AES-CTR alone in new systems. Use AES-GCM or ChaCha20-Poly1305 instead.
How AES-GCM Works
AES-GCM (Galois/Counter Mode) is the most widely deployed AEAD construction. It combines AES-CTR encryption with GHASH, a Galois Field authentication function, to produce a 128-bit authentication tag.
During encryption, AES-CTR generates a keystream from the key and a 96-bit nonce, which is XOR-ed with the plaintext. Simultaneously, GHASH processes both the ciphertext and any associated data to produce the tag. On decryption, the tag is verified first — if it does not match, the operation aborts and no plaintext is returned.
Three inputs, three outputs
Inputs
- •Key — 128, 192, or 256 bits. AES-256-GCM is the recommended choice.
- •Nonce (IV) — 96 bits, must be unique per key-message pair. Never reuse a nonce with the same key.
- •Associated Data — optional metadata to authenticate without encrypting.
Outputs
- •Ciphertext — same length as plaintext (stream cipher mode).
- •Authentication tag — 128 bits (16 bytes). Appended to or stored alongside the ciphertext.
Nonce reuse is catastrophic
Reusing a nonce with the same key in GCM allows an attacker to recover the plaintext and forge authentication tags. Always generate a fresh random nonce (96-bit / 12 bytes) for every encryption operation. Store the nonce alongside the ciphertext — it does not need to be secret.
AEAD Algorithms Compared
| Algorithm | Key Size | Nonce Size | Notes |
|---|---|---|---|
| AES-256-GCM | 256 bits | 96 bits (12 bytes) | Industry standard. Hardware-accelerated on most modern CPUs. Best default choice. |
| ChaCha20-Poly1305 | 256 bits | 96 bits (12 bytes) | Faster than AES on devices without hardware acceleration (older mobile, IoT). Immune to timing attacks by design. Used in TLS 1.3. |
| AES-128-GCM | 128 bits | 96 bits (12 bytes) | Acceptable for most use cases. Slightly faster than AES-256-GCM. Some compliance frameworks require 256-bit. |
| AES-CCM | 128 or 256 bits | 7–13 bytes | AEAD mode used in constrained environments (Bluetooth, 802.11). Requires message length in advance. Less common outside embedded systems. |
| XChaCha20-Poly1305 | 256 bits | 192 bits (24 bytes) | Extended nonce variant. Nonces can be randomly generated without collision risk even for large volumes of messages. Used in libsodium. |
AES-GCM vs AES-CBC: What Changed
| Aspect | AES-CBC | AES-GCM |
|---|---|---|
| Confidentiality | Yes | Yes |
| Integrity / Tamper detection | No | Yes (auth tag) |
| Padding required | Yes (PKCS#7) | No |
| Padding oracle attacks | Vulnerable if not careful | Not applicable |
| Ciphertext length | Padded to block size + IV | Same as plaintext + nonce + 16-byte tag |
| Parallelisable encryption | No (sequential) | Yes |
| Associated data support | No | Yes (AEAD) |
AES-CBC is not inherently broken, but it requires careful padding, a separate MAC, and constant-time comparison to avoid known attacks (BEAST, POODLE, padding oracle). AES-GCM avoids these pitfalls by design. For the broader question of when to use AES vs RSA and how they fit together in hybrid systems, see the AES vs RSA comparison.
When You Cannot Use AEAD: Encrypt-then-MAC
Some legacy systems or constrained environments do not support AEAD modes. In those cases, use Encrypt-then-MAC: encrypt the plaintext first, then compute an HMAC over the ciphertext (and IV). Verify the MAC before decrypting.
This order matters. Performing MAC-then-encrypt (the wrong order) can leak information about the plaintext and is vulnerable to padding oracle attacks. Always MAC the ciphertext, not the plaintext.
For a full treatment of HMAC construction, key length requirements, and use cases, see the Hashing and HMAC guide.
Encrypt-then-MAC
Encrypt plaintext → compute HMAC over ciphertext → verify MAC before decrypt. Correct and recommended when AEAD is unavailable.
MAC-then-Encrypt
Compute MAC over plaintext → encrypt both. Vulnerable to padding oracle attacks because decryption happens before verification.
Encrypt-and-MAC
Compute MAC over plaintext and encrypt separately. MAC can leak plaintext information. Used in SSH but not recommended for new systems.
Which Mode Should I Use?
| Use Case | Recommended |
|---|---|
| General-purpose encryption (server-side) | AES-256-GCM |
| Mobile / IoT devices without AES hardware | ChaCha20-Poly1305 |
| High-volume messages (risk of nonce collision) | XChaCha20-Poly1305 (24-byte nonce) |
| Constrained/embedded systems (Bluetooth, 802.11) | AES-CCM |
| Legacy system without AEAD support | AES-CBC + HMAC (Encrypt-then-MAC) |
| TLS 1.3 cipher suite | AES-256-GCM or ChaCha20-Poly1305 (both supported natively) |
Common Real-World Patterns
Sensitive fields (PII, credit card numbers, health data) are encrypted before storage. Use AES-256-GCM with a randomly generated 12-byte nonce per record. Store the nonce alongside the ciphertext and authentication tag in the same column or as separate fields.
Use the record's primary key or user ID as associated data to prevent ciphertexts from being moved between rows without detection.
TLS 1.3 removed all non-AEAD cipher suites. Every TLS 1.3 connection uses either AES-256-GCM-SHA384, AES-128-GCM-SHA256, or ChaCha20-Poly1305-SHA256. The associated data in TLS includes the record header, binding the ciphertext to its position in the stream.
If your application uses HTTPS, you already benefit from authenticated encryption for data in transit. See What Is TLS? for a full breakdown of the handshake and certificate trust chain.
When encrypting files at rest, stream the plaintext through AES-GCM and prepend the nonce to the output. For large files, use streaming GCM or split the file into chunks, authenticating each chunk independently to enable random access and detect partial corruption.
Libraries like libsodium provide high-level streaming encryption APIs that handle nonce management and chunk authentication automatically.
Stateless tokens (like encrypted JWTs or session cookies) use AEAD so the server can verify the token has not been tampered with before trusting its contents. The header or token type can be passed as associated data to prevent token confusion attacks.
PASETO (Platform-Agnostic Security Tokens) uses XChaCha20-Poly1305 by default and is considered a safer alternative to JWT for encrypted tokens.
Common Mistakes to Avoid
Reusing a nonce with the same key
In AES-GCM, a nonce reuse with the same key exposes the keystream and allows an attacker to forge authentication tags. Generate a fresh random nonce (using a CSPRNG) for every encryption call.
Ignoring the authentication tag
Some API bindings let you skip tag verification or truncate the tag. Never truncate below 128 bits (16 bytes) and always check the result of decryption — if it fails, treat the data as compromised.
Deriving the encryption key directly from a password
Never use a raw password as an AES key. Use a key derivation function (Argon2, scrypt, PBKDF2) with a random salt to derive a key from a password. This adds resistance to brute-force attacks.
Using AES-ECB mode for any purpose
AES-ECB encrypts each block independently, causing identical plaintext blocks to produce identical ciphertext blocks. This leaks structure and patterns. Never use ECB. Use GCM or CCM for encryption.
Implementing AEAD from scratch
Do not implement GCM or Poly1305 yourself. Use a well-audited library such as the Web Crypto API (browser/Node), OpenSSL, libsodium, or the standard library of your language. Subtle bugs in crypto primitives can silently undermine all security guarantees.
Storing the key with the ciphertext
The key must be stored separately from the ciphertext, ideally in a dedicated secret management service (AWS KMS, HashiCorp Vault, GCP Secret Manager). The nonce and associated data can be stored alongside the ciphertext — neither needs to be secret.