Created
January 1, 2021 15:22
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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