def recover_key(ciphertext: bytes, ascii_oracle: Callable[[bytes], None], blksize: int = 16) \ -> bytes: """ params: ciphertext: encrypted using `encryption_method()` must be at least three blocks long ascii_oracle: `validate_ascii()` blksize: blocksize used by encryption returns: key used by encryption, or None if less than three bytes were given """ ciphertext_blocks = [block for block in blocks(ciphertext, blksize)] if len(ciphertext_blocks) < 3: return None # append last two blocks to maintain valid padding attack_ciphertext = ciphertext_blocks[0] + bytes(blksize) + \ ciphertext_blocks[0] + ciphertext_blocks[-2] + ciphertext_blocks[-1] decrypted = ascii_oracle(attack_ciphertext) if decrypted: return xor(get_block_n(decrypted, blksize, 0), get_block_n(decrypted, blksize, 2)) return None
def is_ecb(encrypt: Callable[[bytes], bytes], blksize: int = 16) \ -> bool: """ params: encrypt: encrypts bytes using either ECB or CBC mode returns: True if `encrypt` used ECB mode, False otherwise """ input_bytes = bytes(blksize*4) # four blocks of zeroes res = encrypt(input_bytes) block_count = defaultdict(lambda: 0) for block in blocks(res, blksize): block_count[block] += 1 if block_count[block] >= 3: return True return False
def break_cbc(encrypted: bytes, iv: bytes, padding_oracle: Callable[[bytes, bytes], bool]) \ -> bytes: """ params: encrypted: encrypted message from `encryption_oracle()` iv: IV used for encryption from `encryption_oracle()` padding_oracle: `valid_padding()` returns: decrypted bytes for `encrypted` """ decrypted = b'' prev_blk = iv for curr_blk in blocks(encrypted, 16): decrypted += break_cbc_single_blk(prev_blk, curr_blk, padding_oracle) prev_blk = curr_blk return PKCS7Padding.unapply(decrypted)
def decrypt(self: CBCMode, ciphertext: bytes) -> bytes: """ decrypts each block, then XORs with cipher of the previous block (or IV if first block) raises: ValueError: if size of `plaintext` is not divisible by `self.blksize` """ if len(ciphertext) % self.blksize != 0: raise ValueError("ciphertext is not %s-bit padded" % self.blksize) plaintext = b'' prev_cipherblk = self.iv for block in blocks(ciphertext, self.blksize): plaintext += xor(self.decrypt_blk(block), prev_cipherblk) prev_cipherblk = block return plaintext
def encrypt(self: CBCMode, plaintext: bytes) -> bytes: """ XORs each block with the cipher of the previous block (or the IV if first block), then encrypts raises: ValueError: if size of `plaintext` is not divisible by `self.blksize` """ if len(plaintext) % self.blksize != 0: raise ValueError("plaintext is not %s-bit padded" % self.blksize) ciphertext = b'' prev_cipherblk = self.iv for block in blocks(plaintext, self.blksize): prev_cipherblk = self.encrypt_blk(xor(block, prev_cipherblk)) ciphertext += prev_cipherblk return ciphertext
def length_extension( message: bytes, newtext: bytes, validate_mac_oracle: Callable[[bytes], bool], max_keysize: int = 128) \ -> bytes: """ params: message: authenticated using SHA-1 keyed MAC newtext: text to append to message validate_mac_oracle: oracle that returns True if input is authenticated with a valid MAC, False otherwise max_keysize: max keysize to try (>= 0) returns: valid authenticated message (i.e. passes `MAC.validate()`) with `newtext` appended to it """ mac = message[:SHA1.digest_size] oldtext = message[SHA1.digest_size:] start_state = tuple( int.from_bytes(block, "big") for block in blocks(mac, 4) ) hasher = SHA1() for keysize in range(max_keysize): glue_padding = MDPadding.apply( bytes(keysize)+oldtext )[keysize+len(oldtext):] hasher._message_byte_length = keysize + len(oldtext) + \ len(glue_padding) hasher._h = start_state hasher._unprocessed = b'' hasher.update(newtext) new_mac = hasher.digest() new_message = new_mac + oldtext + glue_padding + newtext if validate_mac_oracle(new_message): return new_message
def main(): """ insert ";admin=true;" string inside encrypted message using only `encryption_oracle()` """ encryption_oracle = gen_encryption_oracle() # `encryption_oracle()` prepends exactly two blocks # add two blocks of our own, the first block will be used to mutate the # second one to our desired string plain2 = bytes(16) plain3 = bytes(16) hack_enc = encryption_oracle(plain2 + plain3) hack_enc_blocks = [b for b in blocks(hack_enc, 16)] # knowing the blocks that are getting encrypted (we just picked them above) # we can figure out the AES decryption of the second block we added decrypted_block3 = xor(hack_enc_blocks[2], plain3) # the AES decryption of our block will get XORed with the previous block # (i.e. the first block we added) # simply XOR decrypted and desired to figure out what first block we need # to use to mutate the second block as desired desired_block = b"\x00\x00\x00\x00;admin=true;" encrypted2 = xor(decrypted_block3, desired_block) # remix the encrypted message by replacing the encrypted block of the first # block we added with the block that will give us the required mutation # all other blocks can remain the same as changing one block will only # mutate the decryption of the next one and won't change the ones further # down (i.e. not risk of ruining the padding down the line) encrypted = hack_enc_blocks[0] + hack_enc_blocks[1] + encrypted2 for i in range(3, len(hack_enc_blocks)): encrypted += hack_enc_blocks[i] print(is_admin(encrypted))
def test_blocks_bytes_not_padded_ignores_extraneous(self): b = b"ABCDEFG" expected_blocks = [b"AB", b"CD", b"EF"] for i, block in enumerate(blocks(b, 2)): self.assertEqual(expected_blocks[i], block)
def test_blocks_bytes_of_proper_length(self): b = b"ABCDEF" expected_blocks = [b"AB", b"CD", b"EF"] for i, block in enumerate(blocks(b, 2)): self.assertEqual(expected_blocks[i], block)
def test_blocks_negative_blksize_returns_empty_bytes(self): b = b"ABCDEF" expected_blocks = [b''] for i, block in enumerate(blocks(b, -1)): self.assertEqual(expected_blocks[i], block)