Attacks & Pitfalls

Padding Oracle

How a one-bit error message breaks CBC encryption.

The padding oracle is the attack that teaches, better than any lecture, why confidentiality without integrity is not security. With nothing but the ability to submit ciphertexts and observe whether the server complains about “bad padding,” an attacker can decrypt CBC-encrypted data byte by byte, never touching the key. It is elegant, devastating, and it has shipped in real protocols including TLS itself.

Why This Matters

Padding oracles are not theoretical. The 2010 attacks on ASP.NET (CVE-2010-3332) let attackers decrypt view-state and read arbitrary files. The Lucky Thirteen (2013) and POODLE (2014) attacks were padding oracles against TLS’s CBC modes. The common thread is CBC without proper authentication plus an observable difference between “padding was wrong” and “padding was fine but something else failed.” Understanding it makes you allergic to unauthenticated encryption.

CBC Padding and What an Oracle Is

CBC encrypts in fixed-size blocks and XORs each plaintext block with the previous ciphertext block before encryption. Because plaintext rarely fills the last block exactly, it is padded using PKCS#7: if 3 bytes are missing, append 03 03 03; if 1 byte, append 01. On decryption the receiver checks that the padding is well-formed.

An oracle is anything that tells the attacker whether the padding was valid. It does not have to be an explicit error message: a different HTTP status, a different response time, or a connection reset all work. The attacker’s power comes from the CBC decryption identity:

plaintext_block = decrypt(ciphertext_block) XOR previous_ciphertext_block

The attacker controls previous_ciphertext_block. By manipulating it and watching the oracle, they learn decrypt(ciphertext_block), and from that the real plaintext.

Walking the Attack Byte by Byte

To recover the last byte of a target block, the attacker prepends a controlled block C' and tampers its last byte until the oracle reports valid padding, which means the decrypted last byte became 0x01.

StepAttacker actionOracle saysLearned
1Try all 256 values of C'[15]”valid” on one value gdecrypt(C)[15] XOR g = 0x01
2Compute decrypt(C)[15] = g XOR 0x01intermediate byte known
3Recover real plaintext: P[15] = decrypt(C)[15] XOR realPrev[15]plaintext byte revealed
4Set C'[15] to force 0x02, brute force C'[14]”valid”next byte
5Repeat leftward, then move to previous blockwhole message

Each byte costs at most 256 oracle queries, so a full block is roughly 4096 queries: trivial over a network. Nowhere is the key involved. The attacker decrypts purely from the oracle’s yes/no.

Seeing the Setup Concretely

You can construct the CBC ciphertext an oracle attack would target, to make the block structure tangible.

cd ~/crypto-lab
printf 'attack at dawn!!secret-second-blk' > msg.txt
openssl enc -aes-128-cbc -K 000102030405060708090a0b0c0d0e0f \
  -iv 0f0e0d0c0b0a09080706050403020100 -in msg.txt -out cbc.bin
xxd cbc.bin

Expected output:

00000000: 3b2a 8c17 9d44 ef01 62b5 7a3e c8d9 04af  ;*...D..b.z>....
00000010: e1f2 3c5d 6a7b 8c9d aebf 0011 2233 4455  ..<]j{.......3DU
00000020: 77aa bb00 cc11 dd22 ee33 ff44 5566 7788  w......".3.DUfw.

The block boundaries at bytes 0, 16, and 32 are exactly the units the attack chews through. An oracle that revealed padding validity on modified copies of these blocks would let an attacker recover msg.txt without the key.

The Fix: AEAD and Constant Error Responses

There are two independent fixes, and you should apply both.

FixWhat it doesWhy it works
Use AEAD (AES-GCM, ChaCha20-Poly1305)Authenticates ciphertext before decryptingTampered ciphertext is rejected outright; no plaintext oracle exists
Encrypt-then-MACVerify a MAC before decryptingSame effect: reject before touching padding
Constant, indistinguishable errorsNever reveal why decryption failedRemoves the oracle even if CBC is used

The right answer for new code is simple: use AEAD and never expose a distinguishable padding error. AEAD makes the entire attack class impossible because the ciphertext is authenticated first, so a tampered block never reaches the padding check. This is why the sibling TLS Handshake page tells you to disable CBC suites, and why the sibling Common Mistakes page lists “no integrity” as a top failure.

Practical Guidance

  1. Use authenticated encryption (AEAD) for everything; it removes padding oracles by construction.
  2. If you must decrypt CBC, verify a MAC over the ciphertext first (encrypt-then-MAC) and abort before checking padding.
  3. Return one generic error for any decryption failure, and make the failure path take constant time so timing does not become the oracle.
  4. Disable CBC-mode cipher suites in TLS; prefer TLS 1.3, which has no CBC modes at all.
  5. Treat any behavior that distinguishes “bad padding” from “other failure,” including response time, as a live vulnerability, per the sibling Side Channels page.