import bisect from operator import itemgetter from typing import List from Crypto.Cipher import DES from utils import * def get_index(a: List[bytes], x: bytes) -> int: """Locate the leftmost value exactly equal to x in list a Args: a (List[bytes]): the list in which to search x (bytes): the value to be searched Returns: int: The leftmost index at which the value is found in the list, or -1 if not found """ i = bisect.bisect_left(a, x) if i != len(a) and a[i] == x: return i else: return -1 def des_enc(k: bytes, m: bytes) -> bytes: """ Encrypt a message m with a key k using DES as follows: c = DES(k, m) Note that for DES the key is given as 8 bytes, where the last bit of each byte is just a parity bit, giving the actual key of 56 bits, as expected for DES. The parity bits are ignored. Args: k (str): bytestring of length exactly 8 bytes. m (str): bytestring containing the message (i.e. a sequence of characters such as 'Hello' or '\x02\x04') Return: bytes: The bytestring ciphertext c """ d = DES.new(k, DES.MODE_ECB) c = d.encrypt(m) return c def des_dec(k: bytes, c: bytes) -> bytes: """ Decrypt a message c with a key k using DES as follows: m = DES(k, c) Note that for DES the key is given as 8 bytes, where the last bit of each byte is just a parity bit, giving the actual key of 56 bits, as expected for DES. The parity bits are ignored. Args: k (str): bytestring of length exactly 8 bytes. c (str): bytestring containing the ciphertext (i.e. a sequence of characters such as 'Hello' or '\x02\x04') Return: bytes: The bytestring plaintext m """ d = DES.new(k, DES.MODE_ECB) m = d.decrypt(c) return m def des2_enc(k1: bytes, k2: bytes, m: bytes) -> bytes: # TODO B.1: implement des2_enc raise NotImplementedError("Not implemented") def des2_dec(k1: bytes, k2: bytes, c: bytes) -> bytes: # TODO B.2: implement des2_dec raise NotImplementedError("Not implemented") def test_des2() -> None: k1 = "Smerenie" k2 = "Dragoste" m1_given = "Fericiti cei saraci cu duhul, ca" c1 = "cda98e4b247612e5b088a803b4277710f106beccf3d020ffcc577ddd889e2f32" c2 = "54826ea0937a2c34d47f4595f3844445520c0995331e5d492f55abcf9d8dfadf" # TODO C: Decrypt c1 and c2 using k1 and k2, and make sure that # des2_dec(k1, k2, c1 || c2) == m1 || m2 # TODO C.1: Convert k1, k2, c1, and c2 to bytes. It may make the exercise # easier to implement. Use string_to_bytes() for plain texts (i.e., string # in human-readable format), and bytes.fromhex() for hex strings. k1 = ... k2 = ... c1 = ... c2 = ... # NOTE: The code to decrypt c1 is already provided below. You **need** # to decrypt c2 as well. m1 = bytes_to_string(des2_dec(k1, k2, c1)) assert m1 == m1_given, f'Expected "{m1_given}", but got "{m1}"' print("ciphertext:", c1) print("plaintext:", m1) print("plaintext in hexa:", str_2_hex(m1)) # TODO C.2: Decrypt m2 similar to m1. Keep in mind that des_dec() # returns bytes m2 = ... print("ciphertext:", c2) print("plaintext:", m2) print("plaintext in hexa:", str_2_hex(m2)) # TODO C.3: Just to make sure you implemented the task correctly: # des2_dec(k1, k2, c1 || c2) == m1 || m2 m12 = ... assert m12 == m1 + m2, f'Expected "{m12}" to equal "{m1 + m2}"' def mitm() -> None: # TODO D: run meet-in-the-middle attack for the following plaintext/ciphertext m1 = "Pocainta" c1 = "9f98dbd6fe5f785d" m2 = "Iertarea" c2 = "6e266642ef3069c2" # TODO D.1: Convert m1, m2, c1, and c2 to bytes. It may make the exercise # easier to implement. Use string_to_bytes() for plain texts (i.e., string # in human-readable format), and bytes.fromhex() for hex strings. m1 = ... c1 = ... m2 = ... c2 = ... # NOTE: You only need to search for the first 2 bytes of the each key (i.e., # to find out what are the values for each `?`). k1 = "??oIkvH5" k2 = "??GK4EoU" # TODO D.2: Generate the table containing (k2, DES(k2, m1)), for every # possible k2. Use a List[Tuple[bytes, bytes]] for the table (see task # description above). # TODO D.3: Sort the table based on the ciphertexts. Extract the ciphertexts # in a list (see task description above). # TODO D.4: Perform binary search for all possible k1, such that . # Save the set of candidate keys (k1, k2) in a list. # TODO D.5: From the set of candidate keys, print the ones matching # the second constraint: 2DES(k1, k2, m2) == c2 def main() -> None: test_des2() mitm() if __name__ == "__main__": main()