AES ECB vs CBC, shown at the byte level

Updated 2026-08-09

Run the same 16 bytes through AES twice with the same key and you get the same 16 bytes out, every time. AES is a deterministic permutation over 16-byte blocks — that is the whole design. Everything worth knowing about choosing between ECB and CBC follows from that one sentence, and the fastest way to internalize it is to encrypt a repeating plaintext in both modes and read the hex side by side.

The experiment below uses nothing but OpenSSL with a raw hex key, so there is no key-derivation magic to obscure what the cipher itself is doing. Every ciphertext byte below came out of a command you can rerun.

The experiment: one plaintext block, repeated three times

The plaintext is the 16-byte string ATTACK-AT-DAWN!! repeated three times — 48 bytes, three identical blocks. Encrypt it under the same AES-128 key in ECB, then in CBC with an explicit IV:

Encrypting the same 48 bytes in both modes
PT=$(python3 -c "print('ATTACK-AT-DAWN!!'*3, end='')")
KEY=00112233445566778899aabbccddeeff
IV=0102030405060708090a0b0c0d0e0f10

printf '%s' "$PT" | openssl enc -aes-128-ecb -K $KEY            | xxd -p -c 16
printf '%s' "$PT" | openssl enc -aes-128-cbc -K $KEY -iv $IV    | xxd -p -c 16
BlockECB ciphertext (hex)CBC ciphertext (hex)
0d3f3862cc4e86f4c8228975c4d4fc2d8909352013762568c26b4e794808665a6
1d3f3862cc4e86f4c8228975c4d4fc2d865d9e208b8aaf8d9d2d4d01ec2abf5c3
2d3f3862cc4e86f4c8228975c4d4fc2d863f40b1b70ba080615acbc9c05286392
3 (padding)00657ea140655a44782747705d422fadd36605a0696500799a6beca57fd1466f

That is the entire argument against ECB, in one table. Three identical plaintext blocks produce three identical ciphertext blocks. An observer who cannot decrypt a single byte still learns that positions 0-15, 16-31, and 32-47 of your plaintext are equal. CBC produces four unrelated-looking blocks from the same input.

Why ECB leaks even though AES itself is fine

Nothing here breaks AES. The key is not recoverable, and no individual block is decryptable. What leaks is equality: ECB is a codebook, mapping each distinct plaintext block to one fixed ciphertext block per key. Any structure in the plaintext coarser than 16 bytes survives encryption as visible structure in the ciphertext. The classic demonstration is encrypting a bitmap in ECB mode — the ciphertext bytes are garbage, but the image outline is still visible because regions of identical pixels become regions of identical blocks.

The same failure shows up in less photogenic places: a column of encrypted fields where two users with the same value get the same ciphertext, tokens where a fixed header block is identical across all messages, or an attacker splicing blocks from one message into another (in ECB, blocks are independently valid, so cut-and-paste attacks work at 16-byte granularity). A longer key changes none of this — AES-256 in ECB repeats exactly the same way, because the leak comes from determinism, not key size.

CBC chaining, and what the IV is actually for

CBC fixes the repetition by mixing each plaintext block with the previous ciphertext block before encryption: C[i] = AES(P[i] XOR C[i-1]), with the IV standing in as C[-1] for the first block. Since every ciphertext block depends on all blocks before it, identical plaintext blocks land in different chaining states and encrypt differently — which is exactly what the table above shows.

The IV is the only source of variation between two messages, and that is why reusing it matters. Encrypt two messages that share a prefix under the same key and IV and the shared prefix is visible for as long as the messages agree:

IV reuse leaks common prefixes (same key, same IV, two messages)
m1 = "user=alice;role=user............"   # 32 bytes
m2 = "user=alice;role=admin..........."   # 32 bytes, same first block

m1 block 0: 388b7117befe3c9aa6e251d73fb59e0c
m2 block 0: 388b7117befe3c9aa6e251d73fb59e0c   <- identical
m1 block 1: cef566a8bf2dda3711412c3b3890834d
m2 block 1: 160569d11957a96b935d33cf15531ace   <- diverges after first difference

Both first blocks encrypt identically because key, IV, and first 16 plaintext bytes all match — the chaining state only diverges once the plaintexts do. An observer learns that both messages start with the same 16 bytes, which in a protocol with predictable prefixes (user=alice;role=) is a real leak. The rule: a CBC IV must be unpredictable and fresh per message. A counter or a fixed IV degrades CBC toward ECB-like behavior at the message-prefix level.

Padding: why 48 bytes came back as 64

Both ciphertexts above are 64 bytes for a 48-byte input. AES only processes whole 16-byte blocks, so implementations pad with PKCS#7: append N bytes each of value N to reach a block boundary. When the plaintext is already block-aligned, a full block of sixteen 0x10 bytes is appended anyway — otherwise the decryptor could not tell payload from padding. That is block 3 in the table. It also explains a familiar debugging moment: decrypting with the wrong key usually surfaces as a padding error, not a wrong-key error, because the garbage plaintext almost never ends in valid PKCS#7 — one more reason libraries report a single generic decryption failure.

Where GCM fits

Neither ECB nor CBC provides integrity. CBC ciphertext is malleable — flipping bit k in block i flips bit k of plaintext block i+1 — and systems that decrypt attacker-supplied CBC data and reveal whether padding was valid have historically been broken by padding-oracle attacks. The modern default is an AEAD mode, usually AES-GCM: internally a counter mode, so no padding at all, plus an authentication tag that makes any ciphertext tampering detectable before you touch the plaintext. Its sharp edge is nonce reuse — repeating a nonce under the same key in GCM is far worse than reusing a CBC IV, compromising both confidentiality and the authentication key. Practical guidance: use GCM (or another AEAD) for anything new; use CBC only for compatibility, with a random IV and a MAC over the ciphertext; use ECB for approximately nothing beyond building-block experiments like this one.

Reproducing this site's AES tool with OpenSSL

The AES tool on this site (crypto-js under the hood) offers exactly ECB and CBC with PKCS#7 padding at 128/192/256-bit key sizes — there is no GCM option, so treat it as an interop and learning tool, not a place to protect real secrets. Two conventions to know before comparing its output with another system. First, your key string is used as raw ASCII bytes, right-padded with the character 0 (0x30) to the key length — the key secret at 256 bits is really the 32 bytes of secret00000000000000000000000000. Second, in CBC mode the IV field is optional and defaults to sixteen zero bytes, which makes output deterministic — convenient for reproducible debugging, and exactly the IV-reuse situation described above, so never carry that default into production code. The output is plain Base64 of the raw ciphertext with no OpenSSL Salted__ header, because the key is passed directly rather than derived from a passphrase.

Reproducing the tool's output with OpenSSL (key 'secret', 256-bit, CBC, default IV)
# tool output for plaintext 'hello': 8B604H5vGkpDMbesqEZWUg==
printf 'hello' | openssl enc -aes-256-cbc \
  -K $(python3 -c "print('secret'.ljust(32,'0').encode().hex())") \
  -iv 00000000000000000000000000000000 | xxd -p
# -> f01eb4e07e6f1a4a4331b7aca8465652  (= base64 8B604H5vGkpDMbesqEZWUg==)

If your ciphertext refuses to decrypt elsewhere, the mismatch is almost always one of: the zero-padding key convention, the zero IV, or the missing passphrase KDF (openssl -k derives a key and emits the Salted__ header; the tool and openssl -K do not). The hex converter and Base64 tool on this site are useful for inspecting the raw blocks the way this article does, and the DES and Triple DES tools follow the same conventions if you are studying older 8-byte-block ciphers.

Questions people ask

Why does OpenSSL output sometimes start with 'Salted__'?

That header appears when you use a passphrase (-k), which makes OpenSSL derive the key and prepend an 8-byte salt marker. With a raw hex key (-K) and explicit IV there is no derivation and no header — which matches what this site's AES tool emits.

Is CBC considered broken?

No — with a random per-message IV and a MAC applied over the ciphertext, CBC is still sound, and it remains everywhere in legacy systems. But it is unforgiving: IV misuse leaks prefixes and missing integrity checks enable padding-oracle attacks, which is why AEAD modes like GCM are the default recommendation for new designs.

Does switching to AES-256 fix the ECB repetition problem?

No. The repetition comes from ECB's determinism — identical plaintext blocks always map to identical ciphertext blocks under a given key — and that holds at any key size. The fix is a mode with per-message randomness (CBC with a fresh IV, or an AEAD mode), not a longer key.

Why is my ciphertext exactly one block longer than my plaintext?

PKCS#7 padding always adds at least one byte, so a block-aligned plaintext gains a full 16-byte padding block. A 48-byte input becoming 64 bytes of ciphertext is normal in both ECB and CBC.

Try it yourself