def attack_padding_oracle(ciphertext, oracle): """Decrypts the given ciphertext by using the padding oracle CBC encryption attack.""" plaintext = b'' # Split the ciphertext in blocks of the AES block_size (which can get it from the IV too) ciphertext_blocks = [oracle.iv] + [ciphertext[i:i + block_size] for i in range(0, len(ciphertext), block_size)] for c in range(1, len(ciphertext_blocks)): plaintext_block = b'' # This is the part of plaintext corresponding to each ciphertext block # Take each character of the ciphertext block (starting from the last one) # and decrypt it by forcing the previous block as IV. for i in range(block_size - 1, -1, -1): # The padding len for the current character will depend on how many characters of this # block (starting from the right), we have already decrypted. padding_len = len(plaintext_block) + 1 # Find each possible character which gives us a correct padding possible_last_bytes = [] for j in range(256): # Create a IV with the guessed character j forced_iv = create_forced_previous_block(ciphertext_blocks[c - 1], j, padding_len, plaintext_block) # If the guessed character j gave us a working padding, save it as one of the candidates if oracle.decrypt_and_check_padding(ciphertext_blocks[c], forced_iv) is True: possible_last_bytes += bytes([j]) # In case of ambiguity, if we found more than one candidate, we can choose the best by trying # to force the next character too. # # This is useful because, for example, if we were trying to find the last character # of this plaintext (which was already padded): # # 123456789012/x04/x04/x04/x04 # # There would be two possible last characters that form a valid padding (/x01 and /x04). # However if we try the next character too, we can easily choose the correct one. if len(possible_last_bytes) != 1: for byte in possible_last_bytes: for j in range(256): forced_iv = create_forced_previous_block(ciphertext_blocks[c - 1], j, padding_len + 1, bytes([byte]) + plaintext_block) # If we manage to get a valid padding, then it's very likely that this # candidate is the one that we want. So exclude the others and exit the loop. if oracle.decrypt_and_check_padding(ciphertext_blocks[c], forced_iv) is True: possible_last_bytes = [byte] break # Got the new byte of the plaintext corresponding to the block we are decrypting, # add it on top of the decrypted text. plaintext_block = bytes([possible_last_bytes[0]]) + plaintext_block # Add the block we have decrypted to the final plaintext plaintext += plaintext_block # Return the unpadded plaintext bytes (in base 64) return pkcs7_unpad(plaintext)
def aes_ecb_decrypt(data, key): """Decrypts the given AES-ECB encrypted data with the given key. The un-padding part has been added to support the use that I will make of this method on future challenges (for the sake of this challenge it's not needed). """ cipher = AES.new(key, AES.MODE_ECB) return pkcs7_unpad(cipher.decrypt(data))
def main(): """Approach: 1) Find the block_length and the encryption mode 2) Decrypt byte-by-byte the mysterious message """ secret_padding = b64decode("Um9sbGluJyBpbiBteSA1LjAKV2l0aCBteSByYWctdG9wIGRvd24gc28gbXkgaGF" "pciBjYW4gYmxvdwpUaGUgZ2lybGllcyBvbiBzdGFuZGJ5IHdhdmluZyBqdXN0IH" "RvIHNheSBoaQpEaWQgeW91IHN0b3A/IE5vLCBJIGp1c3QgZHJvdmUgYnkK") oracle = ECBOracle(secret_padding) discovered_secret_padding = byte_at_a_time_ecb_decryption_simple(oracle) # Check if the attack works correctly assert pkcs7_unpad(discovered_secret_padding) == secret_padding
def aes_cbc_decrypt(data, key, iv, unpad=True): """Decrypts the given AES-CBC encrypted data with the given key and iv. Returns the unpadded decrypted message when unpad is true, or keeps the plaintext padded when unpad is false. """ plaintext = b'' prev = iv # Process the decryption block by block for i in range(0, len(data), AES.block_size): curr_ciphertext_block = data[i:i + AES.block_size] decrypted_block = aes_ecb_decrypt(curr_ciphertext_block, key) plaintext += xor_data(prev, decrypted_block) prev = curr_ciphertext_block # Return the plaintext either unpadded or left with the padding depending on the unpad flag return pkcs7_unpad(plaintext) if unpad else plaintext
def malicious_g_attack(): """Simulates the break of Diffie-Hellman with negotiated groups by using malicious 'g' parameters.""" p = DiffieHellman.DEFAULT_P for g in [1, p, p - 1]: # Step 1: the MITM changes the default g sent by Alice to Bob with a forced value alice = DiffieHellman() bob = DiffieHellman(g=g) # Step 2: Bob receives this forced g and sends an ACK to Alice # Step 3: Alice computes A and sends it to the MITM (thinking of Bob) A = alice.get_public_key() # Step 4: Bob computes B and sends it to the MITM (thinking of Alice) B = bob.get_public_key() # Step 5: Alice sends her encrypted message to Bob (without knowledge of MITM) _msg = b'Hello, how are you?' _a_key = unhexlify(sha1(str( alice.get_shared_secret_key(B)).encode()))[:16] _a_iv = Random.new().read(AES.block_size) a_question = aes_cbc_encrypt(_msg, _a_key, _a_iv) + _a_iv # Step 6: Bob receives the message sent by Alice (without knowing of the attack) # However, this time Bob will not be able to decrypt it, because (if I understood the # challenge task correctly) Alice and Bob now use different values of g. # Step 7: the MITM decrypts the Alice's question mitm_a_iv = a_question[-AES.block_size:] # When g is 1, the secret key is also 1 if g == 1: mitm_hacked_key = unhexlify(sha1(b'1').encode())[:16] mitm_hacked_message = aes_cbc_decrypt(a_question[:-AES.block_size], mitm_hacked_key, mitm_a_iv) # When g is equal to p, it works the same as in the S5C34 attack (the secret key is 0) elif g == p: mitm_hacked_key = unhexlify(sha1(b'0').encode())[:16] mitm_hacked_message = aes_cbc_decrypt(a_question[:-AES.block_size], mitm_hacked_key, mitm_a_iv) # When g is equal to p - 1, the secret key is (-1)^(ab), which is either (+1 % p) or (-1 % p). # We can try both and later check the padding to see which one is correct. else: for candidate in [str(1).encode(), str(p - 1).encode()]: mitm_hacked_key = unhexlify(sha1(candidate).encode())[:16] mitm_hacked_message = aes_cbc_decrypt( a_question[:-AES.block_size], mitm_hacked_key, mitm_a_iv, unpad=False) if is_pkcs7_padded(mitm_hacked_message): mitm_hacked_message = pkcs7_unpad(mitm_hacked_message) break # Check if the attack worked assert _msg == mitm_hacked_message