def get_prefix_size(oracle): """ Determines the size of the prefix for the encryption oracle. Args: oracle: The encryption oracle Returns: The size of the prefix and the length of the secret """ controlled_bytes = b'' original = oracle(controlled_bytes) controlled_bytes += b'A' test = oracle(controlled_bytes) # Find the block where the prefix ends prefix_block = find_prefix_block(original, test) start_len = (prefix_block * 16) # Loop through the block, looking for the end for i in range(15): controlled_bytes += b'A' new_test = oracle(controlled_bytes) if c6.get_block(new_test, prefix_block) == c6.get_block(test, prefix_block): break test = new_test # BUG: when prefix size mod block size is 0, this is off by one. I don't care. return start_len + 16 - (len(controlled_bytes) - 1), (len(controlled_bytes) - 1) % 16
def attack_cbc(): """ Breaks CBC mode when the IV is key, as described in the challenge. Returns: True if the attack worked """ ct = encrypt_userdata(b'blahblahblah') bad_ct = ct[:16] + (b'\x00' * 16) + ct[:16] valid, pt = verify_url(bad_ct) k = c2.xorstrs(c6.get_block(pt, 0), c6.get_block(pt, 2)) return k == key
def is_ecb(oracle, blocksize): """ Detects whether or not the given encryption oracle is using ECB mode Args: oracle: The encryption oracle blocksize: The block size of the oracle Returns: True if oracle is using ECB mode """ ct = oracle(b'A' * blocksize * 3) return c6.get_block(ct, 0, blocksize) == c6.get_block(ct, 1, blocksize)
def aes_128_cbc_decrypt(txt, key, IV=b'\x00' * 16): """ Decrypts the given bytestring using AES-128 in CBC mode Args: txt: The text to be decrypted key: The encryption key iv: The initialization vector Returns: The decrypted bytestring. """ # Assert all size constrains if len(txt) % 16 != 0: raise ValueError('Input length must be a multiple of 16, got ' + str(len(txt))) if len(key) != 16: raise ValueError('Key must be length 16, got ' + str(len(key))) if len(IV) != 16: raise ValueError('IV must be length 16, got ' + str(len(IV))) num_blocks = len(txt) // 16 prev_block = IV result = [] # Loop through each block, XORing with the previous for i in range(num_blocks): cur_block = c6.get_block(txt, i, 16) temp = cur_block cur_block = c7.aes_128_ecb_decrypt(cur_block, key) cur_block = c2.xorstrs(cur_block, prev_block) prev_block = temp result.append(cur_block) return b''.join(result)
def aes_128_cbc_encrypt(txt, key, IV=b'\x00' * 16): """ Encrypts a bytestring under AES-128 in CBC mode Args: txt: The plaintext to be encrypted key: The key to encrypt under iv: The initialization vector Returns: The encrypted text under AES-128 in CBC mode. """ # Assert all size constraints if len(txt) % 16 != 0: raise ValueError('Input length must be a multiple of 16, got ' + str(len(txt))) if len(key) != 16: raise ValueError('Key must be length 16, got ' + str(len(key))) if len(IV) != 16: raise ValueError('IV must be length 16, got ' + str(len(IV))) num_blocks = len(txt) // 16 prev_block = IV result = [] # Loop through each block, XORing with the previous for i in range(num_blocks): cur_block = c6.get_block(txt, i, 16) cur_block = c2.xorstrs(prev_block, cur_block) cur_block = c7.aes_128_ecb_encrypt(cur_block, key) prev_block = cur_block result.append(prev_block) return b''.join(result)
def attack_block(txt, block_num): """ Attacks a single block of the ciphertext. Args: txt: The full ciphertext block_num: The block number to attack Returns: The plaintext of this block. """ plaintext = b'' block = c6.get_block(txt, block_num) prev_b = c6.get_block(txt, block_num - 1) for i in reversed(range(16)): p = attack_byte(block, prev_b, i, plaintext) plaintext = p + plaintext return plaintext
def find_prefix_block(oracle, test): """ Finds the block that contains the prefix Args: original: The result of encryption_oracle(b'') test: The result of encryption_oracle(b'A') Returns: The block number where the prefix ends Raises: RuntimeError if the block is not found """ for i in range(len(oracle) // 16): if c6.get_block(oracle, i) != c6.get_block(test, i): return i raise RuntimeError('could not find prefix block')
def fake_admin(): """ Creates a fake admin account using ECB cut-and-paste attack Returns: The decoded profile as a dictionary with dict[role] == admin """ # This attack involves block alignment. # # 0123456789ABCDEF 0123456789ABCDEF 0123456789ABCDEF # email=sponge@bob .com&uid=2&role= user # email=blahblahbl adminBBBBBBBBBBB &uid=3&role=user # Cut and paste the blocks you want # email=spongebobsquar&uid=2&role=admin0000000000B first_entry = encode_profile("*****@*****.**") second_entry = encode_profile("blahblahbladmin" + '\x0B' * 11) bad_cookie = first_entry[:32] + c6.get_block(second_entry, 1, 16) return decode_profile(bad_cookie)
def is_ecb(txt, maxBlocks=1): """ Determines if a given ciphertext was encrypted in ECB mode looking for repeated blocks. Args: txt: The ciphertext in question maxBlocks: The maximum number of repeated blocks allowed before it is considered to be ECB. Returns: True if the txt was encrypted with ECB """ num_blocks = len(txt) // 16 maxCount = 1 for i in range(num_blocks): block = c6.get_block(txt, i, 16) count = txt.count(block) if count > maxCount: maxCount = count return maxCount > maxBlocks