AES is a block cipher. It encrypts exactly one 16-byte block at a time. That is all it does. Everything interesting about how AES gets used in practice, how it handles messages longer than 16 bytes, whether it provides integrity, whether identical plaintexts produce identical ciphertexts, comes from the mode of operation wrapped around it.
Choosing the wrong mode is not a theoretical concern. It is how systems get broken.
The modes, and what they are for
ECB: the wrong answer
Electronic Codebook mode encrypts each block independently with the same key. Identical plaintext blocks produce identical ciphertext blocks.
Plaintext: [Block A] [Block B] [Block A] [Block C]
Ciphertext: [Enc(A)] [Enc(B)] [Enc(A)] [Enc(C)]
↑ ↑
identical identicalThis leaks structure. The classic demonstration is the ECB penguin: encrypt a bitmap image with ECB and the penguin is still clearly visible in the ciphertext. ECB is not suitable for encrypting anything with structure, which is nearly everything.
Warning
If you see ECB mode exposed in production code, it is almost certainly a bug. The narrow legitimate case is encrypting exactly one independent block with a block-cipher primitive, and even then a purpose-built construction is usually the better interface.
CBC: the legacy workhorse
Cipher Block Chaining XORs each plaintext block with the previous ciphertext block before encryption. An initialization vector (IV) bootstraps the chain for the first block.
P₁ ⊕ IV → AES → C₁
P₂ ⊕ C₁ → AES → C₂
P₃ ⊕ C₂ → AES → C₃CBC solves ECB’s pattern leakage. Identical plaintext blocks produce different ciphertext as long as they appear at different positions or under different IVs. For CBC, the IV must also be unpredictable before the plaintext is chosen, not merely unique. A random IV is the normal answer.
But CBC has problems:
- No built-in authentication. CBC by itself provides confidentiality only. You must add a separate MAC (HMAC-SHA-256, typically) and get the composition right. MAC-then-encrypt, encrypt-then-MAC, and encrypt-and-MAC are not equivalent, and the history of getting this wrong is long.
- Padding oracles. CBC requires padding (PKCS#7, typically) to handle messages that are not exact multiples of the block size. If an attacker can distinguish between a padding error and a MAC error, they can decrypt the entire message one byte at a time. This is not theoretical. Padding-oracle issues hit SSL/TLS repeatedly, including POODLE and Lucky13, and they have broken ASP.NET and countless custom protocols.
- Sequential encryption. Each block depends on the previous ciphertext block. You cannot parallelize encryption. (Decryption can be parallelized, since you already have all the ciphertext blocks.)
CBC served for decades, but it has been superseded by authenticated modes for new designs.
CTR: turning a block cipher into a stream cipher
Counter mode does not encrypt the plaintext directly. It encrypts a sequence of counters and XORs the resulting keystream with the plaintext.
AES(Nonce‖Counter=0) ⊕ P₁ → C₁
AES(Nonce‖Counter=1) ⊕ P₂ → C₂
AES(Nonce‖Counter=2) ⊕ P₃ → C₃This has immediate advantages:
- Fully parallelizable. Every block can be encrypted and decrypted independently.
- No padding needed. You just truncate the final keystream block.
- Random access. You can decrypt block N without decrypting blocks 0 through N-1.
CTR is the foundation beneath GCM. On its own, it provides confidentiality but no integrity. It also has a critical constraint: never reuse a nonce/counter stream with the same key. If two messages use the same keystream, the XOR of the two plaintexts is directly recoverable from the ciphertexts.
GCM: the standard AEAD for network records
Galois/Counter Mode combines CTR-mode encryption with a polynomial MAC (GHASH) computed over the ciphertext and any additional authenticated data (AAD). It produces a ciphertext and an authentication tag in a single pass over the data.
┌─────────────┐
│ Plaintext │
└──────┬──────┘
│
CTR encrypt
│
▼
┌─────────────┐ ┌─────────────┐
│ Ciphertext │────▶│ GHASH │◀──── AAD
└─────────────┘ └──────┬──────┘
│
▼
┌─────────────┐
│ Tag │
└─────────────┘GCM is the dominant mode for network protocols. TLS 1.2 and 1.3 both use AES-128-GCM and AES-256-GCM as primary cipher suites. IPsec, SSH, and QUIC all support or prefer it.
Why GCM fits streaming protocols:
- Single-pass authenticated encryption. Encrypt and authenticate in one operation. No need to compose separate encryption and MAC primitives.
- Parallelizable. The CTR encryption and GHASH computation can both be parallelized and pipelined.
- AAD support. Protocol headers can be authenticated without being encrypted. In TLS, this covers the record header. In IPsec, the SPI and sequence number.
- Hardware acceleration. AES-NI handles the CTR encryption on x86. PCLMULQDQ (carry-less multiplication) accelerates GHASH, with analogous AES and polynomial-multiply instructions on newer ARM platforms. Together, AES-GCM on modern hardware runs at wire speed for 10+ Gbps links.
- Record- and chunk-friendly. Data can be encrypted as it arrives, and protocols can authenticate each record independently. On decryption, however, do not release plaintext to the application until the tag for that record or chunk verifies.
GCM’s constraints:
- Nonce reuse is catastrophic. Reusing a nonce with the same key combines CTR’s two-time-pad failure with practical forgery attacks against GCM’s authentication. In common settings, attackers can recover enough about the authentication subkey to forge tags. After nonce reuse, retire the key.
- 96-bit nonces are the standard case. GCM supports other IV lengths, but 96 bits is the recommended fast path. For random nonces, the birthday bound means you hit a ~50% collision probability after 2⁴⁸ messages under the same key, which sounds like a lot but is a real concern for high-volume services. TLS 1.3 solves this by deriving a per-connection static IV from the traffic secrets and XORing it with the record sequence number to produce each record’s nonce.
- Short tags are dangerous. GCM supports truncated tags, but short tags make forgery practical. Use the full 128-bit tag unless you have a specific, analyzed reason not to. NIST has already announced that the next SP 800-38D revision will remove support for tags shorter than 96 bits.
XTS: the right mode for disk encryption
XEX-based Tweaked-codebook mode with ciphertext Stealing. The name is as awkward as the mode is effective for its specific use case.
Disk encryption has different constraints than network encryption:
- No space for expansion. A 512-byte plaintext sector must produce exactly 512 bytes of ciphertext. There is no room for a nonce, IV, or authentication tag.
- Random access is mandatory. The filesystem reads and writes individual sectors. You cannot require decrypting the entire disk to read one file.
- The same sector gets overwritten. Sector 4096 will be written many times over the life of the disk. Each write uses the same “address” but potentially different data.
XTS handles this by using the sector number and block index within the sector as a tweak, a public, non-secret value that domain-separates disk positions. The same plaintext block written to different positions encrypts differently. The same plaintext written again to the same position under the same key and tweak encrypts the same way, because XTS provides confidentiality but not freshness.
Tweak = AES_K2(SectorNumber)
For each block i in the sector:
T = Tweak × αⁱ (GF(2¹²⁸) multiplication)
C_i = AES_K1(P_i ⊕ T) ⊕ TTwo separate keys are used: K1 for the main encryption and K2 for generating the tweak. The polynomial multiplication by αⁱ (in GF(2¹²⁸)) is cheap, just a shift and conditional XOR.
Insight
XTS is not authenticated. It cannot be authenticated without storing integrity metadata somewhere else, and traditional sector encryption has no room in the sector for a tag. This means an attacker with disk access can cause undetected corruption or replay-style tampering depending on the storage context. XTS protects confidentiality, not integrity. This is a known, accepted limitation of conventional full-disk encryption.
Where XTS is used:
- LUKS/dm-crypt on Linux (the default since the AES-XTS option was added)
- BitLocker on Windows
- FileVault 2 on macOS
- Self-encrypting drives (SEDs) implementing TCG Opal
Why not GCM for disks? Because the authentication tag expands the ciphertext. A 512-byte sector encrypted with GCM produces 512 bytes of ciphertext plus a 16-byte tag. That 528-byte output does not fit in a 512-byte sector. You would need to either shrink the usable sector size (breaking filesystem assumptions) or store tags separately (adding complexity and I/O overhead). XTS avoids this entirely by being a length-preserving encryption scheme.
CCM: GCM’s older, slower cousin
Counter with CBC-MAC. CCM combines CTR-mode encryption with a CBC-MAC for authentication. It predates GCM and is used in protocols designed before GCM was standardized, notably:
- Wi-Fi CCMP profiles common in WPA2 and still required for WPA3 compatibility
- Bluetooth Low Energy
- ZigBee and Thread (IoT mesh protocols)
CCM works but is less efficient than GCM. The CBC-MAC computation is inherently sequential, and unlike GHASH, it cannot be accelerated by carry-less multiplication hardware. For new protocol designs, GCM (or ChaCha20-Poly1305) is preferred.
SIV and GCM-SIV: nonce-misuse resistance
Synthetic IV mode addresses GCM’s nonce-reuse vulnerability. In AES-GCM-SIV, the authentication tag is computed first and then used as the nonce for CTR encryption. If you accidentally reuse a nonce, the worst that happens is that identical plaintexts produce identical ciphertexts (an ECB-like leak for that specific message pair). You do not lose the authentication key.
The tradeoff: SIV modes are two-pass. You must process the entire plaintext to compute the tag before you can begin encryption. This makes them unsuitable for large streaming data but excellent for key wrapping, small messages, and any context where nonce management is difficult.
Tip
If you are designing a system where nonce uniqueness is hard to guarantee (distributed systems, client-side encryption, database field encryption), AES-GCM-SIV is worth serious consideration.
Choosing the right mode
The decision is not about which mode is “best.” It is about which constraints dominate your use case.
| Use case | Mode | Why |
|---|---|---|
| Network streams (TLS, IPsec, SSH) | GCM | Single-pass AEAD, parallelizable, hardware-accelerated, supports AAD |
| Object or file chunks | GCM / GCM-SIV | Chunk-level AEAD; use GCM-SIV when nonce uniqueness is hard |
| Full-disk / sector encryption | XTS | Length-preserving, random access, no space for tags |
| Wi-Fi, BLE, IoT mesh | CCM | Legacy standard, adequate for constrained devices |
| Key wrapping, small messages | SIV / GCM-SIV | Nonce-misuse resistant, two-pass acceptable for small data |
| Database field encryption | GCM-SIV | Nonce management is hard in distributed databases |
| Nothing, ever | ECB | Unless you are encrypting exactly one block and know why |
The meta-lesson
Block cipher modes exist because a raw block cipher answers too narrow a question: “how do I encrypt 16 bytes?” Every real system needs to encrypt variable-length data, and most need integrity guarantees too. The mode is where those requirements meet the mathematics.
The recurring pattern across mode design is the tension between four properties: confidentiality, integrity, parallelism, and ciphertext size. No mode gives you all four without tradeoffs. GCM gets confidentiality, integrity, and parallelism but expands the output with an authentication tag. XTS gets confidentiality and parallelism with no expansion but sacrifices integrity and freshness. CBC gets confidentiality but needs authentication elsewhere, cannot parallelize encryption, and usually expands the plaintext with padding unless a ciphertext-stealing variant is used. Understanding which tradeoff your system can tolerate is the actual engineering decision.