This shows you the differences between two versions of the page.
ic:labs:04 [2021/10/14 23:08] philip.dumitru [SPN 2 (3p)] |
ic:labs:04 [2023/10/09 23:21] (current) razvan.smadu |
||
---|---|---|---|
Line 3: | Line 3: | ||
Prezentarea PowerPoint pentru acest laborator poate fi găsită [[https://drive.google.com/file/d/1mjbkxFqYNB-lRRXGAhsdh1vK9nwbnDuS/view?usp=sharing|aici]]. | Prezentarea PowerPoint pentru acest laborator poate fi găsită [[https://drive.google.com/file/d/1mjbkxFqYNB-lRRXGAhsdh1vK9nwbnDuS/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/lab04/lab4.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): | ||
- | yield string[i:i+chunk_size] | ||
- | # THIS ONE IS NEW | + | def _pad(data: str, size: int) -> str: |
- | def byte_2_bin(bval): | + | reminder = len(data) % size |
- | """ | + | if reminder != 0: |
- | Transform a byte (8-bit) value into a bitstring | + | data = "0" * (size - reminder) + data |
+ | return data | ||
+ | |||
+ | |||
+ | 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): | + | def hex_2_bin(data: str) -> str: |
- | return ''.join(f'{int(x, 16):08b}' for x in _chunks(data, 2)) | + | """Converts a hexadecimal string to a binary representation. |
- | def str_2_bin(data): | + | Args: |
- | return ''.join(f'{ord(c):08b}' for c in data) | + | 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). | ||
- | 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): | + | Examples: |
- | return ''.join(f'{ord(c):02x}' for c in data) | + | >>> hex_2_bin("01abcd") |
+ | '000000011010101111001101' | ||
+ | >>> hex_2_bin("0a") | ||
+ | '00001010' | ||
+ | """ | ||
+ | return "".join(f"{int(x, 16):08b}" for x in _chunks(data, 2)) | ||
- | def bin_2_str(data): | ||
- | return ''.join(chr(int(b, 2)) for b in _chunks(data, 8)) | ||
- | def hex_2_str(data): | + | def bin_2_hex(data: str) -> str: |
- | return ''.join(chr(int(x, 16)) for x in _chunks(data, 2)) | + | """Converts a binary string to a hexadecimal representation. |
- | # XOR FUNCTIONS | + | Args: |
- | def strxor(a, b): # xor two strings, trims the longer input | + | data (str): The binary string to be converted. It should have a multiple |
- | return ''.join(chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)) | + | of 8 characters and only contain valid binary digits (0 or 1). |
- | def bitxor(a, b): # xor two bit-strings, trims the longer input | + | Returns: |
- | return ''.join(str(int(x) ^ int(y)) for (x, y) in zip(a, b)) | + | str: The hexadecimal representation of the binary string, where each |
+ | group of 8 binary digits is encoded as a pair of hexadecimal digits. | ||
- | def hexxor(a, b): # xor two hex-strings, trims the longer input | + | Examples: |
- | return ''.join(_hex(int(x, 16) ^ int(y, 16)) for (x, y) in zip(_chunks(a, 2), _chunks(b, 2))) | + | >>> bin_2_hex("000000011010101111001101") |
+ | '01abcd' | ||
+ | >>> bin_2_hex("00001010") | ||
+ | '0a' | ||
+ | """ | ||
+ | return "".join(f"{int(b, 2):02x}" for b in _chunks(data, 8)) | ||
- | # BASE64 FUNCTIONS | ||
- | def b64decode(data): | ||
- | return bytes_to_string(base64.b64decode(string_to_bytes(data))) | ||
- | def b64encode(data): | + | 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 Latin-1 format. | ||
+ | |||
+ | Returns: | ||
+ | str: The string representation of the byte array or the byte string, | ||
+ | decoded using Latin-1 encoding. | ||
+ | |||
+ | Examples: | ||
+ | >>> bytes_to_string(b'Hello') | ||
+ | 'Hello' | ||
+ | >>> bytes_to_string(bytearray(b'IC')) | ||
+ | 'IC' | ||
+ | """ | ||
+ | return bytes_data.decode(encoding="raw_unicode_escape") | ||
+ | |||
+ | |||
+ | 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 | ||
+ | Latin-1 encoding. | ||
+ | |||
+ | Examples: | ||
+ | >>> string_to_bytes('Hello') | ||
+ | b'Hello' | ||
+ | >>> string_to_bytes('IC') | ||
+ | b'IC' | ||
+ | """ | ||
+ | return string_data.encode(encoding="raw_unicode_escape") | ||
+ | |||
+ | |||
+ | # 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 Latin-1 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): | + | def b64decode(data: str) -> str: |
- | return string_data.encode() # default utf-8 | + | """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 Latin-1 encoding. | ||
+ | |||
+ | Examples: | ||
+ | >>> b64decode("SGVsbG8=") | ||
+ | 'Hello' | ||
+ | >>> b64decode("SUM=") | ||
+ | 'IC' | ||
+ | """ | ||
+ | return bytes_to_string(base64.b64decode(string_to_bytes(data))) | ||
</file> | </file> | ||
+ | </spoiler> | ||
+ | <note tip> | ||
In acest laborator veti avea niste exercitii ce folosesc PRFs, PRPs si DES. Puteti sa va uitati in cursul de [[https://drive.google.com/file/d/1Fjybv6k5QudRB1bkAi5shVUGlUyTO_0U/view?usp=sharing|aici]] ca sa va aduceti aminte lucrurile de baza. | In acest laborator veti avea niste exercitii ce folosesc PRFs, PRPs si DES. Puteti sa va uitati in cursul de [[https://drive.google.com/file/d/1Fjybv6k5QudRB1bkAi5shVUGlUyTO_0U/view?usp=sharing|aici]] ca sa va aduceti aminte lucrurile de baza. | ||
+ | </note> | ||
+ | |||
+ | ==== Exercițiul 1 (4p) ==== | ||
- | ==== Exercise 1 (4p) ==== | ||
Fie $F : K × X \to Y$ un $PRF$ sigur cu $K = X = Y = \{0, 1\}^{n}$. | Fie $F : K × X \to Y$ un $PRF$ sigur cu $K = X = Y = \{0, 1\}^{n}$. | ||
- | * a) Aratati ca $F_1(k,x) = F(k,x) \| 0$ nu e un $PRF$ sigur. | + | * a) Arătați că $F_1(k,x) = F(k,x) \| 0$ nu e un $PRF$ sigur. |
- | * b) Aratati ca $F_2(k, x) = F \left(k, x \oplus 1^{n}\right)$ este un $PRF$ sigur. | + | * b) Arătați că $F_2(k, x) = F \left(k, x \oplus 1^{n}\right)$ este un $PRF$ sigur. |
<note tip> | <note tip> | ||
- | Hint: Ca sa aratati ca e sigur, folositi metoda reducerii la absurd: existenta unui adversar $A$ care sparge $F_2$ implica existenta unui alt adversar $B$ care care sparge $F$ intr-un timp similar. | + | Hint: Ca să arătați ca e sigur, folosiți metoda reducerii la absurd: existența unui adversar $A$ care sparge $F_2$ implică existența unui alt adversar $B$ care care sparge $F$ într-un timp similar. |
</note> | </note> | ||
- | * c) Fie $K_3 = \{0, 1\}^{n+1}$. Construiti un nou $PRF$ $F_3 : K_3 \times X \to Y$ care sa aiba urmatoarea prorprietate: $PRF$-ul $F_3$ este sigur, dar daca adversarul afla ultimul bit din cheie atunci $PRF$-ul nu mai e sigur. Puteti astfel observa ca pana si aflarea unui singur bit din cheia secreta poate compromite siguranta unui $PRF$ . | + | * c) Fie $K_3 = \{0, 1\}^{n+1}$. Construiți un nou $PRF$ $F_3 : K_3 \times X \to Y$ care să aibă urmatoarea proprietate: $PRF$-ul $F_3$ este sigur, dar dacă adversarul află ultimul bit din cheie atunci $PRF$-ul nu mai e sigur. Puteți astfel observa că până și aflarea unui singur bit din cheia secretă poate compromite siguranța unui $PRF$ . |
<note tip> | <note tip> | ||
- | Hint: Fie $k_3 = k \| b$ unde $k \in \{0,1\}^{n}$ si $b \in \{0,1\}$. Definiti $F_3(k_3,x)$ ca fiind identic cu $F (k, x)$ pentru toate $x \neq 0^{n}$. Acum trebuie sa mai definit $F_3\left(k_3, 0^{n}\right)$ astfel incat $F_3$ este un PRF sigur, dar poate fi diferentiat de o functie aleatoare daca ultimul bit din cheie este cunoscut. Aratati ca $F_3$ e sigur prin reducere la absurd. | + | Hint: Fie $k_3 = k \| b$ unde $k \in \{0,1\}^{n}$ și $b \in \{0,1\}$. Definiți $F_3(k_3,x)$ ca fiind identic cu $F (k, x)$ pentru toate $x \neq 0^{n}$. Acum trebuie să mai definim $F_3\left(k_3, 0^{n}\right)$ astfel încât $F_3$ este un PRF sigur, dar poate fi diferențiat de o funcție aleatoare dacă ultimul bit din cheie este cunoscut. Arătați că $F_3$ e sigur prin reducere la absurd. |
</note> | </note> | ||
- | * d) Construiti un nou $PRF$ $F_4 : K × X \to Y$ care ramane sigur chiar si atunci cand atacatorul cunoaste unul (oricare) dintre bitii cheiei. Functia voastra $F_4$ trebuie sa cheme o singura data functia $F$. Explicati de ce ramane un $PRF$ sigur chiar si dupa aceasta scurgere de informatie. | + | * d) Construiți un nou $PRF$ $F_4 : K × X \to Y$ care rămâne sigur chiar și atunci când atacatorul cunoaște unul (oricare) dintre biții cheii. Funcția voastră $F_4$ trebuie să cheme o singură dată funcția $F$. Explicați de ce rămâne un $PRF$ sigur chiar și după această scurgere de informație. |
- | <note>Pentru doua siruri de biti $y$ si $z$ folosim $y \| z$ pentru a indica concatenarea intre $y$ si $z$.</note> | + | <note>Pentru două șiruri de biți $y$ și $z$ folosim $y \| z$ pentru a indica concatenarea între $y$ și $z$.</note> |
- | ==== Exercise 2-3-4 ==== | + | ==== Exercițiile 2-3-4 ==== |
- | Vom analiza acum retele substitutie-permutare (SPN - substitution-permutation networks) | + | Vom analiza acum rețele substituție-permutare (SPN - substitution-permutation networks) |
==== SPN 1 (3p) ==== | ==== SPN 1 (3p) ==== | ||
- | Avem urmatorul SPN din figura: | + | Avem următorul SPN din figură: |
{{:sasc:laboratoare:spn_1r_reduced_2s.png|}} | {{:sasc:laboratoare:spn_1r_reduced_2s.png|}} | ||
- | unde S este un S-box din AES (detalii vor fi prezentate la curs), iar 'Permutation' este un bloc care doar sifteaza toti bitii cu 4 pozitii la dreapta in mod ciclic. Atat S-boxurile cat si permutarea sunt inversabile si cunoscute de atacator (de voi). Fiecare intrare (x1, x2) are 8 biti (1 byte). Cheile k1, k2, si iesirile y1, y2 au de asemenea dimensiunea de 1 byte. | + | unde S este un S-box din AES (detalii vor fi prezentate la curs), iar 'Permutation' este un bloc care doar șiftează toți biții cu 4 poziții la dreapta în mod ciclic. Atât S-boxurile cât și permutarea sunt inversabile și cunoscute de atacator (de voi). Fiecare intrare (x1, x2) are 8 biți (1 byte). Cheile k1, k2, și ieșirile y1, y2 au de asemenea dimensiunea de 1 byte. |
- | - Cum puteti determina cheia? | + | - Cum puteți determina cheia? |
- | - Fiind data o pereche mesaj/ciphertext ('Hi' - char, 0xba52 - hex), gasiti k1 si k2. Afisati-le in ASCII. | + | - Fiind dată o pereche mesaj/ciphertext ('Hi' - char, 0xba52 - hex), găsiți k1 și k2. Afișați-le în ASCII. |
<note tip> | <note tip> | ||
- | Puteti porni de la urmatorul schelet de cod: | + | Puteți porni de la urmatorul schelet de cod: |
- | </note> | + | |
+ | <spoiler Click pentru a vedea spn1.py> | ||
<code python spn1.py> | <code python spn1.py> | ||
+ | from typing import List | ||
+ | |||
from utils import * | from utils import * | ||
+ | # fmt: off | ||
# Rijndael S-box | # Rijndael S-box | ||
sbox = [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, | sbox = [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, | ||
Line 154: | Line 431: | ||
0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, | 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, | ||
0x21, 0x0c, 0x7d] | 0x21, 0x0c, 0x7d] | ||
+ | # fmt: on | ||
- | def permute4(s): | ||
- | """ | ||
- | Perform a permutatation by shifting all bits 4 positions right. | ||
- | The input is assumed to be a 16-bit bitstring | ||
- | """ | ||
- | ps = '' | ||
- | ps = ps + s[12:16] | ||
- | ps = ps + s[0:12] | ||
- | return ps | ||
- | def permute_inv4(s): | + | def permute4(s: str) -> str: |
- | """ | + | """Perform a permutatation by shifting all bits 4 positions right. |
- | Perform the inverse of permute4 | + | |
- | The input is assumed to be a 16-bit bitstring | + | |
- | """ | + | |
- | ps = '' | + | |
- | ps = ps + s[4:16] | + | |
- | ps = ps + s[0:4] | + | |
- | return ps | + | |
+ | Args: | ||
+ | s (str): The input is assumed to be a 16-bit bitstring. | ||
- | def spn_1r_reduced_2s(k, x): | + | Raises: |
+ | TypeError: Expected input to be a string. | ||
+ | ValueError: Expected input to have length 16. | ||
+ | |||
+ | Returns: | ||
+ | str: The permuted string by shifting all bits 4 positions right. | ||
""" | """ | ||
- | Performs an encryption with a substitution-permutation network. | + | if not isinstance(s, str): |
- | Key k = {k1, k2}, total of 16 bits (2 x 8 bits) | + | raise TypeError( |
- | Input x = {x1, x2}, total of 16 bits (2 x 8 bits) | + | f"Invalid type for the input. Expected `str`, but got `{type(s)}`." |
- | Both k and x are assumed to be bitstrings. | + | ) |
+ | if len(s) != 16: | ||
+ | raise ValueError( | ||
+ | f"Invalit input length. Expected to be 16, but got {len(s)}." | ||
+ | ) | ||
+ | return s[12:16] + s[0:12] | ||
- | Return: | + | |
- | a 16-bit bitstring containing the encryption y = {y1, y2} | + | def permute_inv4(s: str) -> str: |
+ | """Perform the inverse of permute4. | ||
+ | |||
+ | Args: | ||
+ | s (str): The input is assumed to be a 16-bit bitstring. | ||
+ | |||
+ | Raises: | ||
+ | TypeError: Expected input to be a string. | ||
+ | ValueError: Expected input to have length 16. | ||
+ | |||
+ | Returns: | ||
+ | str: The permuted string by shifting all bits 4 positions left. | ||
""" | """ | ||
+ | if not isinstance(s, str): | ||
+ | raise TypeError( | ||
+ | f"Invalid type for the input. Expected `str`, but got `{type(s)}`." | ||
+ | ) | ||
+ | if len(s) != 16: | ||
+ | raise ValueError( | ||
+ | f"Invalit input length. Expected to be 16, but got {len(s)}." | ||
+ | ) | ||
+ | return s[4:16] + s[0:4] | ||
+ | |||
+ | def spn_1r_reduced_2s(k: str, x: str) -> str: | ||
+ | """Performs an encryption with a substitution-permutation network. | ||
+ | |||
+ | Args: | ||
+ | k (str): Key k = {k1, k2}, total of 16 bits (2 x 8 bits). Assumed to be | ||
+ | bitstring. | ||
+ | x (str): Input x = {x1, x2}, total of 16 bits (2 x 8 bits). Assumed to | ||
+ | be bitstring. | ||
+ | |||
+ | Returns: | ||
+ | str: a 16-bit bitstring containing the encryption y = {y1, y2} | ||
+ | """ | ||
# Split input and key | # Split input and key | ||
- | x1 = x[0:8] | + | x1, x2 = x[0:8], x[8:16] |
- | x2 = x[8:16] | + | k1, k2 = k[0:8], k[8:16] |
- | k1 = k[0:8] | + | |
- | k2 = k[8:16] | + | |
# Apply S-box | # Apply S-box | ||
Line 211: | Line 516: | ||
- | def spn_1r_full_2s(k, x): | + | def spn_1r_full_2s(k: str, x: str) -> str: |
- | """ | + | """Performs an encryption with a substitution-permutation network. |
- | Performs an encryption with a substitution-permutation network. | + | |
- | Key k = {k1, k2, k3, k4}, total of 32 bits (4 x 8 bits) | + | |
- | Input x = {x1, x2}, total of 16 bits (2 x 8 bits) | + | |
- | Both k and x are assumed to be bitstrings. | + | |
- | Return: | + | Args: |
- | a 16-bit bitstring containing the encryption y = {y1, y2} | + | k (str): Key k = {k1, k2, k3, k4}, total of 32 bits (4 x 8 bits). |
+ | Assumed to be bitstring. | ||
+ | x (str): Input x = {x1, x2}, total of 16 bits (2 x 8 bits). | ||
+ | Assumed to be bitstring. | ||
+ | |||
+ | Returns: | ||
+ | str: a 16-bit bitstring containing the encryption y = {y1, y2} | ||
""" | """ | ||
- | |||
# Split input and key | # Split input and key | ||
- | x1 = x[0:8] | + | x1, x2 = x[0:8], x[8:16] |
- | x2 = x[8:16] | + | k1, k2, k3, k4 = k[0:8], k[8:16], k[16:24], k[24:32] |
- | k1 = k[0:8] | + | |
- | k2 = k[8:16] | + | |
- | k3 = k[16:24] | + | |
- | k4 = k[24:32] | + | |
# Apply S-box | # Apply S-box | ||
Line 249: | Line 551: | ||
y2 = bitxor(po2, k4) | y2 = bitxor(po2, k4) | ||
- | return y1+y2 | + | return y1 + y2 |
- | def main(): | ||
+ | |||
+ | def main() -> None: | ||
# Run reduced 2-byte SPN | # Run reduced 2-byte SPN | ||
- | msg = 'Hi' | + | msg = "Hi" |
- | key = '??' # Find this | + | key = "??" # Find the key that should replace ?? |
- | xs = str_2_bin(msg) | + | |
ks = str_2_bin(key) | ks = str_2_bin(key) | ||
+ | |||
+ | # TODO 1: Find the key in the reduced 2-byte SPN | ||
+ | |||
+ | |||
+ | xs = str_2_bin(msg) | ||
ys = spn_1r_reduced_2s(ks, xs) | ys = spn_1r_reduced_2s(ks, xs) | ||
- | print('Two y halves of reduced SPN: ' + ys[0:8] + ' (hex: ' + bin_2_hex( | + | print( |
- | ys[0:8]) + '), ' + ys[8:16] + ' (hex: ' + bin_2_hex(ys[8:16]) + ')') | + | "Two y halves of reduced SPN:" |
+ | f" {ys[0:8]} (hex: {bin_2_hex(ys[0:8])})," | ||
+ | f" {ys[8:16]} (hex: {bin_2_hex(ys[8:16])})" | ||
+ | ) | ||
# Run full 2-byte SPN | # Run full 2-byte SPN | ||
- | msg = 'Om' | + | msg = "Om" |
- | key = '????' # Find this | + | key = "????" # Find the key that should replace ???? |
- | xs = str_2_bin(msg) | + | |
ks = str_2_bin(key) | ks = str_2_bin(key) | ||
+ | |||
+ | # TODO 2: Find the key in the full 2-byte SPN | ||
+ | |||
+ | xs = str_2_bin(msg) | ||
ys = spn_1r_full_2s(ks, xs) | ys = spn_1r_full_2s(ks, xs) | ||
- | print('Two y halves of full SPN (2 bytes): ' + ys[0:8] + ' (hex: ' + bin_2_hex( | + | print( |
- | ys[0:8]) + '), ' + ys[8:16] + ' (hex: ' + bin_2_hex(ys[8:16]) + ')') | + | "Two y halves of full SPN (2 bytes):" |
+ | f" {ys[0:8]} (hex: {bin_2_hex(ys[0:8])})," | ||
+ | f" {ys[8:16]} (hex: {bin_2_hex(ys[8:16])})" | ||
+ | ) | ||
if __name__ == "__main__": | if __name__ == "__main__": | ||
- | main() | + | main() |
</code> | </code> | ||
+ | </spoiler> | ||
+ | |||
+ | </note> | ||
==== SPN 2 (3p) ==== | ==== SPN 2 (3p) ==== | ||
- | Vom folosi acum un SPN mai mai bun unde rezultatul permutarilor este XOR-at cu alti 2 octeti din cheie k3 si k4, ca in figura: | + | Vom folosi acum un SPN mai mai bun unde rezultatul permutarilor este XOR-at cu alți 2 octeți din cheie k3 și k4, ca în figură: |
{{:sasc:laboratoare:spn_1r_full_2s.png|}} | {{:sasc:laboratoare:spn_1r_full_2s.png|}} | ||
- | - Try to find the key in this case, when given the following message/ciphertext pairs: ('Om', 0x0073), ('El', 0xd00e), ('an', 0x855b). Print the key in ascii. | + | - Găsiți cheia știind următoarele perechi mesaj/ciphertext: ('Om', 0x0073), ('El', 0xd00e), ('an', 0x855b). Afișați-o în ASCII |
- | - Gasiti cheia stiind urmatoarele perechi mesaj/ciphertext: ('Om', 0x0073), ('El', 0xd00e), ('an', 0x855b). Afisati-o in ASCII | + | |
+ | <note tip>O să trebuiască să faceți căutare brute-force.</note> | ||
- | <note tip>O sa trebuiasca sa faceti cautare brute-force.</note> | + | ==== SPN 3 - Bonus (2p) ==== |
- | ==== SPN 3 (Bonus) ==== | + | În acest exercițiu vom folosi un bloc mai mare. În acest SPN sunt introduși 4 octeți x=[x1 || x2 || x3 || x4], iar cheia folosită conține 8 octeți k=[k1 || k2 || k3 || k4 || k5 || k6 || k7 || k8] ca în figură: |
- | As another example, which uses a larger block size, let's use an SPN that takes a 4-byte input x=[x1 || x2 || x3 || x4] and an 8-byte key k=[k1 || k2 || k3 || k4 || k5 || k6 || k7 || k8] as in this figure: | ||
{{:sasc:laboratoare:spn_1r_full_4s.png|}} | {{:sasc:laboratoare:spn_1r_full_4s.png|}} | ||
- | Note that in this 4-byte SPN, the permutation operates on all 4 bytes, similarly to the 2-byte SPN: that is, it shifts all bits four bits to the right. | + | Ca și în SPN-ul precedent, permutarea rotește toți octeții la dreapta cu 4 biți. |
- | - Try to find the key in this case as well, using the following message/ciphertext pairs: ('Omul', 0xddcf7bc7), ('stea', 0x96d58b43), ('luna', 0x9c3f2303) . Again print the key in ascii. | + | - Găsiți cheia știind următoarele perechi mesaj/ciphertext: ('Omul', 0xddcf7bc7), ('stea', 0x96d58b43), ('luna', 0x9c3f2303) |
- | <note tip> This time you cannot (easily) do a brute-force on all the bytes of the last XOR. However, you may try to attack one S-box at a time. Think of the bits that affect one such S-box and find an efficient attack. Suppose you brute force k5 and k6, is it possible to find out k1? | + | <note tip> |
+ | De data aceasta nu se poate executa o cautare brute-force pe toți octeții de XOR. Încercați să atacați câte un S-box pe rând. Gandiți-vă ce biți sunt afectați de fiecare S-box pentru a construi un atac eficient. Ce se întâmplă dacă faceți brute force pe k5 și k6? Puteți să aflați k1? | ||
</note> | </note> | ||
+ | </hidden> |