AES-GCM vs AES-CBC: Which Mode to Use and Why
What block cipher modes do, why GCM has mostly replaced CBC for new code, the IV and authentication rules that matter, and where each mode still earns its place.
AES is a block cipher. Block ciphers, on their own, only encrypt one fixed-size block of data (16 bytes for AES). To encrypt anything bigger or anything that’s not a perfect multiple of 16 bytes, you need a mode of operation layered on top.
The two modes you’ll meet most often in real systems are CBC (Cipher Block Chaining) and GCM (Galois Counter Mode). They’re not interchangeable. Choosing the wrong one is the kind of mistake that doesn’t crash, doesn’t fail tests, and quietly weakens your security until somebody specifically looks. This post walks through what each mode does, the tripwires that bite people, and the cases where each one is the right pick.
What a mode actually does
Imagine you have 80 bytes of data. AES can only encrypt 16 at a time. Naive solution: split into 5 blocks of 16, encrypt each independently, concatenate the results. That’s called ECB (Electronic Codebook), and it’s broken in a famous, visible way: identical input blocks produce identical ciphertext blocks. Patterns in the plaintext leak straight through.
Every other mode exists to solve that problem and the problems that come after it. The two most common solutions:
- CBC chains blocks together. Each block’s plaintext is XORed with the previous block’s ciphertext before encryption. The first block uses an initialization vector (IV) to break the pattern.
- GCM uses a counter to turn AES into a stream cipher (CTR mode), then layers an authentication tag on top to detect tampering.
Both eliminate the ECB pattern problem. They differ in everything else.
If you want to experiment with a real implementation, the AES Encrypt/Decrypt tool runs both modes in your browser and shows you the IV / nonce, the ciphertext, and (for GCM) the authentication tag.
CBC: chained, padded, unauthenticated
The CBC encryption recipe:
- Pick a 16-byte random IV.
- Take plaintext block 1, XOR it with the IV, run it through AES with your key. That’s ciphertext block 1.
- Take plaintext block 2, XOR it with ciphertext block 1, run it through AES. That’s ciphertext block 2.
- Repeat to the end.
- The IV is sent alongside the ciphertext (it’s not secret, it just needs to be unpredictable).
The chaining is what makes CBC secure against the ECB pattern leak: identical plaintext blocks produce different ciphertext blocks because each one is XORed with a different previous ciphertext.
Two consequences fall out of this design that turn into footguns:
Padding. CBC requires plaintext to be a multiple of 16 bytes. If your plaintext is 22 bytes, you have to pad to 32. The standard padding scheme is PKCS#7. The decryption side has to remove the padding before returning the plaintext to the user.
Padding oracle attacks. If your decrypt path tells the attacker (via timing, error messages, or different status codes) whether the padding was valid, an attacker can recover the plaintext one byte at a time without knowing the key. This is the famous “padding oracle” vulnerability that ate Microsoft ASP.NET in 2010 and a long list of other systems before that.
No authentication. CBC encrypts. It does not authenticate. An attacker who can flip bits in the ciphertext can flip corresponding bits in the plaintext. This is sometimes called malleability. To prevent it, you have to wrap CBC ciphertext in a separate MAC (typically HMAC-SHA-256), in an “encrypt-then-MAC” construction. If you forget the MAC, your ciphertext is silently tamperable.
These two problems (padding oracles and missing authentication) are the main reasons CBC has fallen out of favor for new designs.
GCM: counter mode plus an authentication tag
GCM is the poster child of authenticated encryption with associated data (AEAD). It does encryption and authentication in a single, coupled step.
The recipe:
- Pick a 12-byte (96-bit) random or sequential nonce.
- Use AES in counter mode: encrypt counter || nonce, XOR with each plaintext block to produce ciphertext. No padding needed; the counter trick produces a keystream of any length.
- Compute a Galois-field MAC over the ciphertext (and any “associated data” like headers you want authenticated but not encrypted).
- Output: nonce + ciphertext + 16-byte authentication tag.
On decryption, the receiver runs the same MAC computation, compares it to the received tag, and only proceeds if they match. A bit flip anywhere in the ciphertext or the associated data fails the tag check, and the plaintext is rejected.
Three things make GCM nicer than CBC + HMAC for most cases:
- No padding, no padding oracle. Stream-mode encryption produces ciphertext exactly as long as plaintext.
- Authentication is built in. You can’t accidentally forget it.
- Performance. On modern x86 and ARM, AES-GCM has hardware acceleration (AES-NI plus PCLMULQDQ for the Galois math). It’s typically faster than CBC + HMAC-SHA-256 by a healthy margin.
The catch: GCM has one extremely sharp edge.
The nonce-reuse trap
If you encrypt two different plaintexts with the same key and the same nonce, GCM is catastrophically broken. Not “weakened a little” - completely broken. An attacker who sees the two ciphertexts can XOR them to recover the XOR of the two plaintexts, which is enough to read most of both, and can also forge new authenticated messages without knowing the key.
This is much worse than the equivalent mistake in CBC, where IV reuse is bad but recoverable.
In practice, the rule is: never use the same (key, nonce) pair twice. The two strategies for guaranteeing this:
- Random 96-bit nonces with a high-quality RNG. The birthday bound says you have a roughly 50% chance of collision after 2^48 messages. Stay well below that and you’re safe. For most applications, 2^32 messages on the same key is the conservative ceiling.
- Counter-based nonces. Each device or session has a counter that increments per message. Never reset the counter without rotating the key.
If you’re building a system where messages might be retried or replayed by clients (mobile apps with bad networks, for example), counter-based nonces with a strict “rotate key on counter wraparound” policy is more bulletproof than random nonces.
For random keys and IVs, a CSPRNG is mandatory. The Random String Generator uses the browser’s crypto.getRandomValues, which is the same primitive WebCrypto uses internally. If you’re generating keys in code, your language’s crypto.randomBytes (Node), secrets.token_bytes (Python), or secure_random (Ruby) all give you the same guarantee.
When to pick which
A simple decision rule that holds up in 90%+ of cases:
- Use GCM for new applications, network protocols (TLS 1.3 is GCM by default), file encryption with non-repeating keys, and anything where you want one primitive to handle confidentiality and integrity.
- Use CBC + HMAC-SHA-256 if you need to interoperate with an older system that already speaks it, or if you have specific FIPS / compliance reasons that pin you to it.
- Never use CBC alone for anything that crosses a trust boundary. The malleability bites eventually.
- Never use ECB for anything other than single-block encryption (and even then, prefer a wrapper).
The relative footgun count is part of why GCM tends to win. CBC has padding oracles, IV-prediction risks (CBC requires unpredictable IVs, not just unique), and bolted-on MACs that someone forgot to add. GCM has exactly one major footgun (nonce reuse), and it’s loud.
Key sizes and rotation
AES is specified with 128, 192, and 256-bit keys. AES-128 is fine for any threat model that doesn’t include nation-state quantum-computer adversaries (and even those are decades away from breaking 128-bit symmetric encryption). AES-256 is mandated by some compliance regimes (FIPS, certain government data classifications) and is the safer default if you’re not sure.
Both CBC and GCM take a key of any AES-supported length without changes. There’s no security reason to prefer 192 over 128 or 256 for either mode.
Rotate keys when:
- Your nonce counter is approaching the safe ceiling.
- A device is compromised or a credential leaks.
- A scheduled rotation interval (typical: 1 year for long-lived keys, hours for ephemeral session keys) elapses.
For verifying that two arbitrary inputs hash to the same fingerprint (a different but related primitive), the Hash Generator covers SHA-256 and friends. Hashes are what you compare; ciphertext is what you decrypt.
A short note on TLS and other protocols
You don’t usually pick AES modes by hand. Higher-level protocols pick for you:
- TLS 1.3: AES-GCM and ChaCha20-Poly1305 only. CBC is gone.
- TLS 1.2: Both, but configurations using AES-CBC are deprecated and slowly being disabled.
- SSH: GCM is the default for AES-based ciphers in modern OpenSSH.
- PGP/GPG: Mostly OCB or GCM in recent versions; older messages used CFB.
- AWS KMS, Azure Key Vault, GCP KMS: All default to GCM internally.
The fact that every major protocol is migrating to authenticated modes (GCM, ChaCha20-Poly1305, OCB) is itself the strongest argument for picking GCM in your own code.
The bottom line
CBC is a legacy mode that requires careful padding handling and a separate MAC to be safe. GCM is the modern default: faster, authenticated, and free of padding oracles, with the one critical caveat that you must never reuse a nonce. For new projects, pick GCM and put nonce management on a checklist. For old code that already speaks CBC + HMAC correctly, leave it alone until a planned key rotation gives you a chance to migrate cleanly.
If you want to try both modes on real input, the AES Encrypt/Decrypt tool exposes the full IV / nonce, ciphertext, and tag, so you can see the structural difference between a CBC blob and a GCM blob without writing a line of code.
Tools mentioned in this article
- AES-256 Encrypt / Decrypt Online - Free, In-Browser — Encrypt and decrypt text with AES-128, AES-192, or AES-256 in GCM, CBC, or CTR mode. PBKDF2 key derivation, entirely in your browser.
- Hash Generator — Generate SHA-1, SHA-256, SHA-384 and SHA-512 hashes from text.
- Random String Generator — Generate random strings with configurable length and character set.