Symmetric Encryption
Authenticated Encryption
AEAD: encryption that also detects tampering, and why it is the default choice.
Authenticated Encryption with Associated Data (AEAD) is the modern default for symmetric encryption because it solves two problems at once: it hides the data and it detects any tampering. Encryption without authentication seems safe but is not; an attacker who cannot read your ciphertext can often still modify it in meaningful, controlled ways. AEAD closes that gap, and every new system should use it.
Why encryption without authentication is malleable
Confidentiality means an attacker cannot read the plaintext. It says nothing about whether the attacker can change it. With stream-like modes (CTR, and therefore the keystream half of GCM), ciphertext is the plaintext XORed with a keystream. An attacker who flips a bit in the ciphertext flips the exact same bit in the decrypted plaintext, without ever knowing the key.
Consider a message "transfer $10". If the attacker knows or guesses the layout, they can XOR the ciphertext bytes to change it to "transfer $90". The recipient decrypts attacker-chosen plaintext and has no way to know. CBC has related malleability, and its padding checks historically leaked plaintext through padding-oracle attacks. This is why “encrypted” is not “safe” unless integrity is also guaranteed. AEAD guarantees it: a modified ciphertext fails verification and decryption refuses to return anything.
AES-GCM: nonces, tags, and the reuse catastrophe
Galois/Counter Mode (GCM) is the most common AEAD. Under the hood it runs AES in CTR mode for confidentiality and computes a Galois-field authentication tag (GHASH) over the ciphertext and associated data for integrity. On decryption, the tag is recomputed and compared; if it does not match, decryption fails and no plaintext is released.
GCM takes three inputs beyond the plaintext:
- Key: the AES key, typically 256 bits.
- Nonce: a per-message number, 96 bits (12 bytes) is standard. It must be unique per key.
- Tag: the 128-bit authentication value produced on encryption and checked on decryption.
The nonce reuse catastrophe is the one rule you cannot break. If you encrypt two different messages with the same key and the same nonce, GCM’s security collapses in two ways at once. First, the CTR keystream repeats, so XORing the two ciphertexts reveals the XOR of the plaintexts. Second, and worse, the reuse leaks the GHASH authentication subkey, which lets an attacker forge valid tags for arbitrary messages. A single nonce reuse can therefore break both confidentiality and authenticity for that key. This is why GCM nonces must come from a strict counter or a large random value with a per-key message-count budget.
A practical demonstration of why the CLI is the wrong tool here, and why libraries manage the tag for you:
mkdir -p ~/crypto-lab && cd ~/crypto-lab
echo "wire 100 to alice" > tx.txt
KEY=$(openssl rand -hex 32)
NONCE=$(openssl rand -hex 12) # 12 bytes = 96-bit nonce
openssl enc -aes-256-gcm -K $KEY -iv $NONCE -in tx.txt -out tx.gcm 2>/dev/null
echo "encrypted $(wc -c < tx.txt) plaintext bytes into $(wc -c < tx.gcm) ciphertext bytes"
Expected output:
encrypted 18 plaintext bytes into 18 ciphertext bytes
Notice the CLI does not surface or store the 16-byte tag in a usable way, so it cannot actually verify integrity on decrypt. That is the point: real AEAD needs the tag handled correctly, so use a library binding (Python cryptography, Go crypto/cipher, libsodium) that returns and checks the tag automatically.
Associated data and what it is for
The “AD” in AEAD is associated data: information that is authenticated but not encrypted. It is bound to the ciphertext by the tag, so if anyone changes it, decryption fails, yet it stays readable in the clear. This is exactly what you want for headers and routing metadata.
Typical associated data includes:
- A message header or protocol version that routers need to read but must not be swapped.
- A record sequence number, so an attacker cannot reorder or replay records (TLS binds the sequence number this way).
- A file format identifier or key ID, so a ciphertext encrypted for one context cannot be replayed into another.
The mental model: encrypt what must stay secret, authenticate everything (including the associated data) so nothing can be tampered with undetected.
AES-GCM versus ChaCha20-Poly1305
Both are AEADs with the same interface (key, nonce, plaintext, associated data, tag). The choice comes down to hardware.
| Property | AES-256-GCM | ChaCha20-Poly1305 |
|---|---|---|
| Speed with AES-NI hardware | Excellent | Good |
| Speed without AES hardware | Slow, timing-risky | Excellent, constant-time |
| Nonce size (standard) | 96 bits | 96 bits |
| Nonce reuse consequence | Catastrophic | Catastrophic |
| Common uses | TLS, disk, cloud KMS | TLS, WireGuard, mobile |
See ChaCha20-Poly1305 for the alternative and Modes Of Operation for how GCM’s CTR core relates to the non-authenticated modes.
Practical Guidance
- Make an AEAD your default for all new symmetric encryption. AES-GCM on AES-NI hardware, ChaCha20-Poly1305 elsewhere.
- Never reuse a nonce under the same key. Use a strict counter, or a random nonce with a strict per-key message budget, and rotate keys before you approach it.
- Always verify the tag on decryption and treat a tag mismatch as a hard failure. Never process plaintext from a ciphertext that failed authentication.
- Put headers, versions, sequence numbers, and key IDs into associated data so they cannot be tampered with or replayed.
- Use a library that returns and checks the tag for you. The
openssl encCLI is a teaching aid, not a production AEAD interface. - Follow rule zero: never assemble encrypt-then-MAC by hand when a vetted AEAD already does it correctly.