By Freddy ·
Decrypting something successfully feels like proof that nothing went wrong with it. It is not. AES-256-CBC will happily decrypt ciphertext that an attacker has quietly modified in transit, hand you back plaintext that looks structurally valid, and give you no signal whatsoever that anything was tampered with. Confidentiality and integrity are separate properties, and a cipher mode can give you the first without the second. The gap between them is exactly where a lot of production crypto code quietly breaks, usually without anyone noticing until someone goes looking.
Encryption is a confidentiality guarantee: given the ciphertext and not the key, an attacker cannot recover the plaintext. That is the whole promise. Nothing in that definition says anything about whether the ciphertext can be altered, and for a mode like AES-CBC, it very much can be, without the key, without detection, and in ways that produce a controlled, predictable change to the decrypted output. A cipher mode that only provides confidentiality is called unauthenticated encryption, and CBC, along with the simpler CTR and OFB modes, all fall into that category. None of them were ever designed to detect tampering, because that was never the job they were built to do.
CBC decrypts each ciphertext block, then XORs the result with the previous ciphertext block to produce the plaintext. That XOR step is the vulnerability. Flip a bit in ciphertext block N and the corresponding bit flips predictably in plaintext block N + 1 after decryption, while block N itself just turns into garbage. An attacker who knows, or can guess, the rough structure of the plaintext, such as a JSON field, a serialized session object, or a fixed-format record, can flip specific bits in a specific ciphertext block and change specific bytes in the next block on decryption, all without ever holding the key. This is not a contrived scenario. It is the standard textbook attack against IV-and-CBC-only encrypted cookies and session tokens, and it has shown up repeatedly in real vulnerability disclosures against systems that encrypted a payload and trusted the ciphertext to arrive unmodified.
AES-GCM belongs to a category called AEAD, authenticated encryption with associated data, and the difference is structural, not incidental. Alongside the ciphertext, GCM produces an authentication tag computed over the entire ciphertext using the key. On decryption, the same computation runs again, and if the recomputed tag does not match the one that arrived with the ciphertext, decryption fails outright: no plaintext, no partial result, just a rejected operation. Flip a single bit anywhere in a GCM ciphertext and the tag check fails, because the tag is a function of every byte, not a separate add-on checked loosely at the end. This is why the Web Crypto API throws a bare OperationError on any failed GCM decrypt rather than returning corrupted data: the authentication check is not optional, and a failure there is not a special case you have to remember to handle, it is the only way decryption can fail.
Authentication in GCM depends entirely on the nonce never repeating under the same key, which is a separate requirement from picking GCM in the first place. Reuse a nonce and GCM's integrity guarantee collapses along with its confidentiality guarantee, to the point that an attacker who observes two ciphertexts encrypted under the same key and nonce can recover the authentication key itself and forge valid tags for ciphertext they choose. Choosing GCM is necessary but not sufficient. A correct implementation still needs a fresh, unpredictable nonce for every encryption call, generated by the same random source you would trust for a key.
Some systems still use CBC for compatibility reasons, and that is not automatically wrong as long as it is paired with a separate integrity check applied correctly. The pattern that works is encrypt-then-MAC: encrypt the plaintext with CBC first, then compute an HMAC over the resulting ciphertext, and send both. On the receiving end, verify the HMAC before attempting to decrypt anything, and reject the message outright if it does not match. The order matters more than it looks like it should: MAC-then-encrypt and encrypt-and-MAC (computing the MAC over the plaintext instead of the ciphertext) both have documented weaknesses, including cases where an attacker can manipulate ciphertext without the MAC ever being able to catch it, since the MAC was never actually checking the bytes that got tampered with. Encrypt-then-MAC, with the MAC over the ciphertext and verified before decryption ever runs, is the one combination with no known structural gap, and it is exactly what GCM already does internally, which is the real reason to prefer GCM over rolling this by hand.
“Encrypted” and “safe from tampering” are two different claims, and code that only checks the first one is exposed to the second. If you are choosing a mode for new work, default to AES-256-GCM and let the authentication tag do the integrity checking for you, rather than encrypting with CBC and hoping nothing downstream depends on the plaintext being exactly what was originally encrypted. If you are auditing existing code and it uses CBC, CTR, or any other unauthenticated mode, the question to ask is not whether the data is confidential, it probably is, it is whether anything checks that the ciphertext arrived unmodified before that plaintext gets trusted anywhere downstream.