diff --git a/attack.py b/attack.py new file mode 100644 index 0000000000000000000000000000000000000000..607a38e4655114b88c20a3a65239d6f1e41c866e --- /dev/null +++ b/attack.py @@ -0,0 +1,75 @@ +import time + +from oracle import encrypt, is_padding_ok, BLOCK_SIZE + + +def attack(ciphertext): + + pause_print("Attacking. . .\n. . .", 2000) + split_string = lambda x, n: [x[i:i + n] for i in range(0, len(x), n)] + blocks = split_string(ciphertext, BLOCK_SIZE) + + guessed_clear = b'' + for block_n in range(len(blocks) - 1, 0, -1): # build pair of blocks starting from end of message + spliced_ciphertext = blocks[block_n - 1] + blocks[block_n] + + decoded_bytes = b'?' * BLOCK_SIZE # output of block cipher decoding values + + # GET VALUE OF SECRET BYTE byte + for byte in range(BLOCK_SIZE - 1, -1, -1): + padding_length = BLOCK_SIZE - byte + + # Build hacked ciphertext tail with values to obtain desired padding + hacked_ciphertext_tail = b'' + for padder_index in range(1, padding_length): + hacked_ciphertext_tail += bytearray.fromhex( + '{:02x}'.format(padding_length ^ decoded_bytes[byte + padder_index])) + + for i in range(0, 256): + attack_str = bytearray.fromhex('{:02x}'.format((i ^ spliced_ciphertext[byte]))) + hacked_ciphertext = spliced_ciphertext[:byte] + attack_str + hacked_ciphertext_tail + \ + spliced_ciphertext[byte + padding_length:] + + if is_padding_ok(hacked_ciphertext): + + test_correctness = hacked_ciphertext[:byte - 1] + bytearray.fromhex( + '{:02x}'.format((1 ^ hacked_ciphertext[byte]))) + hacked_ciphertext[byte:] + if not is_padding_ok(test_correctness): + continue + + decoded_bytes = decoded_bytes[:byte] + bytearray.fromhex( + '{:02x}'.format(hacked_ciphertext[byte] ^ padding_length)) + decoded_bytes[byte + 1:] + guessed_clear = bytearray.fromhex('{:02x}'.format(i ^ padding_length)) + guessed_clear + # pause_print("Decoded bytes: {:s}".format(str(decoded_bytes)), 100) + pause_print("Guessed bytes: {:s}".format(str(guessed_clear)), 100) + pause_print(". . .", 400) + break + + return guessed_clear[:-guessed_clear[-1]] # remove padding! + + +def test_the_attack(): + plaintext = input("Enter plaintext: ").encode("UTF-8") + + pause_print("Plaintext: {:s}".format(str(plaintext)), 500) + pause_print("Length: {:d}".format(len(plaintext)), 500) + encrypted = encrypt(plaintext) + pause_print("Ciphertext: {:s}".format(str(encrypted)), 700) + pause_print("Block size: {:d}".format(BLOCK_SIZE)) + padding_length = BLOCK_SIZE - (len(plaintext) % BLOCK_SIZE) + pause_print("Padding should be: {:d} bytes of {:s}" + .format(padding_length, str(bytes([padding_length]))), 2000) + + cracked_ct = attack(encrypted) + pause_print("Remove {:d} paddings and you got:\n{:s}".format(padding_length, str(cracked_ct))) + + assert (cracked_ct == plaintext) + + +def pause_print(msg, ms=1500): + print(msg) + time.sleep(ms / 1000) + + +if __name__ == '__main__': + test_the_attack() diff --git a/oracle.py b/oracle.py new file mode 100644 index 0000000000000000000000000000000000000000..2e7bb239d09c33267f1eb9a594627ca6f528723c --- /dev/null +++ b/oracle.py @@ -0,0 +1,45 @@ +from Crypto.Cipher import AES +from Crypto import Random + +KEY_LENGTH = 16 # AES128 +BLOCK_SIZE = AES.block_size + +_random_gen = Random.new() +_key = _random_gen.read(KEY_LENGTH) + + +def _add_padding(msg): + pad_len = BLOCK_SIZE - (len(msg) % BLOCK_SIZE) + padding = bytes([pad_len]) * pad_len + return msg + padding + + +def _remove_padding(data): + pad_len = data[-1] + + if pad_len < 1 or pad_len > BLOCK_SIZE: + return None + for i in range(1, pad_len): + if data[-i - 1] != pad_len: + return None + return data[:-pad_len] + + +def encrypt(msg): + iv = _random_gen.read(AES.block_size) + cipher = AES.new(_key, AES.MODE_CBC, iv) + return iv + cipher.encrypt(_add_padding(msg)) + + +def _decrypt(data): + iv = data[:BLOCK_SIZE] + cipher = AES.new(_key, AES.MODE_CBC, iv) + return _remove_padding(cipher.decrypt(data[BLOCK_SIZE:])) + + +def is_padding_ok(data): + return _decrypt(data) is not None + + +if __name__ == '__main__': + print("RUN attack.py!!")