Introduction

Every time you connect to a website, download an app update, or send an encrypted message, AES is doing the heavy lifting somewhere in the chain. AES — the Advanced Encryption Standard — is a block cipher: it takes a 128-bit chunk of data and a secret key, scrambles them together, and produces 128 bits of ciphertext that look like random noise to anyone without the key.

But real data is never exactly 128 bits. It could be a photo, a database, a stream of video. So how do you use a block cipher to encrypt something larger? You need a mode of operation — a recipe for combining successive blocks.

Here is the uncomfortable truth: the cipher is almost never what breaks. AES has withstood decades of cryptanalysis. The modes, on the other hand, have a long history of catastrophic failures. ECB leaks the structure of your data in plain sight. CBC enabled the POODLE and BEAST attacks. CTR is brilliant but reusing a nonce is fatal. GCM is today's gold standard — and it too can be misused.

The mode is the part nobody teaches you until something breaks.

The ECB Penguin

The most famous demonstration in applied cryptography is the ECB penguin: encrypt the Linux mascot Tux with AES in ECB mode and the penguin is still clearly visible in the ciphertext — because identical plaintext blocks produce identical ciphertext blocks, and the image has large uniform regions.

Draw a shape below (or use the preset) and encrypt it in ECB mode. Notice how the structure survives encryption. Then switch to CTR mode and see the difference.

<div class="controls">
  <div class="btn-group">
    <button id="btn-tux" type="button" class="active">{{btn_load_tux}}</button>
    <button id="btn-clear" type="button" class="ghost">{{btn_clear_canvas}}</button>
  </div>
  <div class="mode-row">
    <span class="label">{{label_enc_mode}}</span>
    <label class="radio"><input type="radio" name="mode" value="ecb" checked> ECB</label>
    <label class="radio"><input type="radio" name="mode" value="ctr"> CTR</label>
  </div>
  <button id="btn-encrypt" type="button">{{btn_encrypt}}</button>
  <button id="btn-decrypt" type="button" class="ghost" disabled>{{btn_decrypt}}</button>
</div>
<div class="canvases">
  <div class="canvas-wrap">
    <div class="canvas-label">{{label_plaintext}}</div>
    <canvas id="plain" width="160" height="160"></canvas>
  </div>
  <div class="arrow">→</div>
  <div class="canvas-wrap">
    <div class="canvas-label">{{label_ciphertext}}</div>
    <canvas id="cipher" width="160" height="160"></canvas>
  </div>
</div>
<div id="status" class="status"></div>
<div class="legend">
  {{legend_block_size}}
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; }
.controls { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin-bottom: .75rem; }
.btn-group { display: flex; gap: .4rem; }
.mode-row { display: flex; align-items: center; gap: .5rem; font-size: .88rem; }
.label { font-weight: 600; color: #444; }
.radio { display: flex; align-items: center; gap: .25rem; cursor: pointer; font-size: .88rem; }
button { font: 600 13px system-ui; padding: .4rem .85rem; border: 1.5px solid #1d3557;
         background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button.active { background: #e63946; border-color: #c92f3c; }
button:disabled { opacity: .45; cursor: default; }
.canvases { display: flex; align-items: center; gap: .75rem; margin: .5rem 0; }
.canvas-wrap { display: flex; flex-direction: column; align-items: center; gap: .3rem; }
.canvas-label { font-size: .78rem; font-weight: 700; color: #555; letter-spacing: .04em; text-transform: uppercase; }
canvas { border: 1.5px solid #b0bcc8; border-radius: 4px; cursor: crosshair; image-rendering: pixelated; display: block; }
.arrow { font-size: 1.6rem; color: #888; }
.status { font-size: .88rem; font-weight: 600; min-height: 1.3em; margin: .4rem 0; color: #1a6e35; }
.status.warn { color: #c92f3c; }
.legend { font-size: .76rem; color: #888; margin-top: .3rem; }
// Code not found

ECB (Electronic Codebook): each 4×4 block of pixels is encrypted independently. Identical blocks → identical ciphertext. The pattern leaks.

CTR (Counter): a counter value is encrypted and XORed with each block of plaintext. Every block gets a unique keystream — the output looks uniformly random regardless of the input. This is how a block cipher becomes a stream cipher.

The lesson: a secure cipher in a broken mode is a broken cipher.

The Real Complexity

Each mode makes a different security promise — and breaks in a different way when misused.

ECB — Electronic Codebook (never use it for real data): Encrypt each block independently. Identical plaintext blocks produce identical ciphertext blocks. The mode is not semantically secure: an attacker who can see which blocks are the same learns structure about the plaintext without breaking AES at all. The ECB penguin is the canonical demonstration. ECB is only appropriate for encrypting a single block.

CBC — Cipher Block Chaining (widely used, widely broken): Each plaintext block is XORed with the previous ciphertext block before encryption. This erases the block-repetition problem, but introduces padding. Because block ciphers require fixed-size blocks, you must pad the last block. That padding, combined with decryption APIs that reveal whether padding is correct, enables the padding oracle attack: an attacker who can submit ciphertexts and observe whether decryption succeeds can decrypt any CBC ciphertext one byte at a time without knowing the key. POODLE (SSL 3.0, 2014) and BEAST (TLS 1.0, 2011) are real-world SSL/TLS exploits built on exactly this weakness.

CTR — Counter Mode (excellent when used correctly): Encrypt a counter that increments with each block and XOR the result with the plaintext. No padding needed; the cipher is used only for encryption (not decryption); and blocks can be processed in parallel. The one critical requirement: never reuse a (key, nonce) pair. If two messages are encrypted with the same nonce, XORing the two ciphertexts cancels the keystream and reveals the XOR of the two plaintexts — which is usually enough to recover both. Project Nayuki and others have published practical attacks on nonce-reusing CTR. See also lattice-based cryptography for attacks on systems that share similar structural weaknesses.

GCM — Galois/Counter Mode (the current standard): CTR encryption plus a polynomial authentication tag over the ciphertext. GCM is authenticated encryption with associated data (AEAD): it simultaneously guarantees confidentiality and that the ciphertext has not been tampered with. It is the mode mandated in TLS 1.3, SSH, and modern HTTPS. The weakness: if the nonce is ever reused, the authentication key is revealed and an attacker can forge arbitrary authenticated ciphertexts. Nonce misuse resistance is an active research area (e.g., AES-GCM-SIV). GCM's provable security rests on the hardness of the discrete logarithm problem over a binary field.

The progression ECB → CBC → CTR → GCM is also the history of cryptographers learning that confidentiality and integrity must be provided together. Encrypting without authenticating (CBC without a MAC) has been broken over and over.

Where It Matters

Block cipher modes are the working layer of almost every security protocol in use today:

  • TLS 1.3 and HTTPS: the only symmetric cipher suites allowed are AEAD modes — AES-128-GCM, AES-256-GCM, and ChaCha20-Poly1305. CBC was removed entirely because padding oracle attacks made it impossible to implement safely. Every time you see the padlock in your browser, AES-GCM is running.
  • Disk and file encryption: BitLocker (Windows) and FileVault (macOS) use XTS-AES, a tweaked mode designed so that identical plaintext blocks at different disk positions encrypt differently. Full-disk encryption with ECB would leak which sectors have identical content.
  • Secure messaging: Signal and WhatsApp use the Double Ratchet algorithm, which derives fresh keys for every message and uses AES-CBC or ChaCha20 (a stream cipher). The ratchet ensures nonce reuse is structurally impossible.
  • Database-level encryption: transparent database encryption frequently used ECB or CBC in early implementations. A 2015 paper showed that CBC-encrypted database columns could be partially sorted and searched by an attacker, leaking query patterns.
  • Breaking real systems: the Wired Equivalent Privacy protocol (WEP, 2001–2004) used RC4 in a mode that reused key streams — effectively the CTR nonce-reuse catastrophe. WPA2's TKIP had the same issue. Both were broken in minutes with commodity hardware. POODLE (Google Security Team, 2014) exploited CBC padding in SSL 3.0 to decrypt HTTPS cookies.

The practical lesson mirrors what we find in factoring and RSA: the hard mathematical core is rarely what breaks. The failure is in how the primitive is composed and deployed.

Conclusion

AES has never been broken. But ECB made a penguin visible in ciphertext. CBC gave attackers the POODLE. CTR handed them XOR-of-plaintexts the moment a nonce was recycled. The cipher was fine; the mode was not.

The arc from ECB to GCM is the story of the cryptography community slowly learning that encryption without authentication is not encryption — it is a locked door with a letterbox big enough to reach through. TLS 1.3 settled the argument by banning every non-AEAD mode outright.

The next time you read that a system "uses AES-256", ask which mode. The key size is the least important detail in the sentence.

Share this article

Pick a channel — or use your device's native share sheet.

Comments

Loading comments...

https://www.kipuhub.com/en/article/block-cipher-modes/Content licensed under CC BY-NC 4.0.