This shows you the differences between two versions of the page.
ic:labs:03 [2021/10/18 09:18] razvan.smadu [Exercițiul 1] |
ic:labs:03 [2023/10/16 20:53] (current) razvan.smadu |
||
---|---|---|---|
Line 1: | Line 1: | ||
===== Laboratorul 03 - PRGs ===== | ===== Laboratorul 03 - PRGs ===== | ||
- | Prezentarea PowerPoint pentru acest laborator poate fi găsită [[https://drive.google.com/file/d/1IfyMkfV6jQDzW5KhS_B6wt_azV31SLjL/view?usp=sharing|aici]]. | + | Prezentarea PowerPoint pentru acest laborator poate fi găsită [[https://drive.google.com/file/d/1IfyMkfV6jQDzW5KhS_B6wt_azV31SLjL/view?usp=sharing|aici]]. Puteți găsi o scurtă prezentare a noțiunii de avantaje în [[https://drive.google.com/file/d/1F_mtKi0zeAn1xYQs6Wc7IiiVX8ZW6aeG/view?usp=sharing|acest]] PDF. |
+ | |||
+ | 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/lab03/lab3.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): | + | def _pad(data: str, size: int) -> str: |
- | for i in range(0, len(string), chunk_size): | + | reminder = len(data) % size |
- | yield string[i:i+chunk_size] | + | if reminder != 0: |
+ | data = "0" * (size - reminder) + data | ||
+ | return data | ||
- | def _hex(x): | ||
- | return format(x, '02x') | ||
- | def hex_2_bin(data): | + | def _chunks(data: str, chunk_size: int) -> Generator[str, None, None]: |
- | return ''.join(f'{int(x, 16):08b}' for x in _chunks(data, 2)) | + | data = _pad(data, chunk_size) |
+ | for i in range(0, len(data), chunk_size): | ||
+ | yield data[i : i + chunk_size] | ||
- | def str_2_bin(data): | ||
- | return ''.join(f'{ord(c):08b}' for c in data) | ||
- | def bin_2_hex(data): | + | def _hex(data: int) -> str: |
- | return ''.join(f'{int(b, 2):02x}' for b in _chunks(data, 8)) | + | return format(data, "02x") |
- | def str_2_hex(data): | ||
- | return ''.join(f'{ord(c):02x}' for c in data) | ||
- | def bin_2_str(data): | + | # Conversion functions |
- | return ''.join(chr(int(b, 2)) for b in _chunks(data, 8)) | + | |
- | def hex_2_str(data): | ||
- | return ''.join(chr(int(x, 16)) for x in _chunks(data, 2)) | ||
- | # XOR FUNCTIONS | + | def hex_2_bin(data: str) -> str: |
+ | """Converts a hexadecimal string to a binary representation. | ||
- | def strxor(a, b): # xor two strings, trims the longer input | + | Args: |
- | return ''.join(chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)) | + | 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 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 binary representation of the hexadecimal string, where each |
+ | pair of hexadecimal digits is encoded as an 8-bit binary number. | ||
- | 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))) | + | >>> hex_2_bin("01abcd") |
+ | '000000011010101111001101' | ||
+ | >>> hex_2_bin("0a") | ||
+ | '00001010' | ||
+ | """ | ||
+ | return "".join(f"{int(x, 16):08b}" for x in _chunks(data, 2)) | ||
- | # BASE64 FUNCTIONS | ||
- | def b64decode(data): | + | def bin_2_hex(data: str) -> str: |
- | return bytes_to_string(base64.b64decode(string_to_bytes(data))) | + | """Converts a binary string to a hexadecimal representation. |
- | def b64encode(data): | + | 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 hexadecimal representation of the binary string, where each | ||
+ | group of 8 binary digits is encoded as a pair of hexadecimal digits. | ||
+ | |||
+ | Examples: | ||
+ | >>> bin_2_hex("000000011010101111001101") | ||
+ | '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 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): | + | def b64decode(data: str) -> str: |
- | return bytes_data.decode() # default utf-8 | + | """Decodes a base64 encoded string. |
- | def string_to_bytes(string_data): | + | Args: |
- | return string_data.encode() # default utf-8 | + | 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> | ||
- | ==== Exercițiul 1 ==== | + | |
- | In acest exercitiu vom incerca sa spargem un LCG (Linear Congruential Generator), ce se poate folosi pentru a genera numere "slab" aleatoare. | + | ==== Exercițiul 1 (3p) ==== |
- | Am folosit un astfel de generator pentru a obtine o secventa de octeti cu care a fost criptat un mesaj (plaintext). | + | În acest exercițiu vom încerca să spargem un LCG (Linear Congruential Generator, care se poate folosi pentru a genera numere “slab” aleatoare. |
- | Textul cifrat (ciphertext) in sistemul hexazecimal este urmatorul: | + | Am folosit un astfel de generator pentru a obține o secvență de octeți cu care a fost criptat un mesaj (plaintext). |
+ | Textul cifrat (ciphertext) în sistemul hexazecimal este următorul: | ||
<code> | <code> | ||
a432109f58ff6a0f2e6cb280526708baece6680acc1f5fcdb9523129434ae9f6ae9edc2f224b73a8 | a432109f58ff6a0f2e6cb280526708baece6680acc1f5fcdb9523129434ae9f6ae9edc2f224b73a8 | ||
</code> | </code> | ||
- | Dupa cum stiti, LCG foloseste urmatoare formula pentru a genera octeti: | + | |
+ | |||
+ | După cum știți, LCG folosește următoarea formula pentru a genera octeți: | ||
s_next = (a * s_prev + b) mod p | s_next = (a * s_prev + b) mod p | ||
- | unde s_next si s_prev sunt octeti (0-255) generati, p = 257 iar a si b au valori intre 0 si 256. | + | unde s_next și s_prev sunt octeți (0-255) generați, p = 257, iar a și b au valori între 0 și 256. |
- | Mai stiti si ca primele 16 litere din plaintext sunt "Let all creation". Criptarea a fost obtinuta executand XOR intre plaintext si secventa de octeti consecutivi generati de LCG | + | Mai știți și că primele 16 litere din plaintext sunt “Let all creation”. Criptarea a fost obținută realizând XOR între plaintext și secvența de octeți consecutivi generați de LCG. |
- | Spargeti LCG astfel incat sa puteti prezice sirul de numere generate "aleator". Care este mesajul initial? | + | Spargeți LCG astfel încât să puteți prezice șirul de numere generate “aleatoriu”. Care este mesajul inițial? |
- | Puteti folosi urmatorul schelet de cod: | + | Puteți folosi următorul schelet de cod: |
<code python 'ex1_weak_rng.py'> | <code python 'ex1_weak_rng.py'> | ||
+ | from typing import List, Tuple | ||
+ | |||
from utils import * | from utils import * | ||
- | #Parameters for weak LC RNG | + | |
class WeakRNG: | class WeakRNG: | ||
- | "Simple class for weak RNG" | + | """Simple class for weak RNG""" |
- | def __init__(self): | + | |
+ | def __init__(self) -> None: | ||
self.rstate = 0 | self.rstate = 0 | ||
self.maxn = 255 | self.maxn = 255 | ||
- | self.a = 0 #Set this to correct value | + | self.a = 0 # Set this to correct value |
- | self.b = 0 #Set this to correct value | + | self.b = 0 # Set this to correct value |
self.p = 257 | self.p = 257 | ||
- | def init_state(self): | + | def init_state(self, rstate: int) -> None: |
- | "Initialise rstate" | + | """Initialise rstate""" |
- | self.rstate = 0 #Set this to some value | + | self.rstate = rstate # Set this to some value |
self.update_state() | self.update_state() | ||
- | def update_state(self): | + | def update_state(self) -> None: |
- | "Update state" | + | """Update state""" |
self.rstate = (self.a * self.rstate + self.b) % self.p | self.rstate = (self.a * self.rstate + self.b) % self.p | ||
- | def get_prg_byte(self): | + | def get_prg_byte(self) -> int: |
- | "Return a new PRG byte and update PRG state" | + | """Return a new PRG byte and update PRG state""" |
b = self.rstate & 0xFF | b = self.rstate & 0xFF | ||
self.update_state() | self.update_state() | ||
return b | return b | ||
- | | ||
- | def main(): | ||
- | #Initialise weak rng | + | def main() -> None: |
+ | # Initialise weak rng | ||
wr = WeakRNG() | wr = WeakRNG() | ||
- | wr.init_state() | + | wr.init_state(0) |
- | #Print ciphertext | + | # Print ciphertext |
- | CH = 'a432109f58ff6a0f2e6cb280526708baece6680acc1f5fcdb9523129434ae9f6ae9edc2f224b73a8' | + | CH = "a432109f58ff6a0f2e6cb280526708baece6680acc1f5fcdb9523129434ae9f6ae9edc2f224b73a8" |
- | print("Full ciphertext in hexa: " + CH) | + | print("Full ciphertext in hexa:", CH) |
- | #Print known plaintext | + | # Print known plaintext |
- | pknown = 'Let all creation' | + | pknown = "Let all creation" |
nb = len(pknown) | nb = len(pknown) | ||
- | print("Known plaintext: " + pknown) | + | print("Known plaintext:", pknown) |
pkh = str_2_hex(pknown) | pkh = str_2_hex(pknown) | ||
- | print("Plaintext in hexa: " + pkh) | + | print("Plaintext in hexa:", pkh) |
- | #Obtain first nb bytes of RNG | + | # Obtain first nb bytes of RNG |
- | gh = hexxor(pkh, CH[0:nb*2]) | + | gh = hexxor(pkh, CH[0 : nb * 2]) |
print(gh) | print(gh) | ||
gbytes = [] | gbytes = [] | ||
for i in range(nb): | for i in range(nb): | ||
- | gbytes.append(ord(hex_2_str(gh[2*i:2*i+2]))) | + | gbytes.append(ord(hex_2_str(gh[2 * i : 2 * i + 2]))) |
print("Bytes of RNG: ") | print("Bytes of RNG: ") | ||
print(gbytes) | print(gbytes) | ||
- | #Break the LCG here: | + | # Break the LCG here: |
- | #1. find a and b | + | # TODO 1: Find a and b, and set them in the RNG |
- | #2. predict/generate rest of RNG bytes | + | |
- | #3. decrypt plaintext | + | |
- | # Print full plaintext | + | # TODO 2: Predict/generate rest of RNG bytes |
- | p = '' | + | |
- | print("Full plaintext is: " + p) | + | # TODO 3: Decrypt plaintext |
+ | |||
+ | # TODO 4: Print the full plaintext | ||
+ | p = ... | ||
+ | print("Full plaintext is:", p) | ||
if __name__ == "__main__": | if __name__ == "__main__": | ||
- | main() | + | main() |
</code> | </code> | ||
- | ==== Exercițiul 2 ==== | ||
- | Avantaje. Scopul acestui exercitiu e de a clarifica conceptul de avantaje prezentat la curs. Vom considera doua expermimente $\mathsf{EXP}(0)$ si $\mathsf{EXP}(1)$: | + | ==== Exercițiul 2 (2p) ==== |
- | * In $\mathsf{EXP}(0)$ verificatorul da cu banul (probabilitate de $1/2$ pentru CAP si $1/2$ pentru PAJURA) si transmite rezultatul adversarului $\mathsf{A}$. | + | |
- | * In $\mathsf{EXP}(1)$ verificatorul intotdeauna va transmite PAJURA catre adversar. | + | Avantaje. Scopul acestui exercițiu este de a clarifica conceptul de avantaje prezentat la curs. Vom considera două experimente $\mathsf{EXP}(0)$ și $\mathsf{EXP}(1)$, astfel: |
+ | * În $\mathsf{EXP}(0)$ verificatorul dă cu banul (probabilitate de $1/2$ pentru CAP și $1/2$ pentru PAJURĂ) și transmite rezultatul adversarului $\mathsf{A}$. | ||
+ | * În $\mathsf{EXP}(1)$ verificatorul întotdeauna va transmite PAJURĂ către adversar. | ||
- | Consideram $r = 0$ pentru CAP si $r = 1$ pentru PAJURA. Jocul va arata astfel: | + | Considerăm $r = 0$ pentru CAP și $r = 1$ pentru PAJURĂ. Jocul va arăta astfel: |
{{:sasc:laboratoare:adversar_prg.png?400|}} | {{:sasc:laboratoare:adversar_prg.png?400|}} | ||
- | Scopul adversarului este este sa diferentieze intre cele doua experimente: la finalul fiecarui experiment, adversarul returneaza un bit $\mathsf{b'}$, $0$ sau $1$, in speranta de a ghici $\mathsf{EXP}(b) = \mathsf{EXP}(b')$. | + | Scopul adversarului este să diferențieze între cele două experimente: la finalul fiecarui experiment, adversarul returnează un bit $\mathsf{b'}$, $0$ sau $1$, în speranța de a ghici experimentul în care se află (adică, $\mathsf{EXP}(b) = \mathsf{EXP}(b')$). |
- | Fie $W_{b}$ evenimentul cand in experimentul $\mathsf{EXP}(b)$ adversarul returneaza $b' = 1$. Adversarul vrea sa isi maximizeze avantajul de a distinge experimentele. Mai exact, el vrea sa maximizeze valoarea $\mathsf{Adv} = \left| \mathsf{Pr}\left[W_{0}\right] − \mathsf{Pr}\left[W_{1}\right] \right| \in \left[0, 1\right]$. | + | Fie $W_{b}$ evenimentul când în experimentul $\mathsf{EXP}(b)$ adversarul returnează $b' = 1$. Adversarul vrea să își maximizeze avantajul de a distinge experimentele. Mai exact, el vrea să maximizeze valoarea $\mathsf{Adv} = \left| \mathsf{Pr}\left[W_{0}\right] − \mathsf{Pr}\left[W_{1}\right] \right| \in \left[0, 1\right]$. |
- | Avantajul $\mathsf{Adv}$ reda capacitatea adversarului de a diferentia experimentele. Daca avantajul este $0$, atunci advesarul se comporta identic in ambele cazuri si nu poate sa le distinga. Daca avantajul este $1$, atunci adversarul stie concret care este valoarea lui $b$. Daca avantajul este neglijabil pentru orice adversar eficient atunci putem spune ca nu se poate distinge intre experimente. | + | Avantajul $\mathsf{Adv}$ redă capacitatea adversarului de a diferenția experimentele. Dacă avantajul este $0$, atunci advesarul se comportă identic în ambele cazuri și nu poate să le distingă. Dacă avantajul este $1$, atunci adversarul știe concret care este valoarea lui $b$. Dacă avantajul este neglijabil pentru orice adversar eficient atunci putem spune că nu se poate distinge între experimente. |
- | a. Calculati avantajul pentru fiecare dintre urmatorii adversari: | + | a. Calculați avantajul pentru fiecare dintre următorii adversari: |
- | * A1: Returneaza mereu $b'=1$. | + | * A1: Returnează mereu $b'=1$. |
- | * A2: Ignora valoarea primita de la verificator si returneaza aleator $0$ sau $1$ cu aceeasi probabilitate. | + | * A2: Ignoră valoarea primită de la verificator și returnează aleatoriu $0$ sau $1$, cu aceeași probabilitate. |
- | * A3: Returneaza $1$ daca a primit CAP ($r=0$) de la verificator, altfel returneaza $0$. | + | * A3: Returnează $1$ dacă a primit CAP ($r=0$) de la verificator, altfel returnează $0$. |
- | * A4: Returneaza $0$ daca a primit CAP de la verificator, altfel returneaza $1$. | + | * A4: Returnează $0$ dacă a primit CAP de la verificator, altfel returnează $1$. |
- | * A5: Daca a primit CAP returneaza $1$. Daca a primit PAJURA returneaza aleator $0$ sau $1$ cu aceeasi probabilitate. | + | * A5: Dacă a primit CAP, returnează $1$. Dacă a primit PAJURĂ, returnează aleatoriu $0$ sau $1$ cu aceeași probabilitate. |
- | * A6: Daca a primit CAP returneaza aleator $0$ sau $1$ cu aceeasi probabilitate. Daca a primit PAJURA returneaza $0$. | + | * A6: Dacă a primit CAP, returnează aleatoriu $0$ sau $1$, cu aceeași probabilitate. Dacă a primit PAJURĂ, returnează $0$. |
- | b. Care este avantajul maxim pe care il poate obtine un adversar? De ce? | + | b. Care este avantajul maxim pe care îl poate obține un adversar? De ce? |
<note tip> | <note tip> | ||
- | Incercati sa obtineti mai intai o formula generala ce nu depinde de adversar. | + | Încercați să obtineți mai întai o formulă generală ce nu depinde de adversar. |
</note> | </note> | ||
- | ==== Exercise 3 ==== | ||
- | O aplicatie a avantajului in scheme criptografice este de a arata daca un $PRG$ este sigur sau nu. Amintiti-va din curs: $G : K \rightarrow \{0, 1\}^n$ este un $PRG$ sigur daca pentru orice test statistic $\mathsf{A}$ eficient (care ruleaza in timp polinomial), avantajul $\mathsf{Adv_{PRG}[A, G]}$ este neglijabil. Un test statistic este de fapt un algoritm care incearca sa determine daca intrarea este aleatoarea sau nu. Acest algoritm poate fi definit: | + | ==== Exercițiul 3 (2p) ==== |
+ | |||
+ | O aplicație a avantajului în schemele criptografice este de a arăta că un $PRG$ este sigur sau nu. Amintiți-vă de la curs că $G : K \rightarrow \{0, 1\}^n$ este un $PRG$ sigur dacă pentru orice test statistic $\mathsf{A}$ eficient (care rulează în timp polinomial), avantajul $\mathsf{Adv_{PRG}[A, G]}$ este neglijabil. Un test statistic este de fapt un algoritm care încearcă să determine dacă intrarea pare aleatoare sau nu. Acest algoritm poate fi definit astfel: | ||
\begin{equation*} | \begin{equation*} | ||
A(x) = | A(x) = | ||
\begin{cases} | \begin{cases} | ||
- | 1 ,& \text{daca x e aleator}\\ | + | 1 ,& \text{dacă x e aleatoriu}\\ |
0 ,& \text{altfel} | 0 ,& \text{altfel} | ||
- | \end{cases} | + | \end{cases} |
\end{equation*} | \end{equation*} | ||
- | Putem asocia pentru fiecare test statistic cate un adversar care incearca sa sparga $\mathsf{PRG}$-ul. (i.e. poate distinge intre rezultatul $\mathsf{PRG}$-ului si un generator de numere complet aleatoare). In acest caz, vom defini experimentele: | + | Putem asocia pentru fiecare test statistic $A$ câte un adversar care încearcă să spargă $\mathsf{PRG}$-ul (adică poate distinge între rezultatul $\mathsf{PRG}$-ului și un generator de numere complet aleatoare). În acest caz, vom defini experimentele după cum urmează: |
- | * In $\mathsf{EXP}(0)$ verificatorul returneaza catre adversarul $\mathsf{A}$ valoarea $G(k)$ generata de $\mathsf{PRG}$; | + | * În $\mathsf{EXP}(0)$ verificatorul trimite către adversarul $\mathsf{A}$ valoarea $G(k)$ generată de $\mathsf{PRG}$; |
- | * In $\mathsf{EXP}(1)$ verificatorul returneaza catre adversarul $\mathsf{A}$ o valoare $r$ generata de un generator de numere complet aleatoare. | + | * În $\mathsf{EXP}(1)$ verificatorul trimite către adversarul $\mathsf{A}$ o valoare $r$ generată de un generator de numere complet aleatoare. |
+ | |||
+ | Ca mai devreme, $W_{b}$ este evenimentul în care adversarul spune că numărul este aleatoriu și a avut loc experimentul $b$. Dacă adversarul poate să distingă între $\mathsf{PRG}$ și un generator de numere complet aleatoare, atunci $\mathsf{PRG}$-ul nu e sigur. Așadar, avantajul devine: | ||
- | Ca si mai devreme, $W_{b}$ este evenimentul in care adversarul spune ca numarul este aleator si a avut loc experimentul $b$. Daca adversarul poate sa distinga intre $\mathsf{PRG}$ si un generator de numere complet aleatoare, atunci $\mathsf{PRG}$ nu e sigur. Asadar avantajul devine: | + | $$\mathsf{Adv_{PRG}[A, G]} = \left|\underset{k \leftarrow K}{Pr}[A(G(k)) = 1] - \underset{r \leftarrow \{0, 1\}^n}{Pr}[A(r) = 1] \right|$$ |
- | $\mathsf{Adv_{PRG}[A, G]} = \left|\underset{k \leftarrow K}{Pr}[A(G(k)) = 1] - \underset{r \leftarrow \{0, 1\}^n}{Pr}[A(r) = 1] \right|$. | + | |
- | Chiar daca problema determinarii daca un $\mathsf{PRG}$ poate fi demonstrat ca sigur este echivalent cu a rezolva binecunoscuta problema $\mathsf{P}$ vs $\mathsf{NP}$, in practica exista $\mathsf{PRG}$-uri considerate sigure datorita unor euristici. | + | Deși problema determinării dacă există $\mathsf{PRG}$-uri care să poată fi demonstrate ca fiind sigure este echivalentă cu a rezolva bine-cunoscuta problemă $\mathsf{P}$ vs $\mathsf{NP}$, în practică există $\mathsf{PRG}$-uri considerate sigure prin folosirea unor euristici. |
- | In acest exercitiu, se da un $\mathsf{PRG}$ $G : \{0, 1\}^s \rightarrow \{0, 1\}^n$ sigur. Cu ajutorul acestuia, spuneti daca urmatoarele $\mathsf{PRG}$-uri sunt sigure. | + | În acest exercițiu, se dă un $\mathsf{PRG}$ $G : \{0, 1\}^s \rightarrow \{0, 1\}^n$ sigur. Cu ajutorul acestuia, spuneți dacă următoarele $\mathsf{PRG}$-uri sunt sigure. |
* $G_{1}(k) = G(k) \oplus 1^n$ | * $G_{1}(k) = G(k) \oplus 1^n$ | ||
* $G_{2}(k) = G(k) \| 0$ | * $G_{2}(k) = G(k) \| 0$ | ||
Line 205: | Line 468: | ||
* $G_{5}(k)=G(k) \| G(k)$ | * $G_{5}(k)=G(k) \| G(k)$ | ||
- | <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> |
- | <note tip>Pentru unele dintre ele nu e nevoie sa calculati avantajul</note> | + | <note tip>Pentru unele dintre ele nu este nevoie să calculați avantajul.</note> |
- | ==== Exercițiul 4 ==== | ||
- | Vom folosi experimentul definit mai devreme pentru a construi un pseudorandom generator ($\mathsf{PRG}$): | + | ==== Exercițiul 4 (3p) ==== |
- | - Se alege lungimea $n$ a sirului ce va fi generat | + | |
- | - Se obtine un sir de biti $R$ de lungime $n$ (e.g. puteti folosi chiar si LCG-ul din Exercitiul 1) | + | |
- | - Pentru fiecare bit $r$ din sirul $R$ generat anterior, $\mathsf{PRG}$-ul va intoarce un bit $b$ astfel: | + | |
- | * daca bitul $r$ este $0$, atunci intoarce un bit aleator $b \in \{0, 1\}$ | + | |
- | * daca bitul $r$ este $1$, atunci intoarce $1$ | + | |
- | a. Implementati //frequency (monobit) test// asa cum e descris in articolul din [[http://csrc.nist.gov/publications/nistpubs/800-22-rev1a/SP800-22rev1a.pdf | NIST (vezi section 2.1)]]. Verificati daca secventa generata de $\mathsf{PRG}$-ul de mai sus este aleatoare sau nu. (presupunem $n=100$) | + | Vom folosi experimentul definit mai devreme pentru a construi un generator pseudoaleatoriu ($\mathsf{PRG}$): |
+ | - Se alege lungimea $n$ a șirului ce va fi generat | ||
+ | - Se obține un șir de biți $R$ de lungime $n$ (de exemplu, puteți folosi chiar și LCG-ul din Exercițiul 1) | ||
+ | - Pentru fiecare bit $r$ din șirul $R$ generat anterior, $\mathsf{PRG}$-ul va întoarce un bit $b$ astfel: | ||
+ | * dacă bitul $r$ este $0$, atunci întoarce un bit aleatoriu $b \in \{0, 1\}$ | ||
+ | * dacă bitul $r$ este $1$, atunci întoarce $1$ | ||
- | b. Executati testul pe un sir aleator de biti (puteti folosi sirul R folosit in $\mathsf{PRG}$) si comparati rezultatele. | + | a. Implementați //frequency (monobit) test// așa cum este descris de către [[http://csrc.nist.gov/publications/nistpubs/800-22-rev1a/SP800-22rev1a.pdf | NIST (vezi secțiunea 2.1)]]. Verificați dacă secvența generată de $\mathsf{PRG}$-ul de mai sus este aleatoare sau nu (să presupunem pentru $n=100$). |
- | Daca rezultatele celor 2 experimente difera in mod regulat, acest test ofera un atacator care sparge $\mathsf{PRG}$-ul descris mai sus. | + | b. Executați testul pe un șir aleatoriu de biți (de exemplu, șirul R folosit în $\mathsf{PRG}$) și comparați rezultatele. |
- | <note tip>Pentru a genera un sir aleator de biti, puteti folosi o functie de forma: | + | Dacă rezultatele celor două experimente diferă în mod regulat, acest test vă oferă un atacator care sparge $\mathsf{PRG}$-ul descris mai sus. |
+ | |||
+ | <note tip>Pentru a genera un șir aleatoriu de biți, puteți folosi o funcție de forma: | ||
<code python> | <code python> | ||
import random | import random | ||
- | def get_random_string(n): #generate random bit string | + | def get_random_string(n: int) -> str: |
- | bstr = bin(random.getrandbits(n)).lstrip('0b').zfill(n) | + | """Generate random bit string""" |
- | return bstr | + | return bin(random.getrandbits(n)).lstrip("0b").zfill(n) |
</code> | </code> | ||
</note> | </note> | ||
- | <note tip>In Python puteti folosi functii precum sqrt, fabs sau erfc din modulul math | + | <note tip>În Python puteți folosi funcții precum sqrt, fabs sau erfc din modulul math. |
<code python> | <code python> | ||
import math | import math | ||
Line 240: | Line 504: | ||
</note> | </note> | ||
- | ==== Exercițiul 5 (Bonus) ==== | + | <file python ex4.py> |
+ | import math | ||
+ | import random | ||
- | In acest exercitiu, veti incerca sa verificati daca intr-adevar Salsa20 stream cipher este mai rapid decat RC4. Descarcati {{:ic:laboratoare:salsa20_rc4_surse_c.zip|aceasta}} arhiva. | + | from utils import * |
- | Rulati programul folosind acelasi fisier de intrare atat pentru Salsa20 cat si pentru RC4. Comparati timpul de executie in ambele cazuri. | + | |
+ | |||
+ | def get_random_string(n: int) -> str: | ||
+ | """Generate random bit string""" | ||
+ | return bin(random.getrandbits(n)).lstrip("0b").zfill(n) | ||
+ | |||
+ | |||
+ | def main() -> None: | ||
+ | N_RUNS = 100 # the number of runs | ||
+ | N_BITS = 1000 # the number of bits to generate | ||
+ | |||
+ | # TODO: implement and run the multiple times (e.g., 100) the | ||
+ | # statistical tests | ||
+ | |||
+ | |||
+ | if __name__ == "__main__": | ||
+ | main() | ||
+ | </file> | ||
+ | ==== Exercițiul 5 - Bonus (2p) ==== | ||
+ | |||
+ | În acest exercițiu, veți încerca să verificați dacă într-adevar Salsa20 stream cipher este mai rapid decât RC4. Descărcați {{:ic:laboratoare:salsa20_rc4_surse_c.zip|această}} arhivă. | ||
+ | Rulați programul folosind același fișier de intrare atât pentru Salsa20, cât și pentru RC4. Comparați timpul de execuție în ambele cazuri. | ||
<note tip> | <note tip> | ||
- | Pentru sistemele UNIX (si WSL), folositi scriptul **prepare.sh** pentru a complila sursele si a genera fisierele necesare. | + | Pentru sistemele UNIX (și WSL), folosiți scriptul **prepare.sh** pentru a complila sursele și a genera fișierele necesare. |
- | Daca nu folositi WSL pe Windows, scriptul **prepare.ps1** genereaza fisierele necesare dar **nu** si compileaza sursele. | + | Dacă nu folosiți WSL pe Windows, scriptul **prepare.ps1** generează fișierele necesare, dar **nu** și compilează sursele. |
</note> | </note> | ||
<note tip> | <note tip> | ||
- | Folositi optiunea "-h" pentru a afisa lista tuturor argumentelor folosite de program. | + | Folosiți opțiunea "-h" pentru a afișa lista tuturor argumentelor folosite de program. |
</note> | </note> | ||
<note tip> | <note tip> | ||
- | Uitati-va in fisierul salsa20.h. Unde se executa rundele de criptare? | + | Uitați-vă în fișierul salsa20.h. Unde se execută rundele de criptare? |
</note> | </note> | ||
- | Incercati sa implementati aceeasi functionalitate folosint OpenSSL. OpenSSL suporta RC4 dar nu si Salsa20. Folositi ChaCha20 in loc de Salsa20, aceasta fiind o varianta imbunatatita a algoritmului. | + | Încercați să implementați aceeași funcționalitate folosind OpenSSL. OpenSSL suportă RC4, dar nu și Salsa20. Folosiți ChaCha20 in loc de Salsa20, aceasta fiind o variantă îmbunătățită a algoritmului. |
+ | |||
+ | </hidden> |