Symmetric Encryption

Modes Of Operation

ECB, CBC, CTR, GCM: why the mode matters as much as the cipher.

A block cipher like AES only encrypts a single 16-byte block. The mode of operation is the recipe that applies it across a whole message, and choosing the wrong recipe breaks security even when the cipher is perfect. The mode is where most real-world symmetric-encryption failures live, so it deserves as much attention as the cipher itself.

The ECB penguin: seeing why ECB leaks structure

Electronic Codebook (ECB) is the naive approach: split the message into blocks and encrypt each block independently with the same key. The fatal flaw is determinism across blocks. Identical plaintext blocks always produce identical ciphertext blocks, so any repetition or structure in the data shows straight through the “encryption”.

You can see this directly. Take a simple bitmap with large uniform regions, encrypt it with ECB, and the outline remains visible because repeated pixel blocks map to repeated ciphertext blocks.

mkdir -p ~/crypto-lab && cd ~/crypto-lab
# Build a file with repeated 16-byte blocks
python3 -c "open('plain.bin','wb').write(b'AAAAAAAAAAAAAAAA'*64)"
KEY=$(openssl rand -hex 32)

# ECB: repeated input blocks -> repeated output blocks
openssl enc -aes-256-ecb -K $KEY -in plain.bin -out ecb.bin -nopad
echo "distinct 16-byte ciphertext blocks under ECB:"
xxd -p -c16 ecb.bin | sort -u | wc -l

Expected output:

distinct 16-byte ciphertext blocks under ECB:
       1

Sixty-four identical input blocks produced exactly one distinct ciphertext block. The structure leaked completely. Now compare a randomized mode:

cd ~/crypto-lab
IV=$(openssl rand -hex 16)
openssl enc -aes-256-cbc -K $KEY -iv $IV -in plain.bin -out cbc.bin -nopad
echo "distinct 16-byte ciphertext blocks under CBC:"
xxd -p -c16 cbc.bin | sort -u | wc -l

Expected output:

distinct 16-byte ciphertext blocks under CBC:
      64

CBC produced 64 distinct blocks from the same repetitive input. The rule is absolute: never use ECB for anything.

CBC and IVs; CTR and nonces

Both CBC and CTR fix ECB’s determinism by mixing in a per-message value, but the requirements on that value differ, and misusing it is a common bug.

Cipher Block Chaining (CBC) XORs each plaintext block with the previous ciphertext block before encrypting, and seeds the chain with an Initialization Vector (IV). The IV must be unpredictable (random) for each message; a predictable IV enabled the BEAST attack against TLS 1.0. CBC also needs padding, which historically opened padding-oracle attacks (POODLE, Lucky Thirteen) when decryption errors were observable.

Counter (CTR) mode turns the block cipher into a stream cipher: it encrypts a running counter combined with a nonce, then XORs the result with the plaintext. The nonce plus counter must never repeat under the same key. If a nonce is reused, two ciphertexts XOR to reveal the XOR of their plaintexts, which is often enough to recover both.

ModePer-message valueUniqueness requirementParallelizableProvides integrity?
ECBnonenever useyesno
CBCIVrandom and unpredictableencrypt: no, decrypt: yesno
CTRnonce + counterunique per key (never repeat)yesno
GCMnonceunique per key (never repeat)yesyes (AEAD)
ChaCha20-Poly1305nonceunique per key (never repeat)yesyes (AEAD)

The critical point across the whole table: ECB, CBC, and CTR provide confidentiality only. None of them detect tampering. An attacker can flip bits in CTR ciphertext and flip the exact same bits in the recovered plaintext. CBC ciphertext manipulation shifts predictably too. Confidentiality without integrity is malleable, which is the subject of Authenticated Encryption.

Choosing a mode in 2026: almost always an AEAD

For essentially all new work, the answer is an AEAD mode: AES-GCM or ChaCha20-Poly1305. These give confidentiality and integrity together, so a tampered ciphertext fails to decrypt rather than returning attacker-chosen plaintext. You should reach for raw CBC or CTR only when a higher-level protocol already supplies authentication (for example, encrypt-then-MAC assembled by an expert), and even then a plain AEAD is simpler and safer.

cd ~/crypto-lab
echo "balance: 42 credits" > acct.txt
KEY=$(openssl rand -hex 32); NONCE=$(openssl rand -hex 12)
openssl enc -aes-256-gcm -K $KEY -iv $NONCE -in acct.txt -out acct.gcm 2>/dev/null
echo "AEAD ciphertext bytes: $(wc -c < acct.gcm)"

Expected output:

AEAD ciphertext bytes: 20

See AES for the cipher itself, Authenticated Encryption for how GCM adds the tag, and ChaCha20-Poly1305 for the software-friendly alternative.

Practical Guidance

  1. Never use ECB. It leaks plaintext structure regardless of key strength, as the block-count demo above shows.
  2. Default to an AEAD (AES-GCM or ChaCha20-Poly1305). It removes the entire class of malleability and padding-oracle bugs.
  3. If you ever touch CBC, use a random unpredictable IV per message and never expose padding or decryption errors to callers.
  4. If you ever touch CTR, guarantee the nonce plus counter never repeats under a given key. A single repeat can expose two full plaintexts.
  5. Never treat any of ECB, CBC, or CTR as providing integrity. Pair them with a MAC only under expert design, or just use an AEAD.
  6. Follow rule zero: let an audited library assemble the mode. Hand-wiring cipher, mode, IV, and MAC is where breakage happens.