This is an old revision of the document!
Prezentarea PowerPoint pentru acest laborator poate fi găsită aici.
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
In acest exercitiu vom incerca sa spargem un LCG (Linear Congruential Generator), ce se poate folosi pentru a genera numere “slab” aleatoare. Am folosit un astfel de generator pentru a obtine o secventa de octeti cu care a fost criptat un mesaj (plaintext). Textul cifrat (ciphertext) in sistemul hexazecimal este urmatorul:
a432109f58ff6a0f2e6cb280526708baece6680acc1f5fcdb9523129434ae9f6ae9edc2f224b73a8
Dupa cum stiti, LCG foloseste urmatoare formula pentru a genera octeti:
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.
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
Spargeti LCG astfel incat sa puteti prezice sirul de numere generate “aleator”. Care este mesajul initial?
Puteti folosi urmatorul schelet de cod:
from utils import * #Parameters for weak LC RNG 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): "Initialise rstate" self.rstate = 0 #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() #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: #1. find a and b #2. predict/generate rest of RNG bytes #3. decrypt plaintext # Print full plaintext p = '' print("Full plaintext is: " + p) if __name__ == "__main__": main()
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)$:
Consideram $r = 0$ pentru CAP si $r = 1$ pentru PAJURA. Jocul va arata astfel:
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')$. 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]$.
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.
a. Calculati avantajul pentru fiecare dintre urmatorii adversari:
b. Care este avantajul maxim pe care il poate obtine un adversar? De ce?
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:
\begin{equation*} A(x) = \begin{cases} 1 ,& \text{daca x e aleator}\\ 0 ,& \text{altfel} \end{cases} \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:
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|$.
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.
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.
Vom folosi experimentul definit mai devreme pentru a construi un pseudorandom generator ($\mathsf{PRG}$):
a. Implementati frequency (monobit) test asa cum e descris in articolul din NIST (vezi section 2.1). Verificati daca secventa generata de $\mathsf{PRG}$-ul de mai sus este aleatoare sau nu. (presupunem $n=100$)
b. Executati testul pe un sir aleator de biti (puteti folosi sirul R folosit in $\mathsf{PRG}$) si comparati rezultatele.
Daca rezultatele celor 2 experimente difera in mod regulat, acest test ofera un atacator care sparge $\mathsf{PRG}$-ul descris mai sus.
import random def get_random_string(n): #generate random bit string bstr = bin(random.getrandbits(n)).lstrip('0b').zfill(n) return bstr
import math
In acest exercitiu, veti incerca sa verificati daca intr-adevar Salsa20 stream cipher este mai rapid decat RC4. Descarcati aceasta arhiva. Rulati programul folosind acelasi fisier de intrare atat pentru Salsa20 cat si pentru RC4. Comparati timpul de executie in ambele cazuri.
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.