def break_cbc_single_blk( cipher_blk1: bytes, cipher_blk2: bytes, padding_oracle: Callable[[bytes, bytes], bool]) \ -> bytes: """ params: cipher_blk1: encrypted block prior to `cipher_blk2` or IV if `cipher_blk2` was the first encrypted block cipher_blk2: block to be decrypted padding_oracle: `valid_padding()` returns: decryption of `cipher_blk2` """ intermediate_blk2 = b'' plain_blk2 = b'' for i in range(1, 17): target_byte = i.to_bytes(1, byteorder=sys.byteorder) atk_blk_prefix = rand_bytes_gen(16 - i) atk_blk_suffix = b'' for k in range(len(intermediate_blk2)): atk_blk_suffix += xor(target_byte, get_byte_n(intermediate_blk2, k)) for j in range(256): atk_byte = j.to_bytes(1, byteorder=sys.byteorder) if valid_padding(cipher_blk2, atk_blk_prefix + atk_byte + atk_blk_suffix): break intermediate_blk2 = xor(atk_byte, target_byte) + intermediate_blk2 plain_blk2 = xor(intermediate_blk2, get_byte_n(cipher_blk1, -i)) + plain_blk2 return plain_blk2
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 test_xor_bytes_of_different_length_only_xors_up_to_smallest_length( self): bytes1 = b'\xD4\x1D\x28' bytes2 = b'\x9C\x78\x51' expected_bytes = b'Hey' actual_bytes = xor(bytes1, bytes2) self.assertEqual(expected_bytes, actual_bytes)
def test_xor_cryptopals_case(self): hex1 = '1c0111001f010100061a024b53535009181c' bytes1 = bytes.fromhex(hex1) hex2 = '686974207468652062756c6c277320657965' bytes2 = bytes.fromhex(hex2) expected_hex = '746865206b696420646f6e277420706c6179' expected_bytes = bytes.fromhex(expected_hex) actual_bytes = xor(bytes1, bytes2) self.assertEqual(expected_bytes, actual_bytes)
def break_rand_access_ctr( ciphertext: bytes, edit_oracle: Callable[[bytes, int, bytes], bytes]) \ -> bytes: """ params: ciphertext: encrypted using AES-128 edit_oracle: `edit_oracle()` from `gen_edit_oracle()` returns: plaintext """ key_cipher = bytes(len(ciphertext)) key_cipher = edit_oracle(ciphertext, 0, key_cipher)[:len(key_cipher)] return xor(ciphertext, key_cipher)
def main(): """ encrypt base64 encoded strings from file with consistent nonce break decryption by using `break_repeat_xor()` to get as much of the key as possible fix key by XORing encrypted messages with the decryption that has been made apparent """ encrypted_lines = [] key = b"YELLOW SUBMARINE" nonce = bytes(8) input_filename = os.path.join(pathlib.Path(__file__).parent, "input") with open(input_filename, "r") as input_file: for line in input_file: encrypted_lines.append(ctr_encrypt_b64(line, key, nonce)) repeat_xor_cipher = b'' blksize = 31 for line in encrypted_lines: if len(line) >= blksize: repeat_xor_cipher += line[:blksize] res = break_repeat_xor(repeat_xor_cipher, blksize) key = res["key"] max_len = max([len(line) for line in encrypted_lines]) key = xor(b"I have met them at close of day", encrypted_lines[0]) key += bytes(max_len - len(key)) key = xor(b"I have passed with a nod of the head", encrypted_lines[4]) key += bytes(max_len - len(key)) key = xor(b"He, too, has been changed in his turn,", encrypted_lines[37]) key += bytes(max_len - len(key)) for line in encrypted_lines: print(xor(key, line).decode())
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 encrypt(self: CTRMode, plaintext: bytes) -> bytes: """ Use cipher to encrypt combination of nonce and block counter, then XORs the result with the block """ ciphertext = b'' i = 0 for blk_count in self.counter(): curr_blk = get_block_n(plaintext, self.blksize, i) if curr_blk == b'': break counter_blk = self.combine(self.nonce, blk_count, self.blksize) ciphertext += xor(curr_blk, self.encrypt_blk(counter_blk)) i += 1 return ciphertext
def single_xor(b: bytes, s: bytes) -> bytes: """ params: b: bytes to be XORed s: single byte used for encryption returns: result of XORing every byte of `b` with `s` if `s` is more than one byte, only first byte is used raises: TypeError if `b` is None """ if not s: return b operand = b'' for i in range(len(b)): operand += s[0].to_bytes(1, byteorder=sys.byteorder) return xor(b, operand)
def repeat_xor(b: bytes, key: bytes) -> bytes: """ params: b: bytes to be XORed key: key used for encryption returns: result of XORing bytes of `b` with bytes of `key` by cycling through the `key` """ if not key: return b operand = b'' j = 0 key_len = len(key) for i in range(len(b)): operand += key[j].to_bytes(1, byteorder=sys.byteorder) j = (j + 1) % key_len return xor(b, operand)
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 main(): """ insert ";admin=true;" string inside encrypted message using only `encryption_oracle()` """ encryption_oracle = gen_encryption_oracle() desired_plaintext = b";admin=true;" # `encryption_oracle()` prepends exactly two blocks # feed some zeroes to figure out the key-cipher for the third block encrypted = encryption_oracle(bytes(len(desired_plaintext))) keycipher_block3 = get_block_n(encrypted, 16, 2) # knowing the key cipher it's easy to figure out what input we should feed # to get the decryption we desire attack_block = xor(desired_plaintext, keycipher_block3) attack_encrypted = encrypted[:32] + attack_block + \ encrypted[32+len(desired_plaintext):] print(is_admin(attack_encrypted))
def test_xor_none_input_raises_typeerror(self): with self.assertRaises(TypeError): xor(None, None)
def main(): """ encrypt base64 encoded strings from file with consistent nonce break decryption by using `break_repeat_xor()` to get as much of the key as possible fix key by XORing encrypted messages with the decryption that has been made apparent """ encrypted_lines = [] key = b"YELLOW SUBMARINE" nonce = bytes(8) input_filename = os.path.join(pathlib.Path(__file__).parent, "input") with open(input_filename, "r") as input_file: for line in input_file: encrypted_lines.append(ctr_encrypt_b64(line, key, nonce)) repeat_xor_cipher = b'' longest_line = max(encrypted_lines, key=len) blksize = len(longest_line) for line in encrypted_lines: repeat_xor_cipher += line + longest_line[len(line):] res = break_repeat_xor(repeat_xor_cipher, blksize) key = res["key"] key = xor( (b'I\'m rated "R"...this is a warning, ya better void / Poets are ' b'paranoid, DJ<s/D-s\';;y\'%................................'), encrypted_lines[0]) key += bytes(blksize - len(key)) key = xor((b"Worse than a nightmare, you don't have to sleep a wink / " b"The pain's a migraine e%,&yb5...................."), encrypted_lines[12]) key += bytes(blksize - len(key)) key = xor( (b"Cuz I came back to attack others in spite- / Strike like lightnin'," b" It's quite frighteningv..........................."), encrypted_lines[1]) key += bytes(blksize - len(key)) key = xor( (b"The fiend of a rhyme on the mic that you know / It's only one " b"capable, breaks-the unbreakable......................."), encrypted_lines[17]) key += bytes(blksize - len(key)) key = xor( (b"For those that oppose to be level or next to this / I ain't a devil" b" and this ain't the Exorcist...................."), encrypted_lines[11]) key += bytes(blksize - len(key)) key = xor( (b"Worse than a nightmare, you don't have to sleep a wink / The pain's" b" a migraine every time ya think.................."), encrypted_lines[12]) key += bytes(blksize - len(key)) key = xor( (b'You want to hear some sounds that not only pounds but please your ' b'eardrums; / I sit back and observe the whole s' b'cenery'), encrypted_lines[26]) key += bytes(blksize - len(key)) for i, line in enumerate(encrypted_lines): print(xor(key, line).decode())
def mock_fun(self, b: bytes) -> bytes: operand = b'' for i in range(len(b)): operand += i.to_bytes(1, byteorder=sys.byteorder) return xor(b, operand)
def test_xor_empty_input_returns_empty_bytes(self): self.assertEqual(xor(b'', b''), b'')