def decrypt(oracle=encryption_oracle): block_size = detect_block_size() if not is_ecb_mode(block_size, oracle): raise Exception('Does not use ecb mode') #known secret prefix prefix = '' #idea: expand known prefix one byte at a time #length of: #'a'*(block_size - (len(prefix) % block_size) - 1) + prefix + guess_byte #is multiple of block size all but last byte is known #try each possible value of next byte. For correct guess, ct blocks match first blocks of: #'a'*(block_size - (len(prefix) % block_size) - 1) + secret while True: #determine next byte of secret pad_len = block_size - (len(prefix) % block_size) - 1 found_next = False #try most common (ascii) bytes first to speed up search for guess in ranking: pt = 'a' * pad_len + prefix + guess + 'a' * pad_len ct_blocks = aes.splitBlocks(oracle(pt), block_size) i = (pad_len + len(prefix) + 1) / block_size if ct_blocks[i - 1] == ct_blocks[2 * i - 1]: prefix += guess found_next = True print prefix break #Assume we are at end of secret when this occurs if not found_next: return prefix
def detect_mode(): '''distiguishes between CBC and ECB modes given encryption oracle access''' pt = 'a' * (11 + 11 + 32) ct_blocks = aes.splitBlocks(encryption_oracle(pt)) if ct_blocks[1] == ct_blocks[2]: print 'ECB' else: print 'probably CBC'
def get_key(): ct = oracle() blocks = aes.splitBlocks(ct) #send oracle c_0 || 00..0 || c_0 # = aes(iv ^ p_0) || 00..0 || aes(iv ^ p_0) # = aes(key ^ p_0) || 00..0 || aes(key ^ p_0) #This decrypts to: # p_0' || p_1' || p_2' = p_0 || aes_dec(00...) ^ c_0 || key ^ p_0 #==> key = p_0' ^ p_2' parts = [blocks[0], '\0' * 16, blocks[0]] parts.extend(blocks[3:]) ctp = ''.join(parts) try: #p_1' = aes_dec(00...0) is non ascii with high probability #so we should get error with returned plaintext consume_ct(ctp) except NonAscii as e: blocks = aes.splitBlocks(e.args[0]) return util.xor(blocks[0], blocks[2])
def get_pad_ct(pad_letter, oracle=encryption_oracle, block_size=16): #get aes(pad_letter*block_size) assert (len(pad_letter) == 1) c = oracle(pad_letter * (3 * block_size)) #because the prefix is random, the first equal blocks are almost #guarrenteed to be enc(pad_letter*block_size) blocks = aes.splitBlocks(c, block_size) i = 0 while blocks[i] != blocks[i + 1]: i += 1 return blocks[i]
def decrypt_ciphertext(ct): ct_blocks = aes.splitBlocks(ct) pt_blocks = [] #for each block (except iv) for i in range(1,len(ct_blocks)): #Consider the first i blocks: #iv || c1,1 c1,2 c1,3 ... c1,16 || ... || ci,1 ci,2 ... ci,16 suffix = '' #determine bytes in ith block working backward for _ in range(16): #try guessing each possible byte value found = False for g in ranking: #setting: #c'_{i - 1} = c_{i - 1} xor 00..0 || guess|| suffix #causes c_i to decrypt to: #m_i xor 00..0 || guess|| suffix = m_i1 ...x00000 # where x = 0 iff the guess is correct #slen = len(suffix) #t1 = '\x00'*(15 - slen) + g + suffix #t2 = '\x00'*(15 - slen) + chr(slen + 1)*(slen + 1) #setting: #c'_{i - 1} = c_{i - 1} xor t1 xor t2 #causes c_i to decrypt to #m_i xor t1 xor t2 #mi,1 mi,2 .. ((mi,k xor guess) xor (slen + 1)) #which has valid padding if the guess is correct cp = xor_tail(ct_blocks[i - 1], g + suffix) cp = xor_tail(cp, chr(len(suffix) + 1)*(len(suffix) + 1)) ct = ct_blocks[:i - 1] ct.append(cp) ct.append(ct_blocks[i]) if valid_padding(''.join(ct)): suffix = g + suffix found = True break if not found: raise Exception("could not find value of byte") pt_blocks.append(suffix) return aes.remove_pkcs7_padding(''.join(pt_blocks))
def is_ecb_mode(block_size=16, oracle=encryption_oracle): '''distiguishes between CBC and ECB modes given encryption oracle access''' ct_blocks = aes.splitBlocks(oracle('a' * (2 * block_size)), block_size) return ct_blocks[0] == ct_blocks[1]
def decrypt(oracle=encryption_oracle): block_size = 16 #1) get enc(r || 3 blocks of as || secret || padding) # find first pair of adjacent blocks that are equal => # the equal blocks are both: enc(aaa...a) cpa = get_pad_ct('a') #2) repeat previous step to find enc(bbb...b) cpb = get_pad_ct('b') known_prefix = '' while True: #while full message not known #3) want: enc( ..... known_prefix <next byte of secret> <block boundary> ....) pad_len = 2 * block_size - len(known_prefix) % block_size - 1 assert (block_size <= pad_len) assert (pad_len < 2 * block_size) #when <next byte of secret> is last byte of block # #c = enc(r || b*bloc_size a*pad_len secret || padding) # #has the blocks enc(b*block_size) || enc(a*block_size) next to each other #find lb = last block of ciphertext when <next secret byte is aligned> aligned = False while not aligned: c = oracle('b' * block_size + 'a' * pad_len) blocks = aes.splitBlocks(c, block_size) #check if next byte of secret is next to block boundary for i in range(len(blocks) - 1): if blocks[i] == cpb and blocks[i + 1] == cpa: #it is #soff = index last block - index of enc(a*block_size) + 1 soff = len(blocks) - (i + 1) lb = blocks[-1] aligned = True break found = False for guess in ranking: #get c = enc(r || a*pad_len known_prefix guess a*pad_len secret || padding) #where guess is algined with byte boundary (this happends when c ends with lb while True: c = oracle('a' * pad_len + known_prefix + guess + 'a' * pad_len) if c.endswith(lb): break #~block_size requests blocks = aes.splitBlocks(c, block_size) #TODO: check these indicies (not correct ones) #blocks[-soff] = first block before secret #blocks[-soff - 1] = enc(end of known prefix guess) #offset from first block before secret to block with <next byte of secret coff = (pad_len + len(known_prefix) + 1) / block_size if blocks[-soff - 1] == blocks[-soff + coff - 1]: known_prefix += guess print known_prefix found = True break if not found: return known_prefix