Chapter 2 · Cryptography· Intermediate· ~18 min

Symmetric Cryptography

Block versus stream, the modes that matter, why nonces never repeat, and how passwords become keys.

You will learn

  • Block ciphers vs stream ciphers, and what AES actually is
  • Why ECB is not a mode you should use, ever
  • CBC, CTR, and how GCM combines counter mode with a MAC into authenticated encryption
  • The single most common crypto bug at this level: reusing a nonce
  • HKDF and PBKDF2/Argon2 for turning secrets into keys

Block ciphers and stream ciphers

A block cipher is a keyed permutation of a fixed-size block of bytes. AES operates on 128-bit blocks with 128-, 192-, or 256-bit keys. By itself, a block cipher only encrypts exactly one block. Any real message needs a mode of operation that wires many block encryptions together.

A stream cipher generates a long pseudorandom keystream from a key and a nonce, and XORs the keystream into the plaintext. ChaCha20 is the canonical modern example. Stream ciphers do not need padding; their security depends absolutely on never reusing the (key, nonce) pair.

ECB: the cautionary tale

Electronic Code Book mode encrypts each block independently with the same key. Identical plaintext blocks produce identical ciphertext blocks. On a bitmap, the structure of the original image is preserved in the encrypted output, which is why every textbook prints the ECB-encrypted Linux penguin.

Real-world impact: ECB leaks structural information about the plaintext. Some attacks let an adversary substitute or rearrange whole blocks of ciphertext without invalidating the result. Treat ECB as a teaching example. No production system should use it.

CBC: chained, needs a unique IV

Cipher Block Chaining XORs each plaintext block with the previous ciphertext block before encrypting. The first block is XORed with an initialization vector, which must be unpredictable for each message but does not have to be secret.

CBC's hazards:

  • Predictable IVs let an attacker mount chosen-plaintext attacks (BEAST against TLS 1.0).
  • Padding is required, which creates the famous padding oracle attack class (POODLE, Lucky 13) when the implementation reveals whether padding was valid.
  • CBC alone provides no authentication. A bit flip in ciphertext flips the corresponding bit in plaintext (and scrambles the previous block) without detection.

CTR: a block cipher as a stream cipher

Counter mode encrypts a counter (typically nonce || counter) with the block cipher to produce keystream, then XORs into the plaintext. No padding. Encryption and decryption are the same operation. Highly parallelizable.

CTR's only real failure mode is nonce reuse: encrypting two different plaintexts with the same (key, nonce) gives an attacker the XOR of the two plaintexts, which is enough to recover both.

GCM: CTR plus a MAC, in one primitive

Galois/Counter Mode is CTR encryption plus a polynomial MAC (GHASH) computed over the ciphertext and any associated data. The result is an authenticated encryption with associated data (AEAD) construction: one call, one tag, one verification. AES-GCM is the default for new TLS deployments; ChaCha20-Poly1305 is the alternative for software-only environments.

GCM's caveats:

  • Nonce reuse is catastrophic. It leaks plaintext AND lets the attacker forge tags for arbitrary messages.
  • The standard 96-bit random nonce gets close to the birthday bound after about 2^32 messages per key. Long-lived keys need a counter-based nonce instead.
  • The 128-bit tag should be verified before any plaintext is processed; "decrypt then check" is the wrong order and has produced real CVEs.

The nonce rule, restated

For every AEAD you will ever use: a (key, nonce) pair must be unique across the lifetime of the key. Random 96-bit nonces are usually safe up to about 2^32 messages per key. Counter-based nonces are safe forever but require coordination across senders.

Padding oracles, in one paragraph

If a CBC-mode decryption tells you, however indirectly, whether the padding was valid (different error message, different timing, different log line), an attacker can decrypt arbitrary ciphertext in O(block_size * 128) queries. The fix is constant-time decryption that returns one error regardless of cause, or, more reliably, an AEAD that authenticates before it decrypts.

From passwords to keys: KDFs

A user-typed password is not a cryptographic key. It has too little entropy, the wrong character distribution, and the wrong length. A key derivation function stretches it into a key suitable for AES.

  • HKDF: derives keys from already-uniform secrets (shared TLS pre-master, ECDH output). Fast, not designed to resist brute force on a low-entropy input.
  • PBKDF2: slow on purpose, parameter is iteration count. Old but still acceptable when memory-hard alternatives are not available.
  • scrypt / Argon2id: memory-hard. Force the attacker to spend RAM as well as CPU, which neutralizes GPU/ASIC attacks. Argon2id is the current default for new systems.

One choice for new systems

For password storage: Argon2id with parameters tuned to one login per second on your slowest server. For session keys: HKDF over your ECDH output. For encrypting data at rest: AES-256-GCM or ChaCha20-Poly1305 with a counter-based nonce.