Laboratorul 03 - PRGs

Prezentarea PowerPoint pentru acest laborator poate fi găsită aici.

utils.py
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

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. 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:

'ex1_weak_rng.py'
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()  

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)$:

  • 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.

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:

  • A1: Returneaza mereu $b'=1$.
  • A2: Ignora valoarea primita de la verificator si returneaza aleator $0$ sau $1$ cu aceeasi probabilitate.
  • A3: Returneaza $1$ daca a primit CAP ($r=0$) de la verificator, altfel returneaza $0$.
  • A4: Returneaza $0$ daca a primit CAP de la verificator, altfel returneaza $1$.
  • A5: Daca a primit CAP returneaza $1$. Daca a primit PAJURA returneaza aleator $0$ sau $1$ cu aceeasi probabilitate.
  • A6: Daca a primit CAP returneaza aleator $0$ sau $1$ cu aceeasi probabilitate. Daca a primit PAJURA returneaza $0$.

b. Care este avantajul maxim pe care il poate obtine un adversar? De ce?

Incercati sa obtineti mai intai o formula generala ce nu depinde de adversar.

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:

\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:

  • In $\mathsf{EXP}(0)$ verificatorul returneaza catre adversarul $\mathsf{A}$ valoarea $G(k)$ generata de $\mathsf{PRG}$;
  • In $\mathsf{EXP}(1)$ verificatorul returneaza catre adversarul $\mathsf{A}$ o valoare $r$ generata de un generator de numere complet aleatoare.

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.

  • $G_{1}(k) = G(k) \oplus 1^n$
  • $G_{2}(k) = G(k) \| 0$
  • $G_{3}(k) = G(0)$
  • $G_{4}(k=(k_1, k_2)) = G(k_1) \| G(k_2)$
  • $G_{5}(k)=G(k) \| G(k)$

Pentru doua siruri de biti $y$ si $z$ folosim $y \| z$ pentru a indica concatenarea intre $y$ si $z$.

Pentru unele dintre ele nu e nevoie sa calculati avantajul

Exercițiul 4

Vom folosi experimentul definit mai devreme pentru a construi un pseudorandom generator ($\mathsf{PRG}$):

  1. Se alege lungimea $n$ a sirului ce va fi generat
  2. Se obtine un sir de biti $R$ de lungime $n$ (e.g. puteti folosi chiar si LCG-ul din Exercitiul 1)
  3. 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 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.

Pentru a genera un sir aleator de biti, puteti folosi o functie de forma:

import random
 
def get_random_string(n): #generate random bit string
    bstr = bin(random.getrandbits(n)).lstrip('0b').zfill(n)
    return bstr

In Python puteti folosi functii precum sqrt, fabs sau erfc din modulul math

import math

Exercițiul 5 (Bonus)

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.

Pentru sistemele UNIX (si WSL), folositi scriptul prepare.sh pentru a complila sursele si a genera fisierele necesare. Daca nu folositi WSL pe Windows, scriptul prepare.ps1 genereaza fisierele necesare dar nu si compileaza sursele.

Folositi optiunea ”-h” pentru a afisa lista tuturor argumentelor folosite de program.

Uitati-va in fisierul salsa20.h. Unde se executa rundele de criptare?

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.

ic/labs/03.txt · Last modified: 2021/10/18 09:18 by razvan.smadu
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0