This shows you the differences between two versions of the page.
ic:labs:05 [2020/11/06 12:19] acosmin.maria |
ic:labs:05 [2023/10/09 23:22] (current) razvan.smadu |
||
---|---|---|---|
Line 1: | Line 1: | ||
===== Laboratorul 05 - DES ===== | ===== Laboratorul 05 - DES ===== | ||
- | In this lab we'll do some exercises with DES and some of its variants, as we discussed in the last lecture [[https://drive.google.com/file/d/1Fjybv6k5QudRB1bkAi5shVUGlUyTO_0U/view|here]]. | + | În acest laborator vom face niște exerciții ce folosesc algoritmul DES și variații ale acestuia discutate la [[https://drive.google.com/file/d/1Fjybv6k5QudRB1bkAi5shVUGlUyTO_0U/view|curs]]. |
+ | Prezentarea PowerPoint pentru acest laborator poate fi găsită [[https://drive.google.com/file/d/1FU422fCHM24fRnMuzFd0OjhQqT6AQZ3X/view?usp=sharing|aici]]. | ||
+ | Puteți lucra acest laborator folosind platforma Google Colab, accesând [[https://colab.research.google.com/github/ACS-IC-labs/IC-labs/blob/main/labs/lab05/lab5.ipynb|acest]] link. | ||
+ | <hidden> | ||
+ | <spoiler Click pentru a vedea utils.py> | ||
<file python utils.py> | <file python utils.py> | ||
import base64 | import base64 | ||
- | + | from typing import Generator | |
- | # CONVERSION FUNCTIONS | + | |
- | def _chunks(string, chunk_size): | + | |
- | for i in range(0, len(string), chunk_size): | + | def _pad(data: str, size: int) -> str: |
- | yield string[i:i+chunk_size] | + | reminder = len(data) % size |
- | + | if reminder != 0: | |
- | # THIS ONE IS NEW | + | data = "0" * (size - reminder) + data |
- | def byte_2_bin(bval): | + | return data |
- | """ | + | |
- | Transform a byte (8-bit) value into a bitstring | + | |
+ | def _chunks(data: str, chunk_size: int) -> Generator[str, None, None]: | ||
+ | data = _pad(data, chunk_size) | ||
+ | for i in range(0, len(data), chunk_size): | ||
+ | yield data[i : i + chunk_size] | ||
+ | |||
+ | |||
+ | def _hex(data: int) -> str: | ||
+ | return format(data, "02x") | ||
+ | |||
+ | |||
+ | # Conversion functions | ||
+ | |||
+ | |||
+ | def byte_2_bin(bval: int) -> str: | ||
+ | """Converts a byte value to a binary string. | ||
+ | |||
+ | Args: | ||
+ | bval (int): | ||
+ | The byte value to be converted. It should be an integer between | ||
+ | 0 and 255. | ||
+ | |||
+ | Returns: | ||
+ | str: The binary string representation of the byte value, where each bit | ||
+ | is encoded as a character. The result has a fixed length of 8 characters | ||
+ | and is padded with leading zeros if necessary. | ||
+ | |||
+ | Examples: | ||
+ | >>> byte_2_bin(72) | ||
+ | '01001000' | ||
+ | >>> byte_2_bin(66) | ||
+ | '01000010' | ||
""" | """ | ||
return bin(bval)[2:].zfill(8) | return bin(bval)[2:].zfill(8) | ||
- | + | ||
- | def _hex(x): | + | |
- | return format(x, '02x') | + | def hex_2_bin(data: str) -> str: |
- | + | """Converts a hexadecimal string to a binary representation. | |
- | def hex_2_bin(data): | + | |
- | return ''.join(f'{int(x, 16):08b}' for x in _chunks(data, 2)) | + | Args: |
- | + | data (str): The hexadecimal string to be converted. It should have an | |
- | def str_2_bin(data): | + | even number of characters and only contain valid hexadecimal digits |
- | return ''.join(f'{ord(c):08b}' for c in data) | + | (0-9, A-F, a-f). |
- | + | ||
- | def bin_2_hex(data): | + | Returns: |
- | return ''.join(f'{int(b, 2):02x}' for b in _chunks(data, 8)) | + | str: The binary representation of the hexadecimal string, where each |
- | + | pair of hexadecimal digits is encoded as an 8-bit binary number. | |
- | def str_2_hex(data): | + | |
- | return ''.join(f'{ord(c):02x}' for c in data) | + | Examples: |
- | + | >>> hex_2_bin("01abcd") | |
- | def bin_2_str(data): | + | '000000011010101111001101' |
- | return ''.join(chr(int(b, 2)) for b in _chunks(data, 8)) | + | >>> hex_2_bin("0a") |
- | + | '00001010' | |
- | def hex_2_str(data): | + | """ |
- | return ''.join(chr(int(x, 16)) for x in _chunks(data, 2)) | + | return "".join(f"{int(x, 16):08b}" for x in _chunks(data, 2)) |
- | + | ||
- | # XOR FUNCTIONS | + | |
- | def strxor(a, b): # xor two strings, trims the longer input | + | def bin_2_hex(data: str) -> str: |
- | return ''.join(chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)) | + | """Converts a binary string to a hexadecimal representation. |
- | + | ||
- | def bitxor(a, b): # xor two bit-strings, trims the longer input | + | Args: |
- | return ''.join(str(int(x) ^ int(y)) for (x, y) in zip(a, b)) | + | data (str): The binary string to be converted. It should have a multiple |
- | + | of 8 characters and only contain valid binary digits (0 or 1). | |
- | def hexxor(a, b): # xor two hex-strings, trims the longer input | + | |
- | return ''.join(_hex(int(x, 16) ^ int(y, 16)) for (x, y) in zip(_chunks(a, 2), _chunks(b, 2))) | + | Returns: |
- | + | str: The hexadecimal representation of the binary string, where each | |
- | # BASE64 FUNCTIONS | + | group of 8 binary digits is encoded as a pair of hexadecimal digits. |
- | def b64decode(data): | + | |
- | return bytes_to_string(base64.b64decode(string_to_bytes(data))) | + | Examples: |
- | + | >>> bin_2_hex("000000011010101111001101") | |
- | def b64encode(data): | + | '01abcd' |
+ | >>> bin_2_hex("00001010") | ||
+ | '0a' | ||
+ | """ | ||
+ | return "".join(f"{int(b, 2):02x}" for b in _chunks(data, 8)) | ||
+ | |||
+ | |||
+ | def str_2_bin(data: str) -> str: | ||
+ | """Converts a string to a binary representation. | ||
+ | |||
+ | Args: | ||
+ | data (str): The string to be converted. | ||
+ | |||
+ | Returns: | ||
+ | str: The binary representation of the string, where each character is | ||
+ | encoded as an 8-bit binary number. | ||
+ | |||
+ | Examples: | ||
+ | >>> str_2_bin("Hello") | ||
+ | '0100100001100101011011000110110001101111' | ||
+ | >>> str_2_bin("IC") | ||
+ | '0100100101000011' | ||
+ | """ | ||
+ | return "".join(f"{ord(c):08b}" for c in data) | ||
+ | |||
+ | |||
+ | def bin_2_str(data: str) -> str: | ||
+ | """Converts a binary string to a string. | ||
+ | |||
+ | Args: | ||
+ | data (str): The binary string to be converted. It should have a multiple | ||
+ | of 8 characters and only contain valid binary digits (0 or 1). | ||
+ | |||
+ | Returns: | ||
+ | str: The string representation of the binary string, where each group | ||
+ | of 8 binary digits is decoded as a character. | ||
+ | |||
+ | Examples: | ||
+ | >>> bin_2_str("0100100001100101011011000110110001101111") | ||
+ | 'Hello' | ||
+ | >>> bin_2_str("0100100101000011") | ||
+ | 'IC' | ||
+ | """ | ||
+ | return "".join(chr(int(b, 2)) for b in _chunks(data, 8)) | ||
+ | |||
+ | |||
+ | def str_2_hex(data: str) -> str: | ||
+ | """Converts a string to a hexadecimal representation. | ||
+ | |||
+ | Args: | ||
+ | data (str): The string to be converted. | ||
+ | |||
+ | Returns: | ||
+ | str: The hexadecimal representation of the string, where each character | ||
+ | is encoded as a pair of hexadecimal digits. | ||
+ | |||
+ | Examples: | ||
+ | >>> str_2_hex("Hello") | ||
+ | '48656c6c6f' | ||
+ | >>> str_2_hex("IC") | ||
+ | '4943' | ||
+ | """ | ||
+ | return "".join(f"{ord(c):02x}" for c in data) | ||
+ | |||
+ | |||
+ | def hex_2_str(data: str) -> str: | ||
+ | """Converts a hexadecimal string to a string. | ||
+ | |||
+ | Args: | ||
+ | data (str): The hexadecimal string to be converted. It should have an | ||
+ | even number of characters and only contain valid hexadecimal digits | ||
+ | (0-9, A-F, a-f). | ||
+ | |||
+ | Returns: | ||
+ | str: The string representation of the hexadecimal string, where each | ||
+ | pair of hexadecimal digits is decoded as a character. | ||
+ | |||
+ | Examples: | ||
+ | >>> hex_2_str("48656c6c6f") | ||
+ | 'Hello' | ||
+ | >>> hex_2_str("4943") | ||
+ | 'IC' | ||
+ | """ | ||
+ | return "".join(chr(int(x, 16)) for x in _chunks(data, 2)) | ||
+ | |||
+ | |||
+ | # XOR functions | ||
+ | |||
+ | |||
+ | def strxor(operand_1: str, operand_2: str) -> str: | ||
+ | """Performs a bitwise exclusive OR (XOR) operation on two strings. | ||
+ | |||
+ | Args: | ||
+ | operand_1 (str): The first string to be XORed. | ||
+ | operand_2 (str): The second string to be XORed. | ||
+ | |||
+ | Returns: | ||
+ | str: The result of the XOR operation on the two strings, where each | ||
+ | character is encoded as an 8-bit binary number. The result has | ||
+ | the same length as the shorter input string. | ||
+ | |||
+ | Examples: | ||
+ | >>> strxor("Hello", "IC") | ||
+ | '\\x01&' | ||
+ | >>> strxor("secret", "key") | ||
+ | '\\x18\\x00\\x1a' | ||
+ | """ | ||
+ | return "".join(chr(ord(x) ^ ord(y)) for (x, y) in zip(operand_1, operand_2)) | ||
+ | |||
+ | |||
+ | def bitxor(operand_1: str, operand_2: str) -> str: | ||
+ | """Performs a bitwise exclusive OR (XOR) operation on two bit-strings. | ||
+ | |||
+ | Args: | ||
+ | operand_1 (str): The first bit-string to be XORed. It should only | ||
+ | contain valid binary digits (0 or 1). | ||
+ | operand_2 (str): The second bit-string to be XORed. It should only | ||
+ | contain valid binary digits (0 or 1). | ||
+ | |||
+ | Returns: | ||
+ | str: The result of the XOR operation on the two bit-strings, where each | ||
+ | bit is encoded as a character. The result has the same length as | ||
+ | the shorter input bit-string. | ||
+ | |||
+ | Examples: | ||
+ | >>> bitxor("01001000", "01000010") | ||
+ | '00001010' | ||
+ | >>> bitxor("10101010", "00110011") | ||
+ | '10011001' | ||
+ | """ | ||
+ | return "".join(str(int(x) ^ int(y)) for (x, y) in zip(operand_1, operand_2)) | ||
+ | |||
+ | |||
+ | def hexxor(operand_1: str, operand_2: str) -> str: | ||
+ | """Performs a bitwise exclusive OR (XOR) operation on two hexadecimal | ||
+ | strings. | ||
+ | |||
+ | Args: | ||
+ | operand_1 (str): The first hexadecimal string to be XORed. It should | ||
+ | have an even number of characters and only contain valid hexadecimal | ||
+ | digits (0-9, A-F, a-f). | ||
+ | operand_2 (str): The second hexadecimal string to be XORed. It should | ||
+ | have an even number of characters and only contain valid | ||
+ | digits (0-9, A-F, a-f). | ||
+ | |||
+ | Returns: | ||
+ | str: The result of the XOR operation on the two hexadecimal strings, | ||
+ | where each pair of hexadecimal digits is encoded as a pair of | ||
+ | hexadecimal digits. The result has the same length as the shorter | ||
+ | input hexadecimal string. | ||
+ | |||
+ | Examples: | ||
+ | >>> hexxor("48656c6c6f", "42696e67") | ||
+ | '0a0c020b' | ||
+ | >>> hexxor("736563726574", "6b6579") | ||
+ | '18001a' | ||
+ | """ | ||
+ | return "".join( | ||
+ | _hex(int(x, 16) ^ int(y, 16)) | ||
+ | for (x, y) in zip(_chunks(operand_1, 2), _chunks(operand_2, 2)) | ||
+ | ) | ||
+ | |||
+ | |||
+ | # Python3 'bytes' functions | ||
+ | |||
+ | |||
+ | def bytes_to_string(bytes_data: bytearray | bytes) -> str: | ||
+ | """Converts a byte array or a byte string to a string. | ||
+ | |||
+ | Args: | ||
+ | bytes_data (bytearray | bytes): The byte array or the byte string to be | ||
+ | converted. It should be encoded in UTF-8 format. | ||
+ | |||
+ | Returns: | ||
+ | str: The string representation of the byte array or the byte string, | ||
+ | decoded using UTF-8 encoding. | ||
+ | |||
+ | Examples: | ||
+ | >>> bytes_to_string(b'Hello') | ||
+ | 'Hello' | ||
+ | >>> bytes_to_string(bytearray(b'IC')) | ||
+ | 'IC' | ||
+ | """ | ||
+ | return bytes_data.decode(encoding="utf-8") | ||
+ | |||
+ | |||
+ | def string_to_bytes(string_data: str) -> bytes: | ||
+ | """Converts a string to a byte string. | ||
+ | |||
+ | Args: | ||
+ | string_data (str): The string to be converted. | ||
+ | |||
+ | Returns: | ||
+ | bytes: The byte string representation of the string, encoded using | ||
+ | UTF-8 encoding. | ||
+ | |||
+ | Examples: | ||
+ | >>> string_to_bytes('Hello') | ||
+ | b'Hello' | ||
+ | >>> string_to_bytes('IC') | ||
+ | b'IC' | ||
+ | """ | ||
+ | return string_data.encode(encoding="utf-8") | ||
+ | |||
+ | |||
+ | # Base64 functions | ||
+ | |||
+ | |||
+ | def b64encode(data: str) -> str: | ||
+ | """Encodes a string to base64. | ||
+ | |||
+ | Parameters: | ||
+ | data (str): The string to be encoded. | ||
+ | |||
+ | Returns: | ||
+ | str: The base64 encoded string, using UTF-8 encoding. | ||
+ | |||
+ | Examples: | ||
+ | >>> b64encode("Hello") | ||
+ | 'SGVsbG8=' | ||
+ | >>> b64encode("IC") | ||
+ | 'SUM=' | ||
+ | """ | ||
return bytes_to_string(base64.b64encode(string_to_bytes(data))) | return bytes_to_string(base64.b64encode(string_to_bytes(data))) | ||
- | |||
- | # PYTHON3 'BYTES' FUNCTIONS | ||
- | def bytes_to_string(bytes_data): | ||
- | return bytes_data.decode() # default utf-8 | ||
- | |||
- | def string_to_bytes(string_data): | ||
- | return string_data.encode() # default utf-8 | ||
- | </file> | ||
- | ==== Exercise 1 (2p) ==== | ||
- | Remember DESX defined as the operation DESX( (k1,k2,k3), m) = k1 ⊕ DES(k2, m ⊕ k3). | + | def b64decode(data: str) -> str: |
- | Show an attack on DESX that runs in time 2<sup>120</sup>. | + | """Decodes a base64 encoded string. |
+ | |||
+ | Args: | ||
+ | data (str): The base64 encoded string to be decoded. It should only | ||
+ | contain valid base64 characters (A-Z, a-z, 0-9, +, /, =). | ||
+ | |||
+ | Returns: | ||
+ | str: The decoded string, using UTF-8 encoding. | ||
+ | |||
+ | Examples: | ||
+ | >>> b64decode("SGVsbG8=") | ||
+ | 'Hello' | ||
+ | >>> b64decode("SUM=") | ||
+ | 'IC' | ||
+ | """ | ||
+ | return bytes_to_string(base64.b64decode(string_to_bytes(data))) | ||
+ | </file> | ||
+ | </spoiler> | ||
+ | ==== Exercițiul 1 (2p) ==== | ||
+ | |||
+ | DESX este definit sub forma DESX( (k1,k2,k3), m) = k1 ⊕ DES(k2, m ⊕ k3). | ||
+ | Construiți un atac împotriva DESX care să ruleze într-un timp proporțional cu 2<sup>120</sup>. | ||
- | ==== Exercise 2 (3p) ==== | + | ==== Exercițiul 2 (3p) ==== |
- | Show why the following schemes do not bring any real advantage compared to DES: | + | De ce următoarele scheme nu aduc nicio îmbunătățire față de DES? Justificați! |
* a) c = k1 ⊕ DES(k2, m) | * a) c = k1 ⊕ DES(k2, m) | ||
* b) c = DES(k2, m ⊕ k1) | * b) c = DES(k2, m ⊕ k1) | ||
- | <note tip>You can try to use multiple (message, ciphertext) pairs.</note> | + | <note tip>Puteți folosi mai multe perechi (mesaj, ciphertext).</note> |
- | ==== Exercise 3 (5p) ==== | + | |
- | The goal of this exercise is to implement the meet-in-the-middle attack on double DES. | + | ==== Exercițiul 3 (5p) ==== |
- | For this, you are given a starter code (see below), implemented using the pycrypto library. | + | |
- | Perform the following tasks: | + | Scopul acestui exercițiu este de a implementa atacul meet-in-the-middle pe 2DES. |
+ | Porniți de la scheletul de laborator (vezi mai jos) care se folosește de biblioteca pycrypto. | ||
- | === A. Install the pycrypto library === | + | Realizați următoarele sarcini: |
- | See https://pypi.org/project/pycryptodome/ | + | === A. Instalați biblioteca pycrypto === |
+ | Vezi https://pypi.org/project/pycryptodome/ | ||
- | === B. Implement 2DES === | ||
- | Starting from the starter code (see below), write methods to encrypt and decrypt using double-DES (2DES), defined as follows: 2DES( (k1,k2), m) = DES(k1, DES(k2, m)) | + | === B. Implementați 2DES (1p) === |
- | === C. Test 2DES === | + | Pornind de la schelet, implementați metodele des2_enc si des2_dec folosind double-DES (2DES). |
- | You are given the ciphertexts | + | 2DES( (k1,k2), m) = DES(k1, DES(k2, m)) |
+ | |||
+ | === C. Testați 2DES (1p) === | ||
+ | |||
+ | Folosiți următoarele ciphertexts | ||
c1 = 'cda98e4b247612e5b088a803b4277710f106beccf3d020ffcc577ddd889e2f32' | c1 = 'cda98e4b247612e5b088a803b4277710f106beccf3d020ffcc577ddd889e2f32' | ||
Line 101: | Line 373: | ||
c2 = '54826ea0937a2c34d47f4595f3844445520c0995331e5d492f55abcf9d8dfadf' | c2 = '54826ea0937a2c34d47f4595f3844445520c0995331e5d492f55abcf9d8dfadf' | ||
- | Decrypt them using the following keys: | + | Decriptați-le folosind cheile: |
k1 = 'Smerenie' | k1 = 'Smerenie' | ||
Line 107: | Line 379: | ||
k2 = 'Dragoste' | k2 = 'Dragoste' | ||
- | The plaintext corresponding to c1 is m1='Fericiti cei saraci cu duhul, ca'. Find the plaintext m2 corresponding to c2. | + | Primul plaintext corespunzator lui c1 este m1='Fericiti cei saraci cu duhul, ca'. Găsiți al doilea plaintext m2 ce îi corespunde lui c2. |
- | <note tip> | + | <note> |
- | With the Pycrypto library, DES is given a key in 8 bytes (64 bits) rather than 7 (56 bits). However, the last bit in each byte is considered as a parity bit, but in fact ignored in this library, leaving the actual key in 56 bits. For this and the following exercises, we assume the entire key as 64 bits, given for example as 8 characters, as above. | + | În biblioteca Pycrypto, DES primește o cheie pe 8 octeți (64 biți) chiar dacă ar trebui să primeasca numai 7 octeti (56 biti). Ultimul bit din fiecare octet este considerat ca fiind un bit de paritate, dar este ignorat de bibliotecă. Astfel cheia ce se folosește va fi într-adevăr de dimensiunea 56 biți. Pentru simplitate însă, în acest exercițiu vom considera că dimensiunea cheiei este de 64 biți. Astfel putem folosi cheile de 8 caractere de mai sus pentru DES. |
- | Note also that for this exercises, we shall be using the default values when initialising the DES cipher (i.e. ECB mode and no IV). | + | Vom considera de asemenea că inițializăm DES-ul cu valorile default (i.e. modul ECB și niciun IV). |
</note> | </note> | ||
+ | Decriptați întregul ciphertext (c1 || c2) cu cheile k1 și k2 folosind 2DES și asigurați-vă că rezultatul dat este mesajul inițial m1||m2. | ||
- | Decrypt the entire ciphertext (c1 || c2) with k1 and k2 using 2DES and check it matches the messages m1||m2 above. | + | === D. Implementați atacul meet-in-the-middle pentru 2DES (3p) === |
- | === D. Implement the meet-in-the-middle attack on 2DES === | + | În ultimul și cel mai important pas vi se dau niște perechi plaintext/ciphertext obținute prin criptarea cu 2DES cu niște chei necunoscute: |
- | In this last but most important task, you are given the following ciphertext/plaintext pairs for 2DES with unknown keys: | + | m1 = 'Pocainta' (în byte string, i.e. poate fi dat direct ca parametru către pycrypto DES) |
- | m1 = 'Pocainta' (in byte string, i.e. can be used directly with pycrypto DES) | + | c1 = '9f98dbd6fe5f785d' (în hex string, trebuie să îl decodificați mai întâi) |
- | + | ||
- | c1 = '9f98dbd6fe5f785d' (in hex string, you need to hex-decode) | + | |
m2 = 'Iertarea' | m2 = 'Iertarea' | ||
Line 130: | Line 401: | ||
c2 = '6e266642ef3069c2' | c2 = '6e266642ef3069c2' | ||
- | You also know the last 6-bytes of each key (note we now have a different key than for the previous tasks): | + | Vi se mai dau și ultimii 6 bytes ale celor 2 chei (care sunt altele decat cele folosite la pașii anteriori): |
k1 (last 6 bytes) = 'oIkvH5' | k1 (last 6 bytes) = 'oIkvH5' | ||
+ | |||
k2 (last 6 bytes) = 'GK4EoU' | k2 (last 6 bytes) = 'GK4EoU' | ||
- | Your task is now to find the full keys k1 and k2 by applying the meet-in-the-middle attack over 2DES. | + | Sarcina voastră este să găsiți restul octeților din k1 și k2 aplicând atacul meet-in-the-middle. |
- | To build a table, we recommend using a list of tuples, where you add new (key,enc) pairs as follows: | + | Pentru a construi un tabel, recomandăm să folosiți o lista de tupluri unde adăugați perechi de forma (cheie, encripție) ca în următorul exemplu: |
<code> | <code> | ||
tb = [] | tb = [] | ||
Line 142: | Line 415: | ||
</code> | </code> | ||
- | To sort the table, you can do this: | + | Pentru a sorta tabelul: |
<code> | <code> | ||
tbs = sorted(tb, key=itemgetter(1)) | tbs = sorted(tb, key=itemgetter(1)) | ||
</code> | </code> | ||
- | To search with binary search, first select just the second column (to search the encryptions): | + | Pentru a realiza căutarea binară în tabel, selectați mai întâi a doua coloană (pentru a căuta după encripție): |
<code> | <code> | ||
tenc = [value for _,value in tbs] | tenc = [value for _,value in tbs] | ||
</code> | </code> | ||
- | then use the bisect library (e.g. bisect.bisect_left) | + | iar apoi folosiți biblioteca bisect (e.g. bisect.bisect_left): |
https://docs.python.org/2/library/bisect.html | https://docs.python.org/2/library/bisect.html | ||
- | The starter code is this: | + | Scheletul de cod: |
- | <code python desmitm.py> | + | <spoiler Click pentru a vedea lab05.py> |
- | from utils import * | + | <file python lab05.py> |
- | from operator import itemgetter | + | |
import bisect | import bisect | ||
+ | from operator import itemgetter | ||
+ | from typing import List | ||
+ | |||
from Crypto.Cipher import DES | from Crypto.Cipher import DES | ||
+ | from utils import * | ||
- | def get_index(a, x): | + | |
- | """Locate the leftmost value exactly equal to x in list a""" | + | 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) | i = bisect.bisect_left(a, x) | ||
if i != len(a) and a[i] == x: | if i != len(a) and a[i] == x: | ||
Line 170: | Line 456: | ||
return -1 | return -1 | ||
- | def des_enc(k, m): | + | |
+ | def des_enc(k: bytes, m: bytes) -> bytes: | ||
""" | """ | ||
Encrypt a message m with a key k using DES as follows: | Encrypt a message m with a key k using DES as follows: | ||
c = DES(k, m) | c = DES(k, m) | ||
- | |||
- | Args: | ||
- | m should be a bytestring (i.e. a sequence of characters such as 'Hello' or '\x02\x04') | ||
- | k should be a bytestring of length exactly 8 bytes. | ||
Note that for DES the key is given as 8 bytes, where the last bit of | 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. | + | each byte is just a parity bit, giving the actual key of 56 bits, as |
- | The parity bits are ignored. | + | 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: | Return: | ||
- | The bytestring ciphertext c | + | bytes: The bytestring ciphertext c |
""" | """ | ||
d = DES.new(k, DES.MODE_ECB) | d = DES.new(k, DES.MODE_ECB) | ||
Line 190: | Line 478: | ||
return c | return c | ||
- | def des_dec(k, c): | + | |
+ | def des_dec(k: bytes, c: bytes) -> bytes: | ||
""" | """ | ||
Decrypt a message c with a key k using DES as follows: | Decrypt a message c with a key k using DES as follows: | ||
m = DES(k, c) | m = DES(k, c) | ||
- | |||
- | Args: | ||
- | c should be a bytestring (i.e. a sequence of characters such as 'Hello' or '\x02\x04') | ||
- | k should be a bytestring of length exactly 8 bytes. | ||
Note that for DES the key is given as 8 bytes, where the last bit of | 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. | + | each byte is just a parity bit, giving the actual key of 56 bits, as |
- | The parity bits are ignored. | + | 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: | Return: | ||
- | The bytestring plaintext m | + | bytes: The bytestring plaintext m |
""" | """ | ||
d = DES.new(k, DES.MODE_ECB) | d = DES.new(k, DES.MODE_ECB) | ||
m = d.decrypt(c) | m = d.decrypt(c) | ||
return m | return m | ||
- | | ||
- | def main(): | ||
- | # Exercitiu pentru test des2_enc | ||
- | key1 = 'Smerenie' | ||
- | key2 = 'Dragoste' | ||
- | m1_given = 'Fericiti cei saraci cu duhul, ca' | ||
- | c1 = 'cda98e4b247612e5b088a803b4277710f106beccf3d020ffcc577ddd889e2f32' | ||
- | # TODO: implement des2_enc and des2_dec | ||
- | m1 = des2_dec(key1, key2, hex_2_str(c1)) | ||
- | print('ciphertext: ' + c1) | ||
- | print('plaintext: ' + m1) | ||
- | print('plaintext in hexa: ' + str_2_hex(m1)) | ||
- | | ||
- | # TODO: run meet-in-the-middle attack for the following plaintext/ciphertext | ||
- | m1 = 'Pocainta' | ||
- | c1 = '9f98dbd6fe5f785d' # in hex string | ||
- | m2 = 'Iertarea' | ||
- | c2 = '6e266642ef3069c2' | ||
- | | ||
- | # Note: you only need to search for the first 2 bytes of the each key: | ||
- | k1 = '??oIkvH5' | ||
- | k2 = '??GK4EoU' | ||
- | if __name__ == "__main__": | + | def des2_enc(k1: bytes, k2: bytes, m: bytes) -> bytes: |
- | main() | + | # TODO B.1: implement des2_enc |
- | </code> | + | 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()</file> | ||
+ | </spoiler> | ||
+ | </hidden> |