RouteHardenHire us
Back to Cryptography Foundations
Cryptography Foundations · Part 1 of 8·Anonymity Engineering··18 min read·intermediate

Symmetric encryption, block ciphers, and AES

AES from first principles: what a block cipher actually is, why ECB is the canonical embarrassment, modes of operation, and why AES alone is not an encryption scheme.

If you've used HTTPS, SSH, full-disk encryption, signal-style messaging, or really any digital communication in the last two decades, AES was almost certainly involved. It's the dominant symmetric cipher of our era — fast, well-studied, hardware-accelerated on every CPU shipped after about 2010, and standardized across enough protocols that it's the boring default. The interesting parts aren't AES itself, which has been substantively unchanged since 1998. The interesting parts are the layer of bookkeeping around AES that turns a 128-bit block primitive into a real encryption scheme — modes of operation, padding, IVs, nonces — and the surprising number of ways every component can be misused. This module is the foundational pass: enough to read a TLS or WireGuard handshake and know which AES-related decisions matter.

Prerequisites

  • Module 1.11 — TLS 1.3 handshake byte by byte. The cipher suite list and key schedule discussion both ride on top of "AES is a thing the protocol uses." This module fills in what AES actually is.

Learning objectives

By the end of this module you should be able to:

  1. State precisely what security goal a symmetric cipher is trying to achieve, and articulate the gap between "looks scrambled" and "is secure."
  2. Distinguish a block cipher from a mode of operation, and explain why "AES" by itself is not a message-encryption protocol.
  3. Describe AES at the round-function level — SubBytes, ShiftRows, MixColumns, AddRoundKey — without drowning in finite-field arithmetic but without faking it either.
  4. Explain why ECB is structurally unsafe for multi-block data even when AES itself remains cryptographically sound.
  5. Recognize the operational decisions that determine whether a real AES deployment is safe: mode choice, IV/nonce handling, integrity, side-channel resistance.

What symmetric encryption buys

The job of a symmetric cipher is to provide confidentiality under a shared secret key. Both parties hold the same key. The sender encrypts; the receiver decrypts; everyone else who doesn't have the key sees only ciphertext that should be indistinguishable from random.

The phrase to be careful with is "indistinguishable from random." Most "encryption" failures are not breaks of the underlying math — AES has held up cryptographically for two and a half decades. They're failures to actually produce indistinguishable-from-random ciphertext, usually because the surrounding mode of operation, IV handling, or implementation introduces structure an attacker can exploit.

Three things confidentiality does not imply, that beginners often assume:

  • Integrity. A symmetric cipher can hide what the message says without preventing an attacker from modifying ciphertext bytes. Modifications may produce nonsense plaintext on decryption, but the ciphertext-modification itself is undetectable. AEAD constructions (covered in Module 2.2) bolt integrity onto encryption to fix this.
  • Authentication. Confidentiality says "no one without the key can read this." It says nothing about who sent it. Anyone who has the key can produce ciphertext that decrypts. If two parties share a key, decryption alone doesn't tell you which party sent the message.
  • Replay protection. Even with confidentiality and integrity, an attacker can capture a valid encrypted message and replay it later. Higher-level constructions (sequence numbers, nonces with strict-once semantics) handle this.

The clean mental model: symmetric encryption is one tool in a stack. It hides content. Other tools (MACs, signatures, sequence numbers) handle integrity, authentication, replay. Real protocols compose all of them — TLS 1.3, WireGuard, Signal — into something that does all four jobs.

Security notions without theorem theater

The standard security goal for a block cipher is indistinguishability under chosen-plaintext attack (IND-CPA). The thought experiment:

  1. The attacker chooses two plaintexts of equal length: P_0 and P_1.
  2. A coin flip selects which one gets encrypted under the secret key, producing C.
  3. The attacker, given C, has to guess which plaintext was encrypted.

If the cipher is good, the attacker can't guess better than 50%. Crucially, this has to hold even if the attacker can request encryptions of other chosen plaintexts under the same key (the "chosen plaintext" part). A real cipher gets used many times with the same key; the security definition has to assume the attacker has seen lots of related ciphertexts before the challenge.

A block cipher should also approximate a pseudorandom permutation (PRP): for each fixed key, the cipher defines a reversible permutation over the block space (16 bytes for AES) that should look uniformly random to anyone without the key. The reversibility matters because decryption has to work; the randomness matters for confidentiality.

These definitions sound abstract but they translate directly into engineering: the way a cipher is used must not give an attacker any information about plaintexts beyond what individual ciphertexts technically reveal. Mode-of-operation failures and IV reuse are concrete violations of these definitions, and they're how real systems break.

Block ciphers as keyed permutations

A block cipher takes:

  • A fixed-size block of plaintext (16 bytes for AES, regardless of variant).
  • A key (128, 192, or 256 bits for AES).
  • And produces a fixed-size block of ciphertext (also 16 bytes).

Decryption with the same key reverses the operation. Each (key, plaintext) pair maps to exactly one ciphertext, and each (key, ciphertext) maps to exactly one plaintext.

The crucial restriction: a block cipher only encrypts one block at a time, deterministically. If you call AES on the same plaintext block twice with the same key, you get the same ciphertext both times. This determinism is unavoidable at the block-cipher level — it's literally a function — and is exactly what makes ECB mode unsafe (we'll get there).

Real messages are not 16 bytes. They're hundreds, thousands, millions of bytes. Bridging the gap between "block cipher operates on 16-byte blocks" and "I want to encrypt a 1 MB file" is what modes of operation do. We need them, and the mode choice is one of the most consequential security decisions in any cryptographic protocol.

AES design goals and why Rijndael won

In 1997 NIST (the U.S. National Institute of Standards and Technology) called for proposals to replace the aging DES (which had a 56-bit key, broken in practice by the late 1990s, and a 64-bit block size, increasingly inadequate). The competition received fifteen submissions, narrowed them down to five finalists, and selected Rijndael in 2000. Rijndael was renamed the Advanced Encryption Standard — AES — and published as FIPS 197 in 2001.

Why Rijndael won:

  • Software speed across architectures. Performant on 8-bit microcontrollers, 32-bit desktops, and 64-bit servers without algorithm-level changes.
  • Hardware-friendly. The round function is structurally simple enough to implement efficiently in dedicated silicon. CPU vendors later added AES instructions (Intel AES-NI in 2010, ARM Cryptography Extensions, etc.) that turn AES into single-cycle operations.
  • Security margin. The selected version (10/12/14 rounds depending on key size) had a comfortable margin over the best known attacks at submission time.
  • Clean structure. Substitution-permutation network rather than the older Feistel structure; easier to analyze and reason about formally.

Twenty-five years later, AES has held up remarkably well. The best published attacks — biclique cryptanalysis, related-key attacks under specific conditions — recover keys at theoretical work factors of around 2^126 for AES-128 (versus the trivial 2^128 brute force). That's a "we technically beat brute force" result, not a practical break. AES-128 in any well-implemented mode remains beyond the reach of any computational adversary today, and probably for decades.

AES round structure

AES operates on a 4×4 matrix of bytes called the state. Each round transforms the state through four operations:

SubBytes. Each byte is replaced via a fixed lookup table called the S-box. The S-box was constructed using algebraic operations over the finite field GF(2^8) to maximize "non-linearity" — ensure that small changes in the input cause widespread, hard-to-predict changes in the output. This is the only non-linear operation in AES; everything else is linear over the byte field.

ShiftRows. Each row of the state is rotated left by a different offset (row 0 by 0, row 1 by 1, row 2 by 2, row 3 by 3 bytes). This mixes columns into rows so that subsequent operations propagate information across the entire state.

MixColumns. Each column is multiplied by a fixed matrix in GF(2^8). This combines the bytes within a column so each output byte depends on every input byte in that column. Combined with ShiftRows, this gives full diffusion: after a few rounds, every output byte depends on every input byte.

AddRoundKey. The state is XOR'd with a 128-bit subkey derived from the master key. This is the only operation that involves the key, and it's what makes AES keyed rather than just a fixed scrambling.

Total: 10 rounds for AES-128, 12 for AES-192, 14 for AES-256. The last round skips MixColumns (a structural detail; trying to "fix" it makes the cipher weaker rather than stronger).

The hand-wavable summary: SubBytes makes the cipher non-linear, ShiftRows and MixColumns spread information across the block, and AddRoundKey ensures every operation depends on the key. After enough rounds, the relationship between plaintext, key, and ciphertext is intractable — at least, that's the design intent, and it's held up under serious cryptanalysis.

A sometimes-asked question: why exactly 10/12/14 rounds? Each additional round increases the security margin against future attacks. The chosen counts give plenty of headroom over the best known attacks at AES's introduction; lower round counts have been broken in academic settings.

Key schedule and round count

The master key (128, 192, or 256 bits) is expanded into a sequence of 128-bit round keys via the key schedule. AES-128 uses 11 round keys (one per round plus the initial AddRoundKey before round 1); AES-192 uses 13; AES-256 uses 15.

The schedule is a small, deterministic algorithm. Each round key is derived from the previous one through a mix of byte rotation, S-box substitution on a single column, and XOR with a round constant. The schedule is reversible (given the last round key you can compute the master), which matters for some implementations and is irrelevant for security analysis.

Why does AES come in three key sizes?

  • AES-128 — 10 rounds, 128-bit key. Fast. The default choice in most performance-sensitive contexts (VPNs, disk encryption when CPU matters, embedded systems).
  • AES-192 — 12 rounds. Largely a NIST political artifact; rare in practice.
  • AES-256 — 14 rounds, 256-bit key. Slower (40% more rounds means 40% more work). Common when paranoia is high or when post-quantum margin is desired.

A common misconception: AES-256 is "obviously better" than AES-128 for every use. The relationship is not that simple. AES-128 has a smaller security margin against future attacks but is faster. AES-256's key schedule is also weaker against some related-key attacks than AES-128's, though those attacks are not relevant in any modern protocol that uses fresh keys per session. For most applications in 2026 — TLS 1.3, WireGuard, full-disk encryption — AES-128 is the right default; AES-256 is the right choice when computation cost is irrelevant or when post-quantum margin matters (the security margin against Grover's algorithm halves the effective key length, putting AES-128 at "128-bit equivalent" against a future quantum attacker — still strong, but AES-256 has more cushion).

Modes of operation

A block cipher encrypts 16 bytes at a time. Real messages are larger. Modes of operation are the rules for applying the block cipher to a multi-block message safely.

The classic modes:

ECB (Electronic Codebook). The simplest mode: split the plaintext into 16-byte blocks, encrypt each block independently with the same key, concatenate the ciphertext blocks. Do not use ECB. We'll cover why in a dedicated section, but the short version: identical plaintext blocks produce identical ciphertext blocks, leaking structural information.

CBC (Cipher Block Chaining). Each plaintext block is XOR'd with the previous ciphertext block (or an Initialization Vector, IV, for the first block) before being encrypted. The chaining hides the structure ECB exposes. CBC was the workhorse of TLS 1.0–1.2 and is still in widespread use, but it has subtleties: the IV must be unpredictable, padding must be handled carefully (CBC needs PKCS#7 padding for non-block-aligned messages), and the mode itself doesn't provide integrity. Padding-oracle attacks against CBC have produced years of TLS bugs (BEAST, Lucky 13, POODLE).

CTR (Counter). Treat the block cipher as a key-stream generator. Encrypt successive counter values (nonce || counter); XOR the resulting key stream with the plaintext. CTR is conceptually clean, parallelizable (different blocks can be encrypted independently), and doesn't need padding. Its critical weakness is that nonce reuse is catastrophic: encrypting two different plaintexts under the same (key, nonce) produces XOR-of-plaintexts visible in the XOR-of-ciphertexts. CTR is the basis of GCM (Galois/Counter Mode), which adds integrity, and is what TLS 1.3 actually uses.

Other modes — CFB, OFB, XTS — exist for specific use cases (XTS is what's used for full-disk encryption). For protocol design, CBC is legacy, CTR is the foundation of modern AEAD, and ECB is forbidden.

The cleanest 2026 advice: use AES in GCM mode (more on this in Module 2.2 — AEAD construction). GCM combines CTR for confidentiality with a Galois-field MAC for integrity, in one construction. It's what TLS 1.3, IPsec ESP-GCM, and many other modern protocols use.

Why ECB is the canonical embarrassment

ECB encrypts each 16-byte plaintext block independently with the same key. Identical plaintext blocks produce identical ciphertext blocks. Patterns in the plaintext show up directly in the ciphertext.

The textbook example is encrypting an image where most pixels are similar colors. Convert the image to a raw byte stream, encrypt with ECB, convert back to an image: you can see the original outline because the byte patterns survived.

But ECB's failure isn't just about images. Any structured data has repetition that ECB preserves:

  • HTTP headers have repeated strings (Content-Type: appearing in many messages).
  • Database records have similar fields across rows.
  • Log lines often share format.

If an attacker knows the structure, they can correlate ciphertext blocks across messages and infer plaintext patterns even without a key.

The cleanest case for "bad mental model" is the persistent belief that "ECB is insecure because AES is broken." It isn't. AES-the-block-cipher is fine; ECB-the-mode is broken. Even if you replaced AES with a perfect, theoretically-ideal block cipher, ECB would still leak structure. The vulnerability is at the mode-of-operation layer, not the cipher itself.

A working security engineering principle: if you ever find yourself reaching for ECB, stop and use CTR or GCM instead. The only legitimate ECB uses are encrypting single blocks (where the multi-block leak doesn't apply) or as a building block inside other constructions (like KWP or ECB-MAC variants). Treating ECB as a general-purpose encryption mode is not a technical error; it's a literacy failure.

Hardware acceleration and side channels

Almost every modern CPU has dedicated AES instructions. AES-NI (Intel and AMD x86), ARM Cryptography Extensions, and POWER ISA Crypto all turn AES into a few-cycle operation rather than the dozens-of-cycles it'd take in software. AES-GCM with hardware acceleration runs at multiple GB/s on a single core, which is why HTTPS at line rate is feasible.

Hardware acceleration also tends to be constant-time: the operation takes the same number of cycles regardless of the key value or plaintext. This is critical because side-channel attacks — analyzing how long an operation takes, how much power it consumes, what cache lines it accesses — can extract keys from non-constant-time implementations. Naive software AES with a 256-byte S-box table is vulnerable to cache-timing attacks; AES-NI sidesteps this by avoiding the table entirely.

Two implications for engineers:

  • Use a library, not your own implementation. Crypto code that's not constant-time is a real exploitable vulnerability. Standard libraries (OpenSSL, libsodium, BoringSSL) handle these issues; hand-rolled crypto rarely does.
  • Verify hardware acceleration is on. Run openssl speed -evp aes-128-gcm and check whether the throughput is software-only (few hundred MB/s) or hardware-accelerated (multiple GB/s). If your kernel or library missed AES-NI detection, you're paying a substantial performance cost.

Hands-on exercise

Exercise 1 — Encrypt the same plaintext in ECB and CBC

Create a test plaintext with repetition:

python3 -c "import sys; sys.stdout.buffer.write(b'AAAAAAAAAAAAAAAA' * 4)" > /tmp/plain.bin

That's 64 bytes — four identical 16-byte AES blocks.

Encrypt with ECB:

openssl enc -aes-128-ecb -K 00112233445566778899aabbccddeeff -in /tmp/plain.bin -out /tmp/ecb.bin
xxd /tmp/ecb.bin

You'll see something like:

00000000: 5d8a 7c6f 4d8c 7d3f 2e1a 5f9e 8c4b 7d62  ]..oM.}?..._..K}b
00000010: 5d8a 7c6f 4d8c 7d3f 2e1a 5f9e 8c4b 7d62  ]..oM.}?..._..K}b
00000020: 5d8a 7c6f 4d8c 7d3f 2e1a 5f9e 8c4b 7d62  ]..oM.}?..._..K}b
00000030: 5d8a 7c6f 4d8c 7d3f 2e1a 5f9e 8c4b 7d62  ]..oM.}?..._..K}b

Four identical ciphertext blocks. The pattern of identical plaintext blocks is visible directly in the ciphertext.

Now encrypt the same plaintext with CBC:

openssl enc -aes-128-cbc -K 00112233445566778899aabbccddeeff -iv 00000000000000000000000000000000 -in /tmp/plain.bin -out /tmp/cbc.bin
xxd /tmp/cbc.bin

The CBC output has four different 16-byte blocks because each block depends on the previous ciphertext block (and the first depends on the IV). The repetition in the plaintext doesn't survive into the ciphertext.

(Note: we're using a fixed IV here for demonstration only. In real CBC use, the IV must be unpredictable per encryption — a fixed IV is its own vulnerability.)

Stretch: write a 10-line Python script that detects repeated 16-byte blocks in a ciphertext file. Run it against /tmp/ecb.bin and /tmp/cbc.bin. ECB will have many repeats; CBC will have none.

Exercise 2 — Inspect block boundaries in Python

"""Show how block boundaries drive the AES interface."""

message = b"This message is exactly 32 bytes."[:32]   # exactly 2 blocks
assert len(message) == 32

print(f"message length: {len(message)} bytes = {len(message) // 16} blocks")
for i in range(0, len(message), 16):
    print(f"  block {i//16}: {message[i:i+16]!r}")

# Now a non-block-aligned message
ragged = b"This message is 25 bytes."
assert len(ragged) == 25

print(f"\nragged length: {len(ragged)} bytes = {len(ragged) // 16} full blocks + {len(ragged) % 16} extra")

# PKCS#7 padding: pad to next block boundary with bytes of value = pad length
pad_len = 16 - (len(ragged) % 16)
padded = ragged + bytes([pad_len] * pad_len)
print(f"PKCS#7 padded: {padded!r} ({len(padded)} bytes)")

Run it. The output shows how a block-cipher mode like CBC needs padding to handle non-aligned data. Stream-like modes (CTR, GCM) don't need padding because they operate on a key stream that's XOR'd with arbitrary-length plaintext — block boundaries are invisible to the application.

This is a small but practical reason CTR-based modes have largely displaced CBC in modern protocols: padding-oracle attacks and the implementation complexity of safe padding are both eliminated.

Common misconceptions

"AES encrypts files directly." AES is a primitive that processes 16-byte blocks. Real file encryption uses a mode of operation around AES, plus an integrity mechanism, plus key derivation, plus IV/nonce handling. "AES" by itself isn't a complete encryption scheme; it's the engine.

"AES-256 is always the obviously correct choice." AES-256 has more rounds and is slower than AES-128. Its security margin against current attacks isn't dramatically larger; both are well beyond practical attack. AES-128 is a fine default for most applications. AES-256 is the right choice when the cost of crypto is irrelevant, when post-quantum margin matters, or when compliance requirements force it.

"ECB is insecure because AES is broken." AES is not broken. ECB is structurally unsafe regardless of which block cipher it wraps. Replacing AES with a hypothetical perfect cipher would not save ECB; the mode itself leaks plaintext patterns into ciphertext patterns by encrypting identical blocks identically.

"Confidentiality implies integrity." A plain encryption mode hides content but doesn't detect tampering. An attacker who flips bits in CBC ciphertext gets unpredictable plaintext changes on decryption — the receiver may notice nothing if the resulting bytes are still parseable. AEAD modes (GCM, ChaCha20-Poly1305) provide both confidentiality and integrity; plain CBC or CTR don't.

"If the ciphertext looks random, the scheme is safe." Random-looking output is necessary but nowhere near sufficient. ECB ciphertext also looks random byte-by-byte; the leakage is across blocks. IV reuse in CTR or GCM produces individual ciphertexts that look random but become catastrophically insecure when combined. "Looks random" is a sanity check, not a security proof.

Further reading

  1. FIPS 197 — Advanced Encryption Standard (AES). The 2001 specification, updated in 2023. The actual round function and key schedule, byte by byte.
  2. NIST SP 800-38A — Recommendation for Block Cipher Modes of Operation. ECB, CBC, CTR, and others. Use it to confirm what each mode is supposed to do.
  3. NIST SP 800-38D — Recommendation for Block Cipher Modes of Operation: Galois/Counter Mode (GCM). The GCM spec; we'll use this as the foundation for Module 2.2's AEAD discussion.
  4. Joan Daemen and Vincent Rijmen, The Design of Rijndael, 2002. The book by the cipher's authors. Worth reading once for the design rationale.
  5. Bruce Schneier, Niels Ferguson, Tadayoshi Kohno, Cryptography Engineering, 2nd ed. The accessible textbook for working engineers; chapters on block ciphers and modes are particularly clean.
  6. Cryptopals Crypto Challenges, set 2. Hands-on attacks on ECB and CBC misuse. Doing them is the fastest way to internalize why mode choice matters.

The next module — Stream ciphers and AEAD construction — picks up by showing how to build a complete encryption-with-integrity scheme from a block cipher (or a stream cipher) plus a MAC, and why ChaCha20-Poly1305 became the alternative-default to AES-GCM in modern protocols.