Padding Oracle Attack
Cryptosystem
AES in CBC mode.
Requirements
Encrypted messages using block ciphers in CBC mode. A padding scheme like PKCS#7 or PKCS#1 v1.5. An oracle that reveals whether the padding of a decrypted ciphertext is valid or not.
Description
The Padding Oracle Attack exploits a side-channel where an attacker can repeatedly send modified ciphertexts and observe whether a padding error occurs. This leakage allows the attacker to gradually decrypt the ciphertext without knowing the key.
The attack takes advantage of how padding is validated after decryption. If an oracle (like a server) tells you whether the padding is correct or not, you can learn information about the plaintext byte-by-byte.
This attack is powerful and has affected real-world protocols like SSL/TLS, ASP.NET, and Java crypto APIs.
Example
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from Crypto.Random import get_random_bytes
BLOCK_SIZE = 16
# Victim setup
key = get_random_bytes(BLOCK_SIZE)
def encrypt(plaintext: bytes) -> tuple[bytes, bytes]:
iv = get_random_bytes(BLOCK_SIZE)
cipher = AES.new(key, AES.MODE_CBC, iv)
ciphertext = cipher.encrypt(pad(plaintext, BLOCK_SIZE))
return iv, ciphertext
def padding_oracle(iv: bytes, ciphertext: bytes) -> bool:
try:
cipher = AES.new(key, AES.MODE_CBC, iv)
unpad(cipher.decrypt(ciphertext), BLOCK_SIZE)
return True
except ValueError:
return False
# Attacker
def decrypt_block(prev_block: bytes, target_block: bytes) -> bytes:
recovered = bytearray(BLOCK_SIZE)
intermediate = bytearray(BLOCK_SIZE)
for i in range(1, BLOCK_SIZE + 1):
pad_byte = i
for guess in range(256):
prefix = bytearray(BLOCK_SIZE)
for j in range(1, i):
prefix[-j] = intermediate[-j] ^ pad_byte
prefix[-i] = guess
if padding_oracle(bytes(prefix), target_block):
intermediate[-i] = guess ^ pad_byte
recovered[-i] = intermediate[-i] ^ prev_block[-i]
break
return bytes(recovered)
def full_decrypt(iv: bytes, ciphertext: bytes) -> bytes:
blocks = [iv] + [ciphertext[i:i+BLOCK_SIZE] for i in range(0, len(ciphertext), BLOCK_SIZE)]
plaintext = b""
for i in range(1, len(blocks)):
decrypted = decrypt_block(blocks[i-1], blocks[i])
plaintext += decrypted
return unpad(plaintext, BLOCK_SIZE)
# Simulate attack
plaintext = b"Top secret message, please don't leak me."
iv, ciphertext = encrypt(plaintext)
recovered_plaintext = full_decrypt(iv, ciphertext)
print(f"Original plaintext : {plaintext}")
print(f"Recovered plaintext: {recovered_plaintext}")
Real world
- In 2010, Padding Oracle Attacks were used in the "Padding Oracle On Downgraded Legacy Encryption" (POODLE) attack on SSL 3.0.
- The ROBOT Attack The ROBOT attack exploited a padding oracle vulnerability in the RSA encryption scheme used in TLS, allowing attackers to decrypt sensitive data.