In [59]:
#! python3
from Crypto.Cipher import AES
import string
from itertools import combinations_with_replacement
from binascii import hexlify, unhexlify


# use 0 to replace the unknown info
raw_ct = unhexlify('fe' + '0'*28 + 'c3' + '307df037c689300bbf2812ff89bc0b49')
raw_iv = '0'*16

pt = 'The message is protected by AES!'
key_prefix = '5d6I9pfR7C1JQt'

def recover_key():

    for guess in combinations_with_replacement(string.printable, 2):
        guess = ''.join(guess)
        key = key_prefix + guess
        decry_pt = AES.new(key, mode=AES.MODE_CBC, IV=raw_iv).decrypt(raw_ct)
        
        
        if decry_pt[16] == ord('r') and decry_pt[31] == ord('!'):
            return key

        

def xor_bytes(a,b):
    return bytes([i^j for i,j in zip(a,b)])



def recover_ct(key):
    right_pt = pt.encode()
    wrong_ct = raw_ct
    
    wrong_pt = AES.new(key, mode=AES.MODE_CBC, IV=raw_iv).decrypt(raw_ct)
    #                                                 
    right_first_ct = xor_bytes(wrong_pt[16:], xor_bytes(right_pt[16:], wrong_ct[:16]))
    return right_first_ct + raw_ct[16:]
    

def recover_iv(ct, key):
    right_pt = pt.encode()
    wrong_iv = raw_iv.encode()
    
    wrong_pt = AES.new(key, mode=AES.MODE_CBC, IV=wrong_iv).decrypt(ct)
    #                                                 
    right_iv = xor_bytes(wrong_pt[:16], xor_bytes(right_pt[:16], wrong_iv))
    return right_iv.decode()
    
    
key = recover_key()
ct = recover_ct(key)
print(recover_iv(ct, key))


Key:rVFvN9KLeYr6
dec(key, second_block_ct) ^ wrong_first_block_ct = wrong_second_block_pt dec(key, second_block_ct) ^ right_first_block_ct = right_second_block_pt => right_first_block_ct = wrong_second_block_pt ^ right_second_block_pt ^ wrong_first_block_ct