Padding OracleCBC Chosen-Ciphertext Attack — Full Walkthrough
AES-CBCPKCS#7Chosen-CiphertextVaudenay 2002
Why this matters:
The padding oracle attack has broken ASP.NET, TLS, and dozens of other systems.
One bit of information — valid or invalid padding — is enough to decrypt
any CBC ciphertext without the key.
CBC Mode and PKCS#7 Padding
AES-CBC decrypts each block as P[i] = AES⁻¹(C[i]) ⊕ C[i−1].
The last block must end with valid PKCS#7 padding. An implementation that
tells you whether padding is valid — even just via an error code —
is a padding oracle.
CBC Decryption Flow
The padding check at the end is the oracle. An HTTP 200 vs 500, a TLS alert code,
or even a measurable timing difference — all are sufficient to mount the full attack.
PKCS#7 Padding Rules
If n bytes of padding are needed, all n bytes must equal n.
A full block of padding (n = 16) is always appended if plaintext fits exactly.
Valid Padding
Invalid Padding
The Oracle
HTTPHTTP 200 = ValidvsHTTP 500 = Invalid
TLSHandshake Alert = ValidvsBad Record MAC = Invalid
The oracle need not be explicit. Any distinguishable behavior
(response time, error message content, error code) leaks one bit per query.
That one bit, iterated, decrypts everything.
The padding oracle attack recovers one plaintext byte at a time.
Starting with the last byte: probe all 256 values of C[n−1][15] until
the oracle returns "valid 0x01 padding". Then:
I[15] = probe ⊕ 0x01 and P[15] = I[15] ⊕ C[n−1][15].
1
Modify last byte of C[n−1] — try all 256 values (0x00–0xFF)
2
Submit modified (C[n−1]′, C[n]) to the oracle
3
Find the value that produces valid 0x01 padding: AES⁻¹(C[n]) ⊕ C[n−1]′ = 0x01
4
XOR with 0x01 to recover intermediate: I[15] = probe ⊕ 0x01
5
XOR I[15] with original C[n−1][15] to recover: P[15] = I[15] ⊕ C[n−1][15]
Live Attack
Any text — will be AES-CBC encrypted with a random key.
Click "Encrypt" to begin.
IV:
Ciphertext:
Byte Recovery Grid
UnknownProbingIntermediate foundRecovered
Oracle queries: 0
Full Block Recovery
Extend single-byte recovery across all 16 bytes. For byte at position j
(counting from right), set up padding 0x(16−j) by fixing up already-recovered
bytes: C[n−1]′[k] = I[k] ⊕ padByte for all known k > j.
Total: ~O(256 × 16) = 4096 oracle calls per block (worst case).
Live Block Recovery
Plaintext: "Attack at dawn!!" (exactly 16 bytes — one block).
The full block will be recovered byte-by-byte.
Click "Encrypt Target Block" to start.
Plaintext Bytes (P[n])
Intermediate (I = AES⁻¹(C[n]))
Oracle queries: 0
XOR: Intermediate ⊕ C[n−1] = Plaintext
Full Ciphertext Decryption
Apply block recovery to every ciphertext block. The IV is required for block 0.
Attack complexity: O(256 × 16 × n) where n is the
number of ciphertext blocks. Watch the total oracle query count grow — and recognize
that this attack succeeds against any CBC implementation with a padding oracle.
Full Decryption Attack
Multiple blocks recommended — see total query count grow.
Enter plaintext and click "Generate Ciphertext".
Oracle queries: 0
Note on Block 0: Recovering block 0 requires the IV.
In most real attacks, the IV is transmitted alongside the ciphertext (TLS record header,
HTTP response) — making full recovery possible. If the IV is secret, only blocks 1–n
are recoverable from the ciphertext alone.
Real-World Exploits Hall of Fame
The padding oracle attack is not theoretical. It has broken production systems
serving billions of users. These exploits share a common thread:
CBC mode + a padding oracle = full plaintext recovery.
Why TLS 1.3 Eliminated CBC
TLS 1.3 (RFC 8446, 2018) removed all CBC cipher suites. Only AEAD cipher suites
are permitted: AES-GCM, AES-CCM, and ChaCha20-Poly1305. The reason is exactly
this attack: CBC with any padding oracle — whether explicit error responses or
timing side-channels — is fundamentally broken. AEAD ciphers authenticate before
decrypting, so no padding oracle can exist.
There are several ways to stop the padding oracle attack. Only AEAD eliminates
the attack class entirely — the others reduce risk but require careful implementation.
Defense 1: Encrypt-then-MAC
Compute a MAC over the ciphertext (not the plaintext).
Verify the MAC before attempting decryption. If the MAC is invalid,
reject immediately — no decryption, no padding, no oracle.
Requires: Constant-time MAC comparison.
Timing differences in comparison can still leak information.
Encrypt (CBC)→MAC(ciphertext)→Verify MAC first→Then decrypt
Defense 2: Constant-Time Padding Validation
Process all padding bytes in constant time, regardless of where
the first invalid byte is found. Return a single valid/invalid
result only after examining the entire block.
Limitation: Eliminates timing oracle but not error-response oracles.
If the system still returns different error codes, the oracle remains.
Lucky Thirteen shows that even microsecond differences matter.
Defense 3: Use AEAD — The Definitive Fix
Recommended
AEAD (Authenticated Encryption with Associated Data) integrates encryption and
authentication into a single primitive. AES-GCM and ChaCha20-Poly1305 check the
authentication tag before any decryption. No padding is used (stream mode).
No oracle is possible because no information is revealed on failure.
Check auth tag (first!)→Reject if invalid→Decrypt (only if valid)
AES-GCM Live Demo: Tampering Rejected
Encrypt a message with AES-256-GCM (WebCrypto). Then tamper with the ciphertext
and watch authentication fail immediately — with no plaintext revealed.
Why This Matters in 2026+
CBC is still in the wild. Legacy systems — payment processors,
medical record systems, enterprise VPNs, and embedded devices — continue to use
AES-CBC. Understanding the padding oracle attack is essential for: