def main(): ciphertext, iv = get_random_ciphertext() target_plaintext = decrypt_aes_128_cbc(ciphertext, KEY, iv) print(ciphertext.encode('hex')) print(target_plaintext.encode('hex')) # Detects byte one by one with the padding oracle leaked information. decoded_message = '' previous_encrypted_block = iv num_blocks = len(ciphertext) / BLOCK_SIZE for block_idx in range(num_blocks): block = ciphertext[block_idx * BLOCK_SIZE:(block_idx + 1) * BLOCK_SIZE] print('Expected Block: %s' % target_plaintext[block_idx * BLOCK_SIZE:(block_idx + 1) * BLOCK_SIZE].encode('hex')) print('Target Block: %s' % block.encode('hex')) intermediate_decoded_last_bytes = last_byte_oracle( padding_oracle, block) print('Decoded last bytes: %s' % intermediate_decoded_last_bytes.encode('hex')) intermediate_decoded_block = block_decrypt_oracle( padding_oracle, block, intermediate_decoded_last_bytes) decoded_block = xor(intermediate_decoded_block, previous_encrypted_block) previous_encrypted_block = block print('Decoded block: %s\n' % decoded_block.encode('hex')) decoded_message += decoded_block print('Decoded message: %s' % decoded_message.decode('base64'))
def main(): keystream_length = max(len(c) for c in ciphertexts) keystream_guessed = ['\x00' for i in range(keystream_length)] longest_ciphertext = max(ciphertexts, key=lambda x: len(x)) #longest_ciphertext = ciphertexts[0] for keystream_byte_idx in range(keystream_length): for c in VALID_LETTERS: keystream_byte = single_char_xor( c, longest_ciphertext[keystream_byte_idx]) plaintext_byte_guesses = [] for ct in ciphertexts[1:]: if keystream_byte_idx >= len(ct): # Skip short ciphertexts continue plaintext_byte = single_char_xor(keystream_byte, ct[keystream_byte_idx]) plaintext_byte_guesses.append(plaintext_byte) if all(b in VALID_LETTERS for b in plaintext_byte_guesses): keystream_guessed[keystream_byte_idx] = keystream_byte break else: print('No satisfying letter...') raw_input() # Decode all of them print('\nDECODING') for idx, ct in enumerate(ciphertexts): decoded_length = min(len(ct), keystream_byte_idx + 1) truncated_keystream = ''.join(keystream_guessed)[:decoded_length] truncated_ciphertext = ct[:decoded_length] plaintext = xor(truncated_keystream, truncated_ciphertext) print('#%d: "%s"' % (idx, plaintext))
def block_decrypt_oracle(oracle, ciphertext_block, decoded_last_bytes): """ Implements the "Block Decryption Oracle" from Serge Vaudenay original paper on the exploit. Source: https://www.iacr.org/archive/eurocrypt2002/23320530/cbc02_e02d.pdf """ block_size = len(ciphertext_block) iv = '\x00' * block_size decoded_bytes = decoded_last_bytes while len(decoded_bytes) < block_size: j = block_size - 1 - len(decoded_bytes) pad_byte = chr(len(decoded_bytes) + 1) random_bytes_block = Random.new().read(block_size - len(decoded_bytes)) random_bytes_block += xor(decoded_bytes, pad_byte * len(decoded_bytes)) for i in range(0, 256): forged_block = random_bytes_block[:j] forged_block += single_char_xor(random_bytes_block[j], chr(i)) forged_block += random_bytes_block[j + 1:] forged_ciphertext = forged_block + ciphertext_block try: oracle(forged_ciphertext, iv) except ValueError as inst: # Wrong padding, go to next byte. continue # Valid padding found. new_decoded_byte = single_char_xor(forged_block[j], pad_byte) decoded_bytes = new_decoded_byte + decoded_bytes return decoded_bytes
def encrypt_aes_128_cbc(plaintext, key, iv, block_size=16): cipher = AES.new(key) ciphertexts = [] plaintext = pkcs_pad(plaintext, block_size=block_size) for block_idx in range(len(plaintext) / block_size): block = plaintext[block_idx * block_size:(block_idx + 1) * block_size] block = xor(block, iv) block = cipher.encrypt(block) ciphertexts.append(block) iv = block return ''.join(ciphertexts)
def decrypt_aes_128_cbc(ciphertext, key, iv, block_size=16): cipher = AES.new(key) plaintexts = [] for block_idx in range(len(ciphertext) / block_size): block = ciphertext[block_idx * block_size:(block_idx + 1) * block_size] next_iv = block block = cipher.decrypt(block) block = xor(block, iv) plaintexts.append(block) iv = next_iv plaintext = ''.join(plaintexts) plaintext = pkcs_unpad(plaintext) return plaintext
def padding_oracle(ciphertext, iv): """Raises exception if the padding is not valid.""" block_size = BLOCK_SIZE cipher = AES.new(KEY) plaintexts = [] for block_idx in range(len(ciphertext) / block_size): block = ciphertext[block_idx * block_size:(block_idx + 1) * block_size] next_iv = block[:] block = cipher.decrypt(block) block = xor(block, iv) plaintexts.append(block) iv = next_iv plaintext = ''.join(plaintexts) validate_pkcs_7_pad(plaintext) plaintext = pkcs_unpad(plaintext) return plaintext
def encrypt_decrypt_aes_128_ctr(key, text, nonce): """Encrypt/Decrypt the given text.""" block_size = 16 nonce = struct.pack('<Q', nonce) assert len(nonce) == block_size / 2 cipher = AES.new(key) ciphertexts = [] counter = 0 num_blocks = int(math.ceil(float(len(text)) / block_size)) for block_idx in range(num_blocks): block = text[block_idx * block_size:(block_idx + 1) * block_size] keystream = cipher.encrypt(nonce + struct.pack('<Q', counter)) keystream = keystream[:len(block)] block = xor(block, keystream) ciphertexts.append(block) counter += 1 return ''.join(ciphertexts)
def main(): injection_input = 'a;admin=true' ciphertext, iv = encrypt_input(injection_input) print('Successfully authenticated: %s' % decrypt_and_check_for_admin(ciphertext, iv)) # Corrupts the ciphertext at the place we have control on the plaintext. # NB: the prefix has a 32 bytes length. prefix_size = 32 # TODO: determine this dynamically exploit_input = '\x00' * len(injection_input) target_ciphertext, iv = encrypt_input(exploit_input) # Selects the block before the one we ant to corrupt. target_ciphertext_block = target_ciphertext[(prefix_size - BLOCK_SIZE):prefix_size] # Corrupts the ciphertext-block with the desired output after the decrypting XOR operation. corrupted_ciphertext_block = xor(target_ciphertext_block, injection_input + target_ciphertext_block[len(injection_input):]) # Recompiles the complete ciphertext to get admin access. corrupted_ciphertext = target_ciphertext[:(prefix_size - BLOCK_SIZE)] + corrupted_ciphertext_block + target_ciphertext[prefix_size:] print('Successfully authenticated: %s' % decrypt_and_check_for_admin(corrupted_ciphertext, iv))
def last_byte_oracle(oracle, ciphertext_block): """ Implements the "Last Word Oracle" from Serge Vaudenay original paper on the exploit. Source: https://www.iacr.org/archive/eurocrypt2002/23320530/cbc02_e02d.pdf """ block_size = len(ciphertext_block) iv = '\x00' * block_size random_bytes_block = Random.new().read(block_size) target_byte_index = len(random_bytes_block) - 1 for i in range(0, 256): forged_block = random_bytes_block[:target_byte_index] forged_block += single_char_xor(random_bytes_block[target_byte_index], chr(i)) forged_ciphertext = forged_block + ciphertext_block try: oracle(forged_ciphertext, iv) except ValueError as inst: # Wrong padding, go to next byte. continue # Validates if the correct padding is \x01 or \x02\x02 or \x03\x03\x03, etc. for n in range(target_byte_index, 0, -1): forged_block_copy = forged_block[:] forged_block_copy = forged_block[:(target_byte_index - n)] forged_block_copy += single_char_xor( forged_block[target_byte_index - n], '\x01') forged_block_copy += forged_block[(target_byte_index - n + 1):] forged_ciphertext = forged_block_copy + ciphertext_block try: oracle(forged_ciphertext, iv) except ValueError as inst: # Wrong padding, meaning we've detected the correct padding detected_padding = forged_block_copy[(target_byte_index - n):] return xor(detected_padding, '\x00' * len(detected_padding)) # Nothing found, the valid padding is \x01. return single_char_xor(forged_block[target_byte_index], '\x01')