This shows you the differences between two versions of the page.
ic:labs:03 [2021/01/25 11:05] razvan.smadu |
ic:labs:03 [2023/10/16 20:53] (current) razvan.smadu |
||
---|---|---|---|
Line 1: | Line 1: | ||
===== Laboratorul 03 - PRGs ===== | ===== Laboratorul 03 - PRGs ===== | ||
- | The powerpoint presentation for this lab can be found [[https://drive.google.com/file/d/1IfyMkfV6jQDzW5KhS_B6wt_azV31SLjL/view?usp=sharing|here]]. | + | 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> | ||
- | ==== Exercise 1 ==== | ||
- | In this exercise we'll try to break a Linear Congruential Generator, that may be used to generate "poor" random numbers. | + | ==== Exercițiul 1 (3p) ==== |
- | We implemented such weak RNG to generate a sequence of bytes and then encrypted a plaintext message. | + | În acest exercițiu vom încerca să spargem un LCG (Linear Congruential Generator, care se poate folosi pentru a genera numere “slab” aleatoare. |
- | The resulting ciphertext in hexadecimal is this: | + | 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> | ||
- | You know that the LCG uses the following formula to produce each byte: | ||
- | s_next = (a * s_prev + b) mod p | ||
- | where both s_prev and s_next are byte values (between 0 and 255) and p is 257. | + | După cum știți, LCG folosește următoarea formula pentru a genera octeți: |
- | Both a and b are values between 0 and 256. | + | |
+ | s_next = (a * s_prev + b) mod p | ||
- | You also know that the first 16 letters of the plaintext are "Let all creation" and that the ciphertext was generated by XOR-ing a string of consecutive bytes generated by the LCG with the plaintext. | + | 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. |
- | Can you break the LCG and predict the RNG stream so that in the end you find the entire plaintext? | + | 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. |
- | You may use this starting code: | + | Spargeți LCG astfel încât să puteți prezice șirul de numere generate “aleatoriu”. Care este mesajul inițial? |
+ | |||
+ | 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: " + known) | + | 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> | ||
- | ==== Exercise 2 ==== | ||
- | Advantage. The purpose of this problem is to clarify the concept of advantage. Consider the following two experiments $\mathsf{EXP}(0)$ and $\mathsf{EXP}(1)$: | + | ==== Exercițiul 2 (2p) ==== |
- | * In $\mathsf{EXP}(0)$ the challenger flips a fair coin (probability $1/2$ for HEADS and $1/2$ for TAILS) and sends the result to the adversary $\mathsf{A}$. | + | |
- | * In $\mathsf{EXP}(1)$ the challenger always sends TAILS to the adversary. | + | 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. | ||
- | Let r = 0 for HEADS and r = 1 for TAILS. Then we have the experiment as shown below: | + | 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|}} | ||
- | The adversary’s goal is to distinguish these two experiments: at the end of each experiment the adversary outputs a bit $0$ or $1$ for its guess for which experiment it is in. For $b = 0,1$ let $W_{b}$ be the event that in experiment $b$ the adversary output $1$. The adversary tries to maximize its distinguishing advantage, namely the quantity | + | 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')$). |
- | $\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]$. |
- | The advantage $\mathsf{Adv}$ captures the adversary’s ability to distinguish the two experiments. If the advantage is $0$ then the adversary behaves exactly the same in both experiments and therefore does not distinguish between them. If the advantage is $1$ then the adversary can tell perfectly what experiment it is in. If the advantage is negligible for all efficient adversaries (as defined in class) then we say that the two experiments are indistinguishable. | + | 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. Calculate the advantage of each of the following adversaries: | + | a. Calculați avantajul pentru fiecare dintre următorii adversari: |
- | * A1: Always output $1$. | + | * A1: Returnează mereu $b'=1$. |
- | * A2: Ignore the result reported by the challenger, and randomly output $0$ or $1$ with even probability. | + | * A2: Ignoră valoarea primită de la verificator și returnează aleatoriu $0$ sau $1$, cu aceeași probabilitate. |
- | * A3: Output $1$ if HEADS was received from the challenger, else output $0$. | + | * A3: Returnează $1$ dacă a primit CAP ($r=0$) de la verificator, altfel returnează $0$. |
- | * A4: Output $0$ if HEADS was received from the challenger, else output $1$. | + | * A4: Returnează $0$ dacă a primit CAP de la verificator, altfel returnează $1$. |
- | * A5: If HEADS was received, output $1$. If TAILS was received, randomly output $0$ or $1$ with even probability. | + | * A5: Dacă a primit CAP, returnează $1$. Dacă a primit PAJURĂ, returnează aleatoriu $0$ sau $1$ cu aceeași probabilitate. |
- | * A6: If HEADS was received, randomly output $0$ or $1$ with even probability. If TAILS was received, output $0$. | + | * A6: Dacă a primit CAP, returnează aleatoriu $0$ sau $1$, cu aceeași probabilitate. Dacă a primit PAJURĂ, returnează $0$. |
- | b. What is the maximum advantage possible in distinguishing these two experiments? Explain why. | + | b. Care este avantajul maxim pe care îl poate obține un adversar? De ce? |
<note tip> | <note tip> | ||
- | You may want to compute the general formula, regardless of what the adversary is. | + | Încercați să obtineți mai întai o formulă generală ce nu depinde de adversar. |
</note> | </note> | ||
- | ==== Exercise 3 ==== | ||
- | One of the applications of computing the advantage for an adversary on a cryptographic scheme is to prove that a $\mathsf{PRG}$ is secure or not. Recall from the lecture that $G : K \rightarrow \{0, 1\}^n$ is a secure $\mathsf{PRG}$ if for any efficient (that runs in polynomial time) statistical test $\mathsf{A}$, the advantage $\mathsf{Adv_{PRG}[A, G]}$ is negligible. A statistical test is basically an algorithm that tries to determine whether the input seems random or not, therefore it can be defined as follows: | + | ==== 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{if x is random}\\ | + | 1 ,& \text{dacă x e aleatoriu}\\ |
- | 0 & \text{otherwise} | + | 0 ,& \text{altfel} |
- | \end{cases} | + | \end{cases} |
\end{equation*} | \end{equation*} | ||
- | You can associate the statistical test $\mathsf{A}$ with an adversary that tries to break the $\mathsf{PRG}$ (i.e. it can distinguish the output of the $\mathsf{PRG}$ from a truly random generator). In this case, we shall define the experiments as follows: | + | 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)$ the challenger sends the output $G(k)$ generated by the $\mathsf{PRG}$ to the adversary $\mathsf{A}$; | + | * În $\mathsf{EXP}(0)$ verificatorul trimite către adversarul $\mathsf{A}$ valoarea $G(k)$ generată de $\mathsf{PRG}$; |
- | * In $\mathsf{EXP}(1)$ the challenger sends the output $r$ generated by a truly random generator to the adversary. | + | * În $\mathsf{EXP}(1)$ verificatorul trimite către adversarul $\mathsf{A}$ o valoare $r$ generată de un generator de numere complet aleatoare. |
- | Also, $W_{b}$ represents the event in which the adversary guesses that the input is random (i.e. $A(x) = 1$), in the experiment $b$. The idea behind this is that if the adversary can tell the difference between the $\mathsf{PRG}$ and a truly random generator, then the $\mathsf{PRG}$ is not secure. Thus, the advantage becomes: | + | 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: |
- | $\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|$. | + | |
- | Although the problem of determining whether there are provably secure $\mathsf{PRG}s$ is equivalent to solving the well-known problem $\mathsf{P}$ vs $\mathsf{NP}$, in practice there are some $\mathsf{PRG}s$ that are considered secure, by using some heuristics. | + | $$\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|$$ |
- | In this exercise, you are given a $\mathsf{PRG}$ $G : \{0, 1\}^s \rightarrow \{0, 1\}^n$ that is known to be secure and your assignment is to determine which of the following $\mathsf{PRG}s$ are also secure: | + | 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. |
+ | |||
+ | Î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 208: | Line 468: | ||
* $G_{5}(k)=G(k) \| G(k)$ | * $G_{5}(k)=G(k) \| G(k)$ | ||
- | <note>For strings $y$ and $z$ we use $y \| z$ to denote the concatenation of $y$ and $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 este nevoie să calculați avantajul.</note> | ||
- | <note tip>Some of these exercises may not require computing any advantage.</note> | ||
- | ==== Exercise 4 ==== | + | ==== Exercițiul 4 (3p) ==== |
- | Let's use the experiment defined earlier as a pseudorandom generator ($\mathsf{PRG}$) as follows: | + | Vom folosi experimentul definit mai devreme pentru a construi un generator pseudoaleatoriu ($\mathsf{PRG}$): |
- | - Set a desired output length $n$ | + | - Se alege lungimea $n$ a șirului ce va fi generat |
- | - Obtain a random sequence $R$ of bits of length $n$ (e.g. using the Linear-congruential generator from Exercise 1) | + | - 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) |
- | - For each bit $r$ in the random sequence $R$ generated in the previous step, output a bit $b$ as follows: | + | - Pentru fiecare bit $r$ din șirul $R$ generat anterior, $\mathsf{PRG}$-ul va întoarce un bit $b$ astfel: |
- | * if the bit $r$ is $0$, then output a random bit $b \in \{0, 1\}$ | + | * dacă bitul $r$ este $0$, atunci întoarce un bit aleatoriu $b \in \{0, 1\}$ |
- | * if the bit $r$ is $1$, then output $1$ | + | * dacă bitul $r$ este $1$, atunci întoarce $1$ |
- | a. Implement the frequency (monobit) test from [[http://csrc.nist.gov/publications/nistpubs/800-22-rev1a/SP800-22rev1a.pdf | NIST (see section 2.1)]] and check if a sequence generated by the above $\mathsf{PRG}$ (say $n=100$) seems random or not. | + | 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$). |
- | b. Run the test on a random bitstring (e.g. a string such as R used by the above $\mathsf{PRG}$), and compare the result of the test. | + | b. Executați testul pe un șir aleatoriu de biți (de exemplu, șirul R folosit în $\mathsf{PRG}$) și comparați rezultatele. |
- | If the two results are different across many iterations, this test already gives you an attacker that breaks the $\mathsf{PRG}$. | + | 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>You may use a function like this to generate a random bitstring</note> | + | <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 tip>Also, in Python you may find the functions sqrt, fabs and erfc from the module math useful</note> | + | <note tip>În Python puteți folosi funcții precum sqrt, fabs sau erfc din modulul math. |
+ | <code python> | ||
+ | import math | ||
+ | </code> | ||
+ | </note> | ||
- | ==== Exercise 5 (Bonus) ==== | + | <file python ex4.py> |
+ | import math | ||
+ | import random | ||
- | In this optional exercise you will try to verify that indeed the Salsa20 stream cipher is faster than the older RC4 stream cipher, by using this archive {{:ic:laboratoare:salsa20_rc4_surse_c.zip|}}. Run the program with the same input file using Salsa20 and after that using RC4 and compare the computation time of both instances. | + | from utils import * |
+ | |||
+ | |||
+ | 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> | ||
- | On UNIX systems, use the script to compile sources and generate setup files. On other systems, try to replicate the functionality where there are system specific commands. | + | Pentru sistemele UNIX (și WSL), folosiți scriptul **prepare.sh** pentru a complila sursele și a genera fișierele necesare. |
+ | 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> | ||
- | Use "-h" option to show all the argument the program requires. | + | Folosiți opțiunea "-h" pentru a afișa lista tuturor argumentelor folosite de program. |
</note> | </note> | ||
<note tip> | <note tip> | ||
- | Have a look on salsa20.h. Can you spot where the rounds take place? | + | Uitați-vă în fișierul salsa20.h. Unde se execută rundele de criptare? |
</note> | </note> | ||
- | Try implementing the same functionality using OpenSSL. OpenSSL should contain RC4 support but seems to have no Salsa20 support; use ChaCha20 instead, which is an improved variant of older Salsa20. | + | Î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> |