AES is only a block cipher
AES transforms 16 bytes at a time. Encrypting arbitrary data requires a mode of operation (GCM, CBC…) that chains blocks together. Saying "I use AES" is therefore incomplete — the mode and parameters matter just as much.
Mode comparison
| Mode | Authenticated | IV requirement | Recommendation |
|---|---|---|---|
| GCM | Yes (AEAD) | 12 random bytes, never reuse | First choice: secrecy plus tamper detection |
| CBC | No | 16 random bytes | Requires a separate HMAC |
| ECB | No | None | Avoid: identical plaintext blocks leak patterns |
Why the IV must never repeat
GCM derives its keystream from the IV and a counter. Reusing an IV under the same key overlaps two keystreams — an attacker XORs the ciphertexts to recover plaintext — and it also breaks the authentication tag. In practice, generate a fresh random IV per encryption and store it next to the ciphertext (the IV does not need to be secret).
A correct encryption flow
- Generate 12 random bytes as the IV;
- Encrypt with AES-GCM, producing ciphertext and an authentication tag;
- Store or transmit
IV + ciphertext + tagtogether (do not encrypt the IV); - On decryption, always verify the tag and fail hard — never use partial plaintext.
Where does the key come from
A user password is not a key — its entropy is far below 128 bits. Derive a key with a KDF (PBKDF2, scrypt, Argon2) using a strong work factor, or generate a random key with a CSPRNG and solve key storage separately.
Common questions
Is an IV the same as a salt? No: the IV randomises the encryption so the same plaintext encrypts differently; the salt randomises key derivation so the same password yields a different key. Do I still need to hash after encrypting? AEAD modes already provide integrity; only CBC needs an additional MAC.
A storage format that ages well
// Keep the IV and tag next to the ciphertext, self-describing
{
"v": 1, // version, so you can migrate later
"alg": "AES-GCM",
"iv": "base64...", // 12 random bytes
"ct": "base64...", // ciphertext
"tag": "base64..." // auth tag (some libraries append it to ct)
}
Keeping the key safe
- Server keys belong in a KMS or environment variables, never in source control;
- For long-lived data, use envelope encryption: a master key in a KMS wraps per-record data keys;
- On the client, store keys in a password manager or the OS keychain — not localStorage;
- Plan rotation: an algorithm upgrade or a suspected leak must migrate smoothly — which is why the version field exists.
Real-world cases: three easy mistakes
- "The ciphertext differs every time — is it broken?" No. CBC/GCM pick a fresh random IV each time, so ciphertext naturally differs; keep the IV to decrypt.
- "I flipped one byte and decryption errored out": that is authenticated encryption (GCM) working as intended — it detects tampering and refuses, safer than CBC's garbage output.
- "Security review rejected ECB in production": ECB uses no IV, so identical plaintext blocks yield identical ciphertext, leaking image and data structure; avoid it for sensitive data.
FAQ
How do I share the key safely? Send the key and ciphertext separately over different channels (e.g. a secrets platform or offline) — never in the same message. Must the IV be stored? Yes; it is not secret but decryption needs it, so store it alongside the ciphertext. What is the GCM tag? It is the integrity check that verifies the ciphertext was not altered; without it integrity cannot be validated. What if the key leaks? Rotate the key and re-encrypt immediately — there is no "change password" for symmetric encryption.
Try them: symmetric encryption, hashing
The consequences of nonce reuse
- Catastrophic in GCM: reusing a nonce under one key lets an attacker recover the authentication key and forge messages — worse than plaintext leakage;
- Inferable plaintext in CBC: the same IV and a shared plaintext prefix yield identical ciphertext blocks, revealing structure (ECB's famous penguin is the extreme case);
- How to generate them: prefer a random 96-bit nonce with a cap on encryptions per key, or a counter you are certain cannot wrap;
- Rotate keys: when approaching the limit, change the key rather than pushing on.