Section 4: Symmetric Cryptography
Symmetric cryptography is the workhorse of the modern internet. Every HTTPS page you load, every disk you encrypt, every messaging app you use — the bulk of the data is protected by a symmetric cipher, almost always AES or ChaCha20. Public-key cryptography (Section 6) gets the glamour, but it is typically used only to agree on a small secret; after that, symmetric crypto does the heavy lifting, because it is thousands of times faster.
Symmetric crypto in one picture
The defining property: the same key encrypts and decrypts. Alice and Bob share one secret value k. Alice runs the encryption function E(k, plaintext) to produce ciphertext; Bob runs the decryption function D(k, ciphertext) to recover the plaintext. Anyone without k should learn nothing useful from the ciphertext.
This elegance comes at a price: the key-distribution problem. Before Alice and Bob can communicate, they must somehow share k over a channel the attacker cannot read — but a secure channel is exactly what they are trying to build. With two people that means a meeting or a courier; with n people who all want pairwise secure links, you need n(n−1)/2 keys. For the pre-internet military this meant printed codebooks and armored briefcases. For the internet it was a showstopper until Diffie–Hellman and RSA arrived in the 1970s (Section 6). Keep that limitation in mind — everything on this page assumes the key is already safely shared.
Stream ciphers vs block ciphers
Symmetric ciphers come in two families that differ in how they consume the plaintext.
A stream cipher takes the key (plus a nonce) and expands it into an endless pseudorandom keystream, which is simply XORed with the plaintext byte by byte:
plaintext: 48 65 6c 6c 6f "Hello"
keystream: a3 91 07 5e c2 (generated from key + nonce)
-------------- XOR
ciphertext: eb f4 6b 32 ad
Decryption is the same operation — XOR the ciphertext with the same keystream and the plaintext falls out, because x ⊕ k ⊕ k = x. The inspiration is the one-time pad: if the keystream were truly random, used once, and as long as the message, the cipher would be provably unbreakable (Shannon, 1949). But a truly random pad as long as all your traffic is wildly impractical to distribute — you are back to couriers with briefcases. A stream cipher is the practical approximation: a short key stretched into a long keystream that is not truly random, merely computationally indistinguishable from random.
History note: RC4 (Ron Rivest, 1987) was for two decades the most-used stream cipher on Earth — it secured SSL/TLS and Wi-Fi WEP. But its keystream has statistical biases, especially in the first bytes; attacks steadily improved until RC4 was practically breakable in TLS, and RFC 7465 (2015) banned it outright. Its modern successor is ChaCha20 (Daniel J. Bernstein), which is fast in pure software, constant-time by design, and used by TLS 1.3 and WireGuard — usually paired with the Poly1305 authenticator.
A block cipher instead is a keyed permutation on fixed-size chunks: AES maps a 128-bit (16-byte) input block to a 128-bit output block, invertibly, under the control of the key. On its own it can only handle exactly one block — encrypting real messages requires a mode of operation, which is the main topic of this page.
| Stream cipher | Block cipher | |
|---|---|---|
| Unit of work | Bit or byte at a time | Fixed block (AES: 128 bits) |
| Core operation | keystream XOR plaintext | Keyed permutation of a block |
| Padding needed | No — any length works | Yes, in some modes (ECB, CBC) |
| Classic examples | RC4 (dead), ChaCha20, Salsa20 | DES (dead), 3DES (retired), AES |
| Ideal ancestor | One-time pad | Random permutation |
| Typical use | TLS (ChaCha20-Poly1305), VPNs | TLS (AES-GCM), disk encryption |
The boundary is blurrier than it looks: CTR mode, covered below, turns any block cipher into a stream cipher. In practice "AES in CTR/GCM mode" and "ChaCha20" are doing the same job with different machinery.
DES — the cipher that started modern cryptanalysis
The Data Encryption Standard, standardized by the US government in 1977 from an IBM design, was the first cipher whose full specification was public and free to analyze. That openness essentially created the academic field of cryptanalysis. DES encrypts 64-bit blocks under a 56-bit key using 16 rounds of a Feistel network — a beautiful construction in which the block is split into left and right halves and each round transforms them like this:
The Feistel trick is that F never needs to be inverted: to undo a round you recompute F(R, Ki) and XOR it back out. This let designers make F as nonlinear and ugly as they wanted (DES uses eight substitution tables called S-boxes) and still get decryption for free.
The design held up remarkably well against mathematics — differential cryptanalysis, discovered publicly in 1990, turned out to be something the DES S-boxes had been secretly hardened against in 1974. What killed DES was arithmetic: 256 ≈ 7.2 × 1016 keys is simply not enough. In 1998 the Electronic Frontier Foundation built Deep Crack, a custom machine costing about $250,000, and brute-forced a DES key in 56 hours; a refined run in early 1999, teamed with distributed.net, did it in about 22 hours. A key space that a hobbyist budget can exhaust is no key space at all — today a rig of off-the-shelf GPUs does the same job in days.
Triple DES (3DES) was the stopgap: encrypt–decrypt–encrypt with two or three DES keys, giving an effective strength of about 112 bits. It bought the industry two decades, but its tiny 64-bit block size proved fatal in a different way: after encrypting around 232 blocks (only 32 GB) under one key, birthday collisions between ciphertext blocks start leaking plaintext — the practical Sweet32 attack (2016) exploited exactly this against long-lived HTTPS and VPN sessions. NIST formally retired 3DES for encryption at the end of 2023. If you see it in a codebase today, that is a finding, not a feature.
AES — the current standard
In 1997 NIST ran an open, worldwide competition to replace DES. Fifteen submissions, three years of public cryptanalysis, and the winner — announced in 2000 and standardized in 2001 as FIPS 197 — was Rijndael, by Belgian cryptographers Joan Daemen and Vincent Rijmen. As AES it encrypts 128-bit blocks with keys of 128, 192, or 256 bits, using 10, 12, or 14 rounds respectively.
Unlike DES, AES is not a Feistel network but a substitution–permutation network (SPN): every round transforms the entire block at once through alternating layers of substitution (confusion) and mixing (diffusion). The block lives in a 4×4 grid of bytes called the state, and each round applies four operations to it:
- SubBytes — every byte is replaced through a fixed 256-entry lookup table, the S-box, built from inversion in the finite field GF(28). This is the only nonlinear step; without it, AES would be solvable with linear algebra.
- ShiftRows — row i of the state rotates left by i bytes (row 0 stays put). Cheap, but it moves bytes between columns so the next step can mix them.
- MixColumns — each column of 4 bytes is multiplied by a fixed matrix over GF(28), so every output byte depends on all four input bytes of its column. ShiftRows + MixColumns together guarantee that after just two rounds every bit of the state depends on every bit of the input — the avalanche effect.
- AddRoundKey — the 128-bit round key is XORed into the state. This is the only place the key touches the data; everything else is public, fixed machinery.
(The final round skips MixColumns, and an extra AddRoundKey happens before round 1 — bookkeeping details that make decryption symmetric.)
The key schedule in one paragraph: the cipher key is expanded into one round key per round (11 for AES-128) by a lightweight recurrence — each new 4-byte word is the previous word, optionally rotated, pushed through the S-box, and XORed with a round constant, then XORed with the word one key-length back. It is deliberately fast rather than cryptographically strong on its own; the related-key weaknesses it admits in AES-256 are theoretical curiosities, not practical attacks. After a quarter century of intense analysis, the best key-recovery attack on full AES-128 (biclique, 2011) saves a factor of about four over brute force — that is, it remains completely secure.
AESENC performs a full round in roughly one cycle per 16-byte block. This makes AES-GCM run at multiple gigabytes per second per core, and, just as importantly, makes it constant-time: no secret-dependent table lookups, so no cache-timing side channels. It is a big reason AES remains the default even though ChaCha20 is faster in pure software on low-end devices.
Modes of operation — where the real danger lives
AES securely encrypts exactly 16 bytes. Your message is not 16 bytes. A mode of operation is the recipe for applying the block cipher to arbitrary-length data — and this is where most real-world symmetric crypto failures happen. The cipher is almost never the weak point; the mode, the IV handling, and the missing authentication are.
ECB — the mode that is not a mode
Electronic Codebook is the naive approach: chop the plaintext into blocks and encrypt each one independently with the same key. The fatal flaw is immediate: identical plaintext blocks produce identical ciphertext blocks. AES is deterministic — same key, same input, same output — so any repetition or structure in the plaintext survives into the ciphertext. Encrypt a bitmap image with ECB and you can still see the picture; only the colors change. This is the famous ECB penguin (the Tux mascot encrypted with ECB, still recognizably a penguin). Watch it happen live:
Notice that clicking the button changes which colors ECB picks, but never destroys the shape — the pattern of "these blocks are equal" is preserved no matter what the key is. That equality pattern is information, and ECB leaks it. Real casualties include the 2013 Adobe breach, where 153 million passwords encrypted with 3DES-ECB became a giant crossword puzzle (identical passwords had identical ciphertexts, and password hints filled in the answers). Rule: never use ECB for anything.
CBC — chaining blocks together
Cipher Block Chaining fixes the determinism by XORing each plaintext block with the previous ciphertext block before encryption. The first block has no predecessor, so it is XORed with a random initialization vector (IV) that is sent along with the ciphertext.
Requirements and consequences: the IV must be unpredictable (random) for each message — a merely unique but predictable IV enabled the BEAST attack on TLS 1.0. The plaintext must be padded to a whole number of blocks (see PKCS#7 below). Encryption is inherently sequential — block i cannot start until block i−1 is done — though decryption can be parallelized since all ciphertext blocks are already in hand. And crucially, CBC by itself provides no integrity: an attacker who flips bit j of Ci flips bit j of Pi+1 predictably. Combined with padding, this is the raw material for padding-oracle attacks.
CTR — the block cipher as stream cipher
Counter mode takes a different route entirely: instead of encrypting the plaintext, it encrypts a counter. Take a per-message nonce, append a block counter (1, 2, 3, ...), encrypt each nonce‖counter value with AES, and use the outputs as a keystream to XOR with the plaintext:
keystream block i = E(k, nonce || i)
ciphertext block i = plaintext block i XOR keystream block i
This has lovely properties: no padding (just truncate the last keystream block), fully parallelizable in both directions (every counter value is independent — great for GPUs and disk encryption), and random access (decrypt block 5,000 without touching blocks 1–4,999). Decryption does not even use the AES decryption circuit — it is the same XOR. The one iron rule: a nonce‖counter value must never repeat under the same key, because two messages encrypted with the same keystream XOR together to reveal the XOR of the plaintexts. Like CBC, plain CTR provides no integrity — flipped ciphertext bits flip plaintext bits exactly.
GCM — encryption that proves itself
Galois/Counter Mode is CTR encryption plus an integrity check called GHASH, a fast polynomial-evaluation MAC over GF(2128) computed over the ciphertext (and any additional non-encrypted "associated data" such as packet headers). The result is a 16-byte authentication tag appended to the ciphertext. On decryption, the tag is recomputed and compared: if even one bit of the ciphertext, tag, or associated data was modified, decryption fails outright and returns nothing.
This combination — confidentiality plus integrity in one primitive — is called AEAD (Authenticated Encryption with Associated Data), and it is the modern default for a simple reason: decades of attacks (padding oracles, bit-flipping, ciphertext splicing) all begin with the attacker modifying ciphertext and observing what happens. AEAD closes that entire class. TLS 1.3 permits only AEAD ciphers: AES-GCM and ChaCha20-Poly1305. GCM uses a 96-bit nonce which, as in CTR, must never repeat under a key — with the extra sting that nonce reuse in GCM also leaks the GHASH key, letting the attacker forge tags at will.
| Mode | Needs IV/nonce | Parallel? | Padding? | Authenticated? | Verdict |
|---|---|---|---|---|---|
| ECB | No | Yes | Yes | No | Never use |
| CBC | Random IV | Decrypt only | Yes (PKCS#7) | No — add a MAC | Legacy; padding-oracle prone |
| CTR | Unique nonce | Yes, both ways | No | No — add a MAC | Fine as a building block |
| GCM | Unique 96-bit nonce | Yes, both ways | No | Yes (AEAD) | Use this |
Try it live: AES-256-GCM in your browser
Your browser ships real AES via the Web Crypto API. This widget derives a 256-bit key from your passphrase with PBKDF2 (310,000 iterations of SHA-256 over a random salt — never use a raw password as a key), then encrypts with AES-GCM under a fresh random 96-bit IV. The output bundles salt ‖ IV ‖ ciphertext+tag as hex. Round-trip it — then try decrypting with a wrong passphrase, or change one hex digit of the ciphertext, and watch GCM refuse.
Encrypt the same message twice: the ciphertext is completely different each time, because the salt and IV are fresh. That is what semantic security looks like — and what ECB fails to provide.
Padding: PKCS#7 at the byte level
Block-oriented modes like CBC need the plaintext length to be a multiple of 16 bytes. The universal scheme is PKCS#7: if n bytes of padding are needed, append n bytes each with value n. Concretely, padding "HELLO" (5 bytes) to a 16-byte block needs 11 bytes of padding:
48 45 4c 4c 4f 0b 0b 0b 0b 0b 0b 0b 0b 0b 0b 0b
H E L L O ┌───── 11 bytes of value 0x0b ─────┐
If the plaintext already fills the block exactly, a whole extra block of sixteen 0x10 bytes is appended — the padding must always be present so the receiver can unambiguously strip it: read the last byte, check that many trailing bytes all match, remove them.
How symmetric crypto actually gets broken
- IV/nonce reuse — catastrophic in CTR and GCM: two messages under the same key+nonce share a keystream, so
C1 ⊕ C2 = P1 ⊕ P2, and known plaintext in one message directly reveals the other. In GCM, reuse additionally leaks the authentication key, enabling arbitrary forgeries. This exact bug broke WEP Wi-Fi, some PS3 firmware signing (a related nonce failure), and countless home-grown protocols. - Using ECB — structure and repetition in the plaintext survive encryption. See penguin above; see Adobe 2013.
- Using a password directly as the key — passwords have far too little entropy and the wrong length. Always run them through a purpose-built KDF (PBKDF2 with high iterations, or better, scrypt/Argon2) with a random salt.
- Hardcoded keys — a key embedded in a binary, firmware image, or git repository is public. Extracted keys from IoT firmware are a cottage industry.
- Unauthenticated encryption — CBC or CTR without a MAC lets attackers flip plaintext bits and mount padding-oracle attacks. If you are not using an AEAD mode, you are probably vulnerable to something. Default to AES-GCM or ChaCha20-Poly1305.
Notice the theme: none of these break AES itself. The mathematics has never been the weak point — the deployment always is.