def test_parity(): key = key_1024 print("\nTest: parity") plaintext1 = bytes(b"Some plaintext ") + random_bytes(10) + bytes( b" anything can it be") plaintext2 = bytes(b"Some plaintext ") + random_bytes(10) + bytes( b" anything can it be2") ciphertext1 = h2b( subprocess.check_output([ "python", rsa_oracles_path, "encrypt", key.identifier, b2h(plaintext1) ]).strip().decode()) ciphertext2 = h2b( subprocess.check_output([ "python", rsa_oracles_path, "encrypt", key.identifier, b2h(plaintext2) ]).strip().decode()) key.texts.append({'cipher': b2i(ciphertext1)}) key.texts.append({'cipher': b2i(ciphertext2)}) msgs_recovered = parity(parity_oracle, key.publickey()) assert msgs_recovered[0] == b2i(plaintext1) assert msgs_recovered[1] == b2i(plaintext2) key.clear_texts()
def test_bit_flipping(): print("Test: cbc.bit_flipping(ciphertext=ciphertext[-2*AES.block_size:]," "plaintext=add_padding(plaintext)[-AES.block_size:],\nwanted_last_block=wanted, block_size=AES.block_size)") plaintext = bytes(b"money=10000&userdata=whateverdata%20huehuehue%20spam%20and%20eggs") wanted = add_padding(bytes(b'&admin=true')) ciphertext = h2b(subprocess.check_output(['python', cbc_oracles_path, 'encrypt', b2h(plaintext)]).strip().decode()) fake_cipher = cbc.bit_flipping(ciphertext=ciphertext[-2*AES.block_size:], plaintext=add_padding(plaintext)[-AES.block_size:], wanted=wanted, block_size=AES.block_size) fake_cipher = ciphertext[-AES.block_size*2:] + fake_cipher decrypted = h2b(subprocess.check_output(['python', cbc_oracles_path, 'decrypt', b2h(fake_cipher)]).strip().decode()) assert decrypted[-len("&admin=true"):] == bytes(b"&admin=true")
def fake_ciphertext(new_plaintext, padding_oracle=None, decryption_oracle=None, block_size=16): """Make ciphertext that will decrypt to given plaintext Give padding_oracle or decryption_oracle (or both) Args: new_plaintext(string): with padding padding_oracle(function/None) decryption_oracle(function/None): maximum one block to decrypt block_size(int) Returns: fake_ciphertext(string): fake ciphertext that will decrypt to new_plaintext """ _check_oracles(padding_oracle=padding_oracle, decryption_oracle=decryption_oracle, block_size=block_size) if block_size % 8 != 0: log.critical_error("Incorrect block size: {}".format(block_size)) log.info("Start fake ciphertext") ciphertext = bytes(b'A' * (len(new_plaintext) + block_size)) # prepare blocks blocks = chunks(ciphertext, block_size) new_pl_blocks = chunks(new_plaintext, block_size) if len(new_pl_blocks) != len(blocks) - 1: log.critical_error( "Wrong new plaintext length({}), should be {}".format( len(new_plaintext), block_size * (len(blocks) - 1))) new_ct_blocks = list(blocks) # add known plaintext for count_block in range(len(blocks) - 1, 0, -1): """ Every block, modify block[count_block-1] to set block[count_block] """ log.info("Block no. {}".format(count_block)) ciphertext_to_decrypt = bytes(b''.join(new_ct_blocks[:count_block + 1])) original_plaintext = decrypt(ciphertext_to_decrypt, padding_oracle=padding_oracle, decryption_oracle=decryption_oracle, block_size=block_size, amount=1, is_correct=False) log.info("Set block no. {}".format(count_block)) new_ct_blocks[count_block - 1] = xor(blocks[count_block - 1], original_plaintext, new_pl_blocks[count_block - 1]) fake_ciphertext_res = bytes(b''.join(new_ct_blocks)) log.success("Fake ciphertext(hex): {}".format(b2h(fake_ciphertext_res))) return fake_ciphertext_res
def test_fake_ciphertext_decryption_oracle(amount=5): for _ in range(amount): new_plaintext = random_bytes(randint(1, 10)) new_plaintext_padded = add_padding(new_plaintext, block_size) print("Test small: cbc.fake_ciphertext(new_plaintext_padded, decryption_oracle=decryption_oracle)") new_ciphertext = cbc.fake_ciphertext(new_plaintext_padded, decryption_oracle=decryption_oracle) decrypted = h2b(subprocess.check_output( ['python', cbc_oracles_path, 'decrypt', b2h(new_ciphertext)]).strip().decode()) assert decrypted == new_plaintext for _ in range(amount): new_plaintext = random_bytes(randint(10, 50)) new_plaintext_padded = add_padding(new_plaintext, block_size) print("Test large: cbc.fake_ciphertext(new_plaintext_padded, decryption_oracle=decryption_oracle)") new_ciphertext = cbc.fake_ciphertext(new_plaintext_padded, decryption_oracle=decryption_oracle, padding_oracle=padding_oracle) decrypted = h2b(subprocess.check_output( ['python', cbc_oracles_path, 'decrypt', b2h(new_ciphertext)]).strip().decode()) assert decrypted == new_plaintext
def iv_as_key(ciphertext, plaintext, padding_oracle=None, decryption_oracle=None, block_size=16): """If iv is used as key, we can recover it using decryption oracle, padding oracle or known plaintext (if first ciphertext block is repeated) Args: ciphertext(string): first block must be AES.encrypt(iv xor plaintext[0]) plaintext(string): with padding padding_oracle(function/None) decryption_oracle(function/None) block_size(int) Returns: string: key (== iv) """ key = None ciphertext = chunks(ciphertext, block_size) plaintext = chunks(plaintext, block_size) try: position_second = ciphertext.index(ciphertext[0], 1) log.debug("Position of the same block as the first is {}".format( position_second)) key = xor(plaintext[0], ciphertext[position_second - 1], plaintext[position_second]) except ValueError: log.debug( "first ciphertext block is not repeated, will use decryption/padding oracle" ) if key is None: iv = bytes(b'A' * block_size) iv_xor_plaintext0 = decrypt(ciphertext[0], padding_oracle=padding_oracle, decryption_oracle=decryption_oracle, iv=iv, block_size=block_size, is_correct=False, amount=1) iv_xor_plaintext0 = xor(iv_xor_plaintext0, iv) key = xor(iv_xor_plaintext0, plaintext[0]) log.success("Key(hex): {}".format(b2h(key))) return key
def merkle_damgard(data, initial_state, compression_function): """Merkle-Damgard construction Args: data(string) initial_state(list of ints) compression_function(function) Returns: final state(string) """ state = initial_state[:] data_chunks = chunks(data, 64) log.debug("Start merkle-damgard, chunks are: {}".format(data_chunks)) for chunk in data_chunks: log.debug("Process chunk: {} with state: {}".format(b2h(chunk), state)) state = compression_function(chunk, state) return state
def test_decrypt(): global constant, prefix_len, suffix_len, secret print( "test: ecb.decrypt(encryption_oracle_aes, constant, block_size=AES.block_size)" ) constant = True for x in range(20): prefix_len = random.randint(0, 90) secret = random_bytes(random.randint(1, 90)) print("Secret to guess(hex): {}".format(b2h(secret))) guessed_secret = ecb.decrypt(encryption_oracle_aes, constant, block_size=AES.block_size) assert secret == guessed_secret guessed_secret = ecb.decrypt(encryption_oracle_des, constant, block_size=DES3.block_size) assert secret == guessed_secret
def decrypt(encryption_oracle, constant=True, block_size=16, prefix_size=None, secret_size=None, alphabet=None): """Given encryption oracle which produce ecb(prefix || our_input || secret), find secret Args: encryption_oracle(callable) constant(bool): True if prefix have constant length (secret must have constant length) block_size(int/None) prefix_size(int/None) secret_size(int/None) alphabet(string): plaintext space Returns: secret(string) """ log.debug("Start decrypt function") if not alphabet: alphabet = bytes(string.printable.encode()) if not block_size: block_size = find_block_size(encryption_oracle, constant) if constant: log.debug("constant == True") if not prefix_size or not secret_size: prefix_size, secret_size = find_prefix_suffix_size(encryption_oracle, block_size) """Start decrypt""" secret = bytes(b'') aligned_bytes = random_bytes(1) * (block_size - (prefix_size % block_size)) if len(aligned_bytes) == block_size: aligned_bytes = bytes(b'') aligned_bytes_suffix = random_bytes(1) * (block_size - (secret_size % block_size)) if len(aligned_bytes_suffix) == block_size: aligned_bytes_suffix = bytes(b'') block_to_find_position = -1 controlled_block_position = (prefix_size+len(aligned_bytes)) // block_size while len(secret) < secret_size: if (len(secret)+1) % block_size == 0: block_to_find_position -= 1 payload = aligned_bytes + aligned_bytes_suffix + random_bytes(1) + secret enc_chunks = chunks(encryption_oracle(payload), block_size) block_to_find = enc_chunks[block_to_find_position] log.debug("To guess at position {}:".format(block_to_find_position)) log.debug("Plain: " + print_chunks(chunks(bytes(b'P'*prefix_size) + payload + bytes(b'S'*secret_size), block_size))) log.debug("Encry: " + print_chunks(enc_chunks)+"\n") for guessed_char in range(256): guessed_char = bytes([guessed_char]) payload = aligned_bytes + add_padding(guessed_char + secret, block_size) enc_chunks = chunks(encryption_oracle(payload), block_size) log.debug("Plain: " + print_chunks(chunks(bytes(b'P'*prefix_size) + payload + bytes(b'S'*secret_size), block_size))) log.debug("Encry: " + print_chunks(enc_chunks)+"\n") if block_to_find == enc_chunks[controlled_block_position]: secret = guessed_char + secret log.debug("Found char, secret={}".format(repr(secret))) break else: log.critical_error("Char not found, try change alphabet. Secret so far: {}".format(repr(secret))) log.success("Secret(hex): {}".format(b2h(secret))) return secret else: log.debug("constant == False")
if plain[:2] == bytes(b'\x00\x01') and plain_hash == asn1 + hash_msg: return True return False if __name__ == '__main__': if len(sys.argv) < 4 or sys.argv[1] not in ['encrypt', 'decrypt', 'sign', 'verify', 'parity', 'verify_bleichenbacher_middle', 'verify_bleichenbacher_suffix']: print("Usage: {} encrypt|decrypt|sign|verify|parity|verify_bleichenbacher_suffix|verify_bleichenbacher_middle " \ "key hexdata [more hexdata]".format(sys.argv[0])) sys.exit(1) key = RSAKey.import_key(sys.argv[2]) if sys.argv[1] == 'encrypt': print(b2h(encrypt(h2b(sys.argv[3]), key))) elif sys.argv[1] == 'decrypt': print(b2h(decrypt(h2b(sys.argv[3]), key))) elif sys.argv[1] == 'sign': print(b2h(sign(h2b(sys.argv[3]), key))) elif sys.argv[1] == 'verify': print(verify(h2b(sys.argv[3]), h2b(sys.argv[4]), key)) elif sys.argv[1] == 'parity': print(parity_oracle(h2b(sys.argv[3]))) elif sys.argv[1] == 'verify_bleichenbacher_suffix': message = h2b(sys.argv[3])
payload = iv + payload try: decrypt(payload, iv_as_key) except BadPadding as e: return False return True blocks_with_correct_padding = encrypt(bytes(b'A' * (block_size + 5)))[block_size:] def decryption_oracle(payload): global iv_as_key iv = bytes(b'A' * block_size) payload = iv + payload + blocks_with_correct_padding plaintext = decrypt(payload, iv_as_key) if iv_as_key: return xor(plaintext[block_size:block_size * 2], iv) return xor(plaintext[:block_size], iv) if __name__ == '__main__': if len(sys.argv) != 3 or sys.argv[1] not in ['encrypt', 'decrypt']: print("Usage: {} encrypt|decrypt data".format(sys.argv[0])) sys.exit(1) if sys.argv[1] == 'encrypt': print(b2h(encrypt(h2b(sys.argv[2])))) else: print(b2h(decrypt(h2b(sys.argv[2]))))
def test_bleichenbacher_signature_forgery(): key = key_1024_small_e print( "\nTest bleichenbacher_signature_forgery(key, garbage='suffix', hash_function='sha1')" ) for _ in range(10): message1 = bytes(b"Some plaintext ") + random_bytes(10) + bytes( b" anything can it be") message2 = bytes(b"Some plaintext ") + random_bytes(10) + bytes( b" anything can it be") key.add_plaintext(b2i(message1)) key.add_plaintext(b2i(message2)) forged_signatures = bleichenbacher_signature_forgery( key, garbage='suffix', hash_function='sha1') assert len(forged_signatures) == 2 verify_signature1 = subprocess.check_output([ "python", rsa_oracles_path, "verify_bleichenbacher_suffix", key.identifier, b2h(message1), i2h(forged_signatures[0]), 'sha1' ]).strip().decode() assert verify_signature1 == 'True' verify_signature2 = subprocess.check_output([ "python", rsa_oracles_path, "verify_bleichenbacher_suffix", key.identifier, b2h(message2), i2h(forged_signatures[1]), 'sha1' ]).strip().decode() assert verify_signature2 == 'True' key.clear_texts() print( "\nTest bleichenbacher_signature_forgery(key, garbage='middle', hash_function='sha1')" ) for _ in range(10): message1 = bytes(b"Some plaintext ") + random_bytes(10) + bytes( b" anything can it be") message2 = bytes(b"Some plaintext ") + random_bytes(10) + bytes( b" anything can it be") key.add_plaintext(b2i(message1)) key.add_plaintext(b2i(message2)) forged_signatures = bleichenbacher_signature_forgery( key, garbage='middle', hash_function='sha1') print(forged_signatures) # first plaintext signed if 0 in forged_signatures: verify_signature1 = subprocess.check_output([ "python", rsa_oracles_path, "verify_bleichenbacher_middle", key.identifier, b2h(message1), i2h(forged_signatures[0]), 'sha1' ]).strip().decode() assert verify_signature1 == 'True' # second plaintext signed if 1 in forged_signatures: verify_signature2 = subprocess.check_output([ "python", rsa_oracles_path, "verify_bleichenbacher_middle", key.identifier, b2h(message2), i2h(forged_signatures[1]), 'sha1' ]).strip().decode() assert verify_signature2 == 'True' key.clear_texts()
def decrypt(ciphertext, padding_oracle=None, decryption_oracle=None, iv=None, block_size=16, is_correct=True, amount=0, known_plaintext=None, async_calls=False): """Decrypt ciphertext Give padding_oracle or decryption_oracle (or both) Args: ciphertext(string): to decrypt padding_oracle(function/None) decryption_oracle(function/None) iv(string): if not specified, first block of ciphertext is treated as iv block_size(int) is_correct(bool): set if ciphertext will decrypt to something with correct padding amount(int): how much blocks decrypt (counting from last), zero (default) means all known_plaintext(string): with padding, from end (aligned to end of ciphertext) async_calls(bool): make asynchronous calls to oracle (not implemented yet) Returns: plaintext(string): with padding """ _check_oracles(padding_oracle=padding_oracle, decryption_oracle=decryption_oracle, block_size=block_size) if block_size % 8 != 0: log.critical_error("Incorrect block size: {}".format(block_size)) if len(ciphertext) % block_size != 0: log.critical_error("Incorrect ciphertext length: {}".format( len(ciphertext))) if decryption_oracle: if iv: ciphertext = iv + ciphertext blocks = chunks(ciphertext, block_size) plaintext = bytes(b'') for position in range(len(blocks) - 1, 0, -1): plaintext = xor(decryption_oracle(blocks[position]), blocks[position - 1]) + plaintext log.info("Plaintext(hex): {}".format(b2h(plaintext))) if amount != 0 and len(plaintext) == amount * block_size: break log.success("Decrypted(hex): {}".format(b2h(plaintext))) return plaintext log.info("Start cbc padding oracle") log.debug(print_chunks(chunks(ciphertext, block_size))) # prepare blocks blocks = chunks(ciphertext, block_size) if iv: if len(iv) % block_size != 0: log.critical_error("Incorrect iv length: {}".format(len(iv))) log.info("Set iv") blocks.insert(0, iv) if amount != 0: amount = len(blocks) - amount - 1 if amount < 0 or amount >= len(blocks): log.critical_error( "Incorrect amount of blocks to decrypt: {} (have to be in [0,{}]". format(amount, len(blocks) - 1)) log.info("Will decrypt {} block(s)".format(len(blocks) - 1 - amount)) # add known plaintext plaintext = bytes(b'') position_known = 0 chars_decoded = 0 if known_plaintext: is_correct = False plaintext = known_plaintext blocks_decoded = len(plaintext) // block_size chars_decoded = len(plaintext) % block_size if blocks_decoded == len(blocks) - 1: log.debug("Nothing decrypted, known plaintext long enough") return plaintext if blocks_decoded > len(blocks) - 1: log.critical_error( "Too long known plaintext ({} blocks)".format(blocks_decoded)) if blocks_decoded != 0: blocks = blocks[:-blocks_decoded] position_known = chars_decoded log.info("Have known plaintext, skip {} block(s) and {} bytes".format( blocks_decoded, chars_decoded)) # start decryption for count_block in range(len(blocks) - 1, amount, -1): """ Blocks from the last to the second (all except iv) """ log.info("Block no. {}".format(count_block)) payload_prefix = bytes(b''.join(blocks[:count_block - 1])) payload_modify = blocks[count_block - 1] payload_decrypt = blocks[count_block] if chars_decoded != 0: # we know some chars, so modify previous block payload_modify = payload_modify[:-chars_decoded] +\ xor(plaintext[:chars_decoded], payload_modify[-chars_decoded:], bytes([chars_decoded + 1])) chars_decoded = 0 position = block_size - 1 - position_known position_known = 0 while position >= 0: """ Every position in block, from the end """ log.debug("Position: {}".format(position)) found_correct_char = False for guess_char in range(256): modified = payload_modify[:position] + bytes( [guess_char]) + payload_modify[position + 1:] payload = bytes(b''.join( [payload_prefix, modified, payload_decrypt])) iv = payload[:block_size] payload = payload[block_size:] log.debug(print_chunks(chunks(iv + payload, block_size))) correct = padding_oracle(payload=payload, iv=iv) if correct: """ oracle returns True """ padding = block_size - position # sent ciphertext decoded to that padding decrypted_char = bytes( [payload_modify[position] ^ guess_char ^ padding]) if is_correct: """ If we didn't send original ciphertext, then we have found original padding value. Otherwise keep searching and if won't find any other correct char - padding is \x01 """ if guess_char == blocks[-2][-1]: log.debug( "Skip this guess char ({})".format(guess_char)) continue dc = int(decrypted_char[0]) log.info( "Found padding value for correct ciphertext: {}". format(dc)) if dc == 0 or dc > block_size: log.critical_error( "Found bad padding value (given ciphertext may not be correct)" ) plaintext = decrypted_char * dc payload_modify = payload_modify[:-dc] + xor( payload_modify[-dc:], decrypted_char, bytes([dc + 1])) position = position - dc + 1 is_correct = False else: """ abcd efgh ijkl o|guess_char|xy || 1234 5678 9tre qwer - ciphertext what ever itma ybex || xyzw rtua lopo k|\x03|\x03\x03 - plaintext abcd efgh ijkl |guess_char|wxy || 1234 5678 9tre qwer - next round ciphertext some thin gels eheh || xyzw rtua lopo guessing|\x04\x04\x04 - next round plaintext """ if position == block_size - 1: """ if we decrypt first byte, check if we didn't hit other padding than \x01 """ payload = iv + payload payload = payload[:-block_size - 2] + bytes( b'A') + payload[-block_size - 1:] iv = payload[:block_size] payload = payload[block_size:] correct = padding_oracle(payload=payload, iv=iv) if not correct: log.debug("Hit false positive, guess char({})". format(guess_char)) continue payload_modify = payload_modify[:position] + xor( bytes([guess_char]) + payload_modify[position + 1:], bytes([padding]), bytes([padding + 1])) plaintext = decrypted_char + plaintext found_correct_char = True log.debug( "Guessed char(\\x{:02x}), decrypted char(\\x{:02x})". format(guess_char, decrypted_char[0])) log.debug("Plaintext: {}".format(plaintext)) log.info("Plaintext(hex): {}".format(b2h(plaintext))) break position -= 1 if found_correct_char is False: if is_correct: padding = 0x01 payload_modify = payload_modify[:position + 1] + xor( payload_modify[position + 1:], bytes([padding]), bytes([padding + 1])) plaintext = bytes(b"\x01") is_correct = False else: log.critical_error( "Can't find correct padding (oracle function return False 256 times)" ) log.success("Decrypted(hex): {}".format(b2h(plaintext))) return plaintext