Skip to content

Instantly share code, notes, and snippets.

@pqlx
Created January 1, 2021 15:22
Show Gist options
  • Save pqlx/1785988321fc5003f543d22232387de0 to your computer and use it in GitHub Desktop.
Save pqlx/1785988321fc5003f543d22232387de0 to your computer and use it in GitHub Desktop.
Adaptive chosen plaintext attack for block ciphers using the electronic codebook (ECB) mode of operation. Allows you to bruteforce any suffix to the plaintext in polynomial time
def bruteforce_suffix(encryption_routine, block_size=16, bruteforce_space=bytes(range(0, 0x100)), debug=False):
def _to_blocks(text):
blocks = []
for i in range(0, len(text), block_size):
blocks.append(text[i:i+block_size])
return blocks
encrypt = encryption_routine
witness = encrypt(b"A" * block_size)
surplus = len(witness) - block_size
if surplus <= 0:
raise Exception("No surplus detected, so no suffix to bruteforce. Double check your encryption routine..")
if len(encrypt(b"A" * (block_size * 2))) != len(witness) + block_size:
raise Exception("Ciphertext of two blocks does not have the same length as ciphertext of a single block PLUS block size")
suffix_length_range = (surplus - block_size, surplus - 1)
print(f"Suffix length in {suffix_length_range}")
# Now check how many bytes we can scrape off..
suffix_length = None
for i in range(1, block_size):
# append extra block.. not necessarily needed but to be safe
expected_length = suffix_length_range[0] + 2 * block_size
plaintext_length = 2 * block_size - i
ciphertext_length = len(encrypt(b"A" * plaintext_length))
if ciphertext_length == expected_length:
suffix_length = suffix_length_range[1] - (block_size - i)
break
assert suffix_length
print(f"Determined suffix length to be: {suffix_length}")
suffix = b""
mirror_block = bytearray(b"\x00" * (block_size - 1))
for i in range(1, suffix_length + 1):
rounded_up = ((suffix_length // block_size) + 1) * block_size
safe_block = block_size * b"A"
found = False
for b in bruteforce_space:
guess_mirror_block = mirror_block + bytes([b])
pad_blocks = b"\x00" * (rounded_up - i)
plaintext = safe_block + guess_mirror_block + pad_blocks
ciphertext = encrypt(plaintext)
if debug:
print()
print(f"safe_block = {safe_block.hex()}")
print(f"guess_mirror_block = {guess_mirror_block.hex()}")
print(f"pad_blocks = {pad_blocks.hex()}")
print()
print(f"corresponding ciphertext: {' '.join(c.hex() for c in _to_blocks(ciphertext))}")
blocks = _to_blocks(ciphertext)
if blocks[1] == blocks[1 + rounded_up // block_size]:
found = True
suffix = suffix + bytes([b])
mirror_block.pop(0)
mirror_block += bytes([b])
print(f"[+] Found value at pos {i}: {bytes([b])}")
print(f"[*] Suffix as of now: {suffix}")
print()
break
if not found:
print("Did not find...")
exit(1)
#exit(1)
print(f"Found suffix: {suffix}")
if __name__ == "__main__":
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
def encrypt_test(plaintext):
key = b"\x00" * 0x10
cipher = AES.new(key, AES.MODE_ECB)
plaintext = pad(plaintext + b"ABCDEFGHIJKLMNOPQRSTUVWXYZ", 0x10)
return cipher.encrypt(plaintext)
import string
space = string.printable.encode()
bruteforce_suffix(encrypt_test, bruteforce_space=space)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment