This is an old revision of the document!
Prezentarea PowerPoint pentru acest laborator poate fi găsită aici.
Puteți lucra acest laborator folosind și platforma Google Colab, accesând acest link.
import base64 # CONVERSION FUNCTIONS def _chunks(string, chunk_size): for i in range(0, len(string), chunk_size): yield string[i:i+chunk_size] def _hex(x): return format(x, '02x') def hex_2_bin(data): return ''.join(f'{int(x, 16):08b}' for x in _chunks(data, 2)) def str_2_bin(data): return ''.join(f'{ord(c):08b}' for c in data) def bin_2_hex(data): return ''.join(f'{int(b, 2):02x}' for b in _chunks(data, 8)) def str_2_hex(data): return ''.join(f'{ord(c):02x}' for c in data) def bin_2_str(data): 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 strxor(a, b): # xor two strings, trims the longer input return ''.join(chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b)) def bitxor(a, b): # xor two bit-strings, trims the longer input return ''.join(str(int(x) ^ int(y)) for (x, y) in zip(a, b)) def hexxor(a, b): # xor two hex-strings, trims the longer input return ''.join(_hex(int(x, 16) ^ int(y, 16)) for (x, y) in zip(_chunks(a, 2), _chunks(b, 2))) # BASE64 FUNCTIONS def b64decode(data): return bytes_to_string(base64.b64decode(string_to_bytes(data))) def b64encode(data): return bytes_to_string(base64.b64encode(string_to_bytes(data))) # PYTHON3 'BYTES' FUNCTIONS def bytes_to_string(bytes_data): return bytes_data.decode() # default utf-8 def string_to_bytes(string_data): return string_data.encode() # default utf-8
În acest exercițiu vom încerca să spargem un LCG (Linear Congruential Generator, care se poate folosi pentru a genera numere “slab” aleatoare. 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:
a432109f58ff6a0f2e6cb280526708baece6680acc1f5fcdb9523129434ae9f6ae9edc2f224b73a8
După cum știți, LCG folosește următoarea formula pentru a genera octeți:
s_next = (a * s_prev + b) mod p
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 ș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.
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:
from utils import * class WeakRNG: """ Simple class for weak RNG """ def __init__(self): self.rstate = 0 self.maxn = 255 self.a = 0 # Set this to correct value self.b = 0 # Set this to correct value self.p = 257 def init_state(self, rstate): """ Initialise rstate """ self.rstate = rstate # Set this to some value self.update_state() def update_state(self): """ Update state """ self.rstate = (self.a * self.rstate + self.b) % self.p def get_prg_byte(self): """ Return a new PRG byte and update PRG state """ b = self.rstate & 0xFF self.update_state() return b def main(): # Initialise weak rng wr = WeakRNG() wr.init_state(0) # Print ciphertext CH = 'a432109f58ff6a0f2e6cb280526708baece6680acc1f5fcdb9523129434ae9f6ae9edc2f224b73a8' print("Full ciphertext in hexa:", CH) # Print known plaintext pknown = 'Let all creation' nb = len(pknown) print("Known plaintext:", pknown) pkh = str_2_hex(pknown) print("Plaintext in hexa:", pkh) # Obtain first nb bytes of RNG gh = hexxor(pkh, CH[0:nb*2]) print(gh) gbytes = [] for i in range(nb): gbytes.append(ord(hex_2_str(gh[2*i:2*i+2]))) print("Bytes of RNG: ") print(gbytes) # Break the LCG here: # TODO 1: Find a and b, and set them in the RNG # TODO 2: Predict/generate rest of RNG bytes # TODO 3: Decrypt plaintext # TODO 4: Print the full plaintext p = '' print("Full plaintext is:", p) if __name__ == "__main__": main()
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:
Considerăm $r = 0$ pentru CAP și $r = 1$ pentru PAJURĂ. Jocul va arăta astfel:
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 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}$ 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. Calculați avantajul pentru fiecare dintre următorii adversari:
b. Care este avantajul maxim pe care îl poate obține un adversar? De ce?
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*} A(x) = \begin{cases} 1 ,& \text{dacă x e aleatoriu}\\ 0 ,& \text{altfel} \end{cases} \end{equation*}
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ă:
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|$$
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.
Vom folosi experimentul definit mai devreme pentru a construi un generator pseudoaleatoriu ($\mathsf{PRG}$):
a. Implementați frequency (monobit) test așa cum este descris de către 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. Executați testul pe un șir aleatoriu de biți (de exemplu, șirul R folosit în $\mathsf{PRG}$) și comparați rezultatele.
Dacă rezultatele celor două experimente diferă în mod regulat, acest test vă oferă un atacator care sparge $\mathsf{PRG}$-ul descris mai sus.
import random def get_random_string(n): """ Generate random bit string """ return bin(random.getrandbits(n)).lstrip('0b').zfill(n)
import math
Î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 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.
Î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.