Under the Hood
Crypto

Symmetric encryption: one shared key

Symmetric encryption uses a single shared key to both encrypt and decrypt — it does the bulk of the world's real encryption because it's fast, but using it safely is entirely about the mode and the nonce, and it can't solve the one problem it depends on, getting that shared key to the other side.

Symmetric encryption: one shared key

Encrypt a file with a password, and hand someone the same password to open it — that's symmetric encryption, and it's almost certainly what "encryption" means in your head. One key. It locks, and it unlocks. Every disk encryption tool, every VPN tunnel, every HTTPS connection once the handshake finishes, is symmetric encryption doing the actual work of scrambling bytes, because it's fast enough to run on every packet without anyone noticing. Lesson 1 put it on the map as the primitive that provides confidentiality. This lesson is about the machinery underneath: what a block cipher actually does to a block, why doing that same simple thing to every block of a real message is a trap, and the one modern answer — AEAD — that closes the trap for good.

The frame: a shared key encrypts and decrypts, but the key alone doesn't make encryption safe — the mode that stitches blocks together and the nonce that makes each encryption unique are just as load-bearing, and getting that shared key to a stranger in the first place is a problem symmetric encryption cannot solve on its own.

One key, two directions

Symmetric encryption takes a key and a plaintext and produces a ciphertext; the same key run in reverse takes that ciphertext back to the plaintext. Nothing asymmetric about it — whoever holds the key can do both operations. That symmetry is exactly why it's fast: the underlying math is comparatively simple arithmetic and bit-shuffling, not the number theory public-key crypto needs (lesson 5), and modern CPUs even have dedicated instructions for it (AES-NI). It's also exactly why it has one hard requirement baked in from the start: both sides must already possess the identical key before any of this works, and that key must never reach anyone else.

Block ciphers vs. stream ciphers

There are two ways to build the core scrambling operation.

A block cipher operates on a fixed-size chunk of data at a time — encrypt exactly one block, get back exactly one block, same size. AES (Advanced Encryption Standard) is the universal answer here: a 128-bit block size, with key sizes of 128 or 256 bits, standardized after a public competition, implemented in hardware on essentially every modern CPU, and — used correctly — unbroken by any known attack. When people say "encrypted with AES," this is the primitive underneath.

A stream cipher doesn't operate on fixed blocks at all. It uses the key (and a nonce) to generate an arbitrarily long keystream — a pseudorandom sequence of bytes — and produces ciphertext by XORing that keystream with the plaintext, byte for byte. ChaCha20 is the modern, widely deployed stream cipher: no block alignment to worry about, and fast even without hardware acceleration, which is why it's popular on mobile and embedded chips that lack AES-NI.

The line between them blurs in practice, because the most common way to use a block cipher today — the CTR mode below — turns AES itself into a stream cipher. The distinction that matters isn't "which one is more modern," it's what comes next: how do you handle a message longer than one block?

Modes of operation: the part that actually goes wrong

A block cipher only knows how to transform one fixed-size block. Real messages are arbitrary length. The mode of operation is the recipe for chaining many block operations together to cover an entire message — and this is where almost every symmetric-encryption disaster in history actually happened, not in the cipher itself.

ECB (Electronic Codebook) — broken, never use it. The naive approach: split the plaintext into blocks and encrypt each one independently with the same key. The problem is immediate — identical plaintext blocks always produce identical ciphertext blocks. Encrypt a bitmap image in ECB mode and the output still shows the outline of the original picture, because large flat regions of identical pixel bytes become large flat regions of identical ciphertext bytes; this is famous enough to have a name, the "ECB penguin." Any structure or repetition in your data — image backgrounds, repeated header fields, padded records — leaks straight through the "encryption." ECB isn't a weak option to avoid when you can; it fails to provide confidentiality at all for any data with structure, which is all real data.

CBC (Cipher Block Chaining). Each plaintext block is XORed with the previous ciphertext block before being encrypted, so identical plaintext blocks now produce different ciphertext (as long as what came before differs). The first block has no predecessor, so it's XORed with an IV (initialization vector) instead — which is why CBC needs one. CBC fixed the ECB leak, but it's fragile in other ways: it's strictly sequential (can't decrypt block 5 without block 4), and mishandled padding around it produced a long line of real-world padding-oracle attacks. It still shows up, but it's being phased out in favor of modes built for AEAD (below).

CTR (Counter mode). Instead of chaining blocks together, CTR encrypts a counter — nonce concatenated with an incrementing number — for each block position, and XORs that encrypted counter with the plaintext. This is precisely the "turn a block cipher into a stream cipher" trick: the counter values don't depend on the data at all, so they can be computed in advance and in parallel, making CTR fast and parallelizable in a way CBC never can be.

The IV/nonce: why it exists, and the one rule you cannot break

Every mode above needs a value that's unique per encryption — an IV for CBC, a nonce ("number used once") for CTR and the AEAD modes built on it. Its job: make sure that encrypting the same plaintext under the same key twice produces different ciphertext each time, so an eavesdropper watching traffic can't even tell "these two messages are identical" by comparing ciphertexts. Without it, encryption would leak repetition even with a perfect cipher and a perfect mode.

The rule that comes with it is absolute: a nonce must never be reused with the same key. For CTR-family modes (which includes the AEAD modes below), reusing a nonce means the same keystream gets XORed against two different plaintexts — and XOR two ciphertexts that share a keystream, and the keystream cancels out, leaving you the XOR of the two plaintexts. From there, with any knowledge of one message (or just enough statistical structure in typical text), you can recover the other. For GCM specifically, nonce reuse is worse than a confidentiality leak: it can hand an attacker the material needed to forge valid authentication tags on messages they never should have been able to authenticate. This is why libraries generate random 96-bit nonces for GCM by default, and why protocols that count messages (like TLS) use a counter instead of randomness where uniqueness must be guaranteed rather than merely probable.

AEAD: bundling confidentiality with integrity

Lesson 1 flagged the classic trap: encryption alone proves nothing about whether ciphertext was tampered with. CBC and CTR, on their own, are pure confidentiality — flip a bit in CTR ciphertext and the corresponding plaintext bit flips too, silently, with no error raised. An attacker who can't read the message can still edit it.

AEAD — Authenticated Encryption with Associated Data — closes that gap by making integrity part of the same operation instead of a separate step bolted on afterward. AES-GCM and ChaCha20-Poly1305 are the two standard choices: both encrypt the plaintext (GCM using CTR-mode AES internally, Poly1305 pairing with the ChaCha20 stream cipher) and, in the same pass, compute an authentication tag over the ciphertext. That tag rides alongside the ciphertext; decryption recomputes it and refuses to return any plaintext at all if it doesn't match. AEAD also covers associated data — bytes you want authenticated but not encrypted, like a packet header or a protocol version number — which get folded into the tag computation without appearing in the ciphertext.

This is why AEAD is the modern default: it's not "encryption, and separately remember to add a MAC" (a step people forget, or combine in the wrong order); it's one primitive, one call, that hands back ciphertext plus a tag, and a decrypt call that simply fails closed if either was altered.

// Encrypting with AES-GCM via the Web Crypto API
const key = await crypto.subtle.generateKey(
  { name: "AES-GCM", length: 256 },
  true,
  ["encrypt", "decrypt"]
);

// The nonce (IV) — 96 bits, freshly random for every single encryption
const iv = crypto.getRandomValues(new Uint8Array(12));

const ciphertext = await crypto.subtle.encrypt(
  { name: "AES-GCM", iv },
  key,
  new TextEncoder().encode("transfer $500 to account 7734")
);
// `ciphertext` here already has the authentication tag appended —
// SubtleCrypto folds it into the returned buffer automatically.

// Decrypting: same key, same iv, same algorithm — a wrong tag throws
const plaintext = await crypto.subtle.decrypt(
  { name: "AES-GCM", iv },
  key,
  ciphertext
);

Notice what has to travel with the ciphertext for the other side to decrypt: the IV (not secret — it's fine to send in the clear alongside the ciphertext) and, implicitly, the tag bundled into the output. What must never travel alongside it, and must already be shared out of band, is the key.

The problem symmetric encryption cannot solve

Here's the wall every symmetric scheme eventually hits: everything above assumes the key already exists on both ends. Fine for encrypting your own disk — you're both endpoints. Not fine for talking to a server you've never contacted before, over a network an attacker can watch. You cannot just send the key alongside the message; anyone who can see the ciphertext can see the key sent next to it, and the whole scheme collapses. Somehow, two parties who share no secret yet need to end up sharing one, without ever transmitting it in the clear.

Symmetric encryption, by itself, has no answer to that. It's fast and it's the right tool for bulk data, but "how do two strangers agree on a key over a public network" is a different problem, solved by a different family of primitives entirely: public-key cryptography (lesson 5), which uses a mathematically linked key pair instead of one shared secret, and key exchange (lesson 7), which shows how two sides derive that shared key live, over a channel anyone can watch. This is also exactly what happens at the start of every HTTPS connection: the slower public-key machinery runs once, just long enough to hand the two sides a symmetric key, and then AES-GCM (or ChaCha20-Poly1305) takes over for the actual data, because it's the only thing fast enough to do the real work.

Where this goes next

The next lesson picks up the piece symmetric encryption leaves dangling: how do two people who've never met establish a shared secret at all? Public-key cryptography answers it with a key pair instead of a key — one half public, one half private, linked by math that's easy to compute one way and infeasible to reverse — and shows why, despite solving the hard problem, it's still symmetric encryption that does the heavy lifting once the two sides are actually talking.

Go deeper

  • MDN — SubtleCrypto.encrypt() The exact API surface for AES-GCM (and AES-CBC, AES-CTR) encryption in the browser, including parameter shapes for IV and additional data.
  • Crypto 101 Works through block cipher modes and the ECB-penguin failure mode in detail, with diagrams of exactly what leaks and why.
  • NIST SP 800-38D — the GCM specification The authoritative source defining AES-GCM's mode of operation, nonce requirements, and authentication tag construction.

Check yourself

Answer out loud, as if an interviewer asked. If you hand-wave, reread that section.

  1. What does it mean for encryption to be 'symmetric,' and why does that same property make it fast but also require a pre-shared secret?
  2. Explain the ECB penguin: mechanically, why does encrypting an image in ECB mode still show the outline of the original picture?
  3. How does CBC fix the ECB weakness, and why does CBC need an IV for the very first block?
  4. How does CTR mode turn a block cipher into something that behaves like a stream cipher?
  5. State the nonce-reuse rule precisely, and explain mechanically (in terms of XOR and keystreams) why reusing a nonce under CTR/GCM leaks plaintext.
  6. What does AEAD add on top of plain AES-CTR or AES-CBC, and what does the authentication tag actually protect against?
  7. Why can't symmetric encryption alone solve the problem of two strangers establishing a shared key over the open internet?