caricato da Utente2997

Appunti Crittografia

annuncio pubblicitario
Introduzione al corso
Libro prima parte corso: "Introduction to modern cryptography", J. Katz, Y.Lindell; Chapman & Hall.
Seconda parte corso: articoli di ricerca.
Prerequisiti
Algoritmica: ragioneremo con qualcosa del tipo "ho algoritmo A che in tempo polinomiale fa qualcosa e
lo uso come subroutine in un algoritmo B che lo usa per fare qualcosa". Gli algoritmi di solito sono
deterministici (passiamo da una istruzione alla successiva) noi ci concentreremo su quelli probabilistici,
ossia algoritmi che procedono in un modo o in un altro con una certa probabilità.
Calcolo delle probabilità: utilizzeremo probabilità sul discreto, in particolare lavoreremo con algoritmi
che potranno leggere uno stream di bit finito random (ma non lo sappiamo a priori, se lo conoscessimo
sarebbe det).
Crittografia
La crittografia è un insieme di tecniche algoritmiche che permettono, a due o più entità, di comunicare tra
loro in modo sicuro. Queste coinvolgono anche lo studio dei classi protocolli di rete e dovremo vedere, come
combinare questi due.
Di fatto la sicurezza implica la crittografia, una comunicazione è sicura se viene criptata. Vedremo cos'è
una comunicazione sicura e le tecniche per garantire le proprietà della sicurezza.
La crittografia verrà studiata in maniera formale e teorica.
La definizione di sicurezza per un cifrario viene data considerando avversari probabilistici, ossia un algoritmo
che cerca di rompere il cifrario. Il fatto che l'avversario possa essere probabilistico ha delle fondamenta solide.
L'avversario deve anche essere efficiente, deve utilizzare risorse limitate. A volte si assume anche che il
cifrario stesso è probabilistico, ad esempio nella chiave pubblica, in certi risultati, va di pari passo con il fatto
che il cifrario sia probabilistico, se non lo fosse, si può dimostrare che non è sicuro (in certi casi). Ad esempio
se in RSA non ci fosse il padding (che lo rende probabilistico) questo è considerato insicuro.
Aspetti della crittografia di interesse 1° parte
Crittografia moderna che va in contrasto con quella classica, che è quella fino agli anni '60/'70. Ma che cosa
c'era di diverso prima? La crittografia era un arte, si procedeva per tentativi per rompere un cifrario (pensa
a the imitation game). Non c'era una teoria precisa poi verso gli anno '70 la crittografia è diventata scienza ed
è nata la crittografia moderna. E' nata nel campo dell'informatica da gente che ha vinto il turing award.
La crittografia è frutto della complessità computazionale, che categorizza un insieme di problemi
computazionali in classi di complessità.
Un ponte tra la crittografia e la complessità è la pseudocasualità, che è anche centrale nell'evoluzione
probabilistica.
Vedremo il concetto di sicurezza perfetta, per implementarla ci sarà uno scotto alto da pagare, il teorema di
shannon dice che per avere la sicurezza perfetta, dovremo avere delle chiavi lunghe quanto i
messaggi, ma nessuno può farlo, non possiamo codificare un file di 4 giga con una chiave di 4 giga, sarebbe
troppo costoso. Quindi dovremo fare in modo di utilizzare una chiave corta ed espanderla preservando la
sicurezza e la casualità della chiave, per garantire questa cosa utilizzeremo i generatori di numeri
pseudocasuali.
Aspetti della crittografia di interesse 2° parte
Verifica di sicurezza dei protocolli crittografici, utilizzeremo un modello diverso da quello della prima parte
(algoritmica e probabilità) più semplice, in cui l'avversario e i cifrari non sono algoritmi polytime
probabilistici ma sono manipolatori di simboli, sono automi che manipolano messaggi visti come
sequenze/simboli o alberi sintattici. Questo modello ha avuto un grande impatto negli aspetti della verifica
legati alla crittografia, in pratica scompare l'aspetto quantitativo che utilizzavamo nella prima parte, la
sicurezza di un protocollo diventa così semplice che la si può automatizzare (vedremo dei tool).
Ci sono dei tool che prendono in input una rappresentazione del protocollo in forma di regole e producono in
output un "si il protocollo è sicuro/ no il protocollo non è sicuro".
Nella prima parte abbiamo quindi un modello computazionale mentre nella seconda parte avremo un
modello formale. Formale perchè gli avversari sono visti come manipolatori di espressioni, ciò che è formale
è il messaggio che verrà prodotto con una certa grammatica e non come una stringa di bit come lo
vediamo nella prima parte.
Tool interessanti
I tool che vedremo sono: Proverif e EasyCript. Dietro proverif c'è come avversario prolog, mentre dietro
easycript c'è la dimostrazione assistita.
Introduzione alla crittografia
Partiremo dai cifrari storici, capiremo perché siano inadeguati e troveremo una strada per ricavare una buona
nozione di sicurezza.
Crittografia classica e crittografia moderna
Prima della crittografia moderna si scrivevano cifrari un po' come farebbe un artigiano, senza avere linee
guida precise di natura matematica.
Con alcuni contributi fondamentali, alla fine degli anni '70 la crittografia diventa una scienza! Ciò permise di
andare oltre alla crittografia classica, che si occupava solamente della segretezza della comunicazione e
si definirono protocolli per l'autenticazione dei messaggi, lo scambio di chiavi, la firma digitale (caso
particolare autenticazione)....
Ricordiamo che il problema dell'autenticazione è ortogonale al problema della segretezza, ossia se
garantisco autenticazione non garantisco segretezza e viceversa. Ci piace avere entrambe le cose
ovviamente.
Da un lato quindi, vogliamo che il documento rimanga segreto ma vogliamo anche che sia autenticato.
Lo scambio di chiavi è il problema che si presenta in un ambiente distribuito aperto, in cui gli agenti in gioco
scambiano informazioni attraverso una chiave condivisa segreta (simmetrica vs non asimmetrica). Per
condividere la chiave questa va scambiata e questo pone un problema nuovo.
Definizione di crittografia
Oggigiorno, con crittografia si fa riferimento a qualunque tecnica volta a garantire la sicurezza delle
comunicazioni, delle transazioni e, in generale, del calcolo distribuito.
Cosa vuol dire comunicazione sicura?
Bisogna dare una definizione precisa, partiremo da dei cifrari (schemi di codifica) che sono insicuri, capiremo
il motivo dell'insicurezza di questi e ricaveremo le cose che invece vogliamo per ricavare una buona
definizione.
Crittografia a chiave segreta
Per definire la comunicazione sicura, partiremo da un particolare scenario, quelli dei protocolli volti a
garantire solo la confidenzialità nello scambio dei dati tra due parti e nient'altro.
M = messaggi che viene criptati nell'inoltro r decrittato in ricezione;
La linea punteggiata è un canale insicuro, sul quale viaggiano informazioni e con il quale l'avversario
può interagire two ways;
A è un agente attaccante che può interagire con il canale sicuro (attaccante). L'attaccante può interagire
in maniera two ways (può leggere dal canale insicuro e sfruttarlo magari anche per scrivere
simulando la comunicazione);
K è l'unica chiave utilizzata per crittare e decrittare, che deve essere preventivamente scambiata tra le
parti.
Le funzioni Enc, preso un messaggio e una chiave genera un crittogramma;
Le funzioni Dec, preso un crittogramma e una chiave genera il messaggio che era stato
precedentemente crittato;
Definizione formale di schema di codifica o Cifrario
Formalmente possiamo vedere uno schema di codifica come composto da tra spazi (per ora astratti)
e da una tripla di algoritmi (per ora astratti)
Gli spazi
dove:
sono tipicamente insiemi di stringhe:
Le chiavi sono insieme di stringhe binarie, magari a lunghezza finita magari a lunghezza arbitraria;
I messaggi potrebbero essere dati dalle stringhe in un linguaggio naturale oppure le stringhe in un
certo alfabeto non naturale(ad esempio prodotti da un software);
I crittogrammi saranno ancora delle stringhe ovviamente.
Come detto definiamo questi spazi in maniera astratta, non fissiamo un alfabeto nè la lunghezza delle
stringhe.
Gli algoritmi:
Gen, è una funzione che genera chiavi.
Enc e Dec sono triviali. Enc prende un messaggio definito dallo spazio dei messaggi e una chiave definita
sullo spazio delle chiavi e genera un crittogramma definito sullo spazio dei crittogrammi (stessa cosa per
Dec scambiando
e
).
Definizione di schema di cifratura corretto
Lo schema è corretto se
. Ossia ogni volta che genero un crittogramma a partire dalla
funzione Enc con messaggio x con una chiave k, se uso la funzione di decrittazione su quel crittogramma e la
stessa chiave k ottengo x.
Ovviamente non stiamo garantendo la sicurezza, stiamo dicendo che c'è solo modo per il ricevente di
recuperare il messaggio spedito dal mittente.
L'insieme 1 è un qualunque insieme di un solo elemento. Gen non può essere definito come elementi di K, è
un algoritmo che produce elementi di K da un certo input.
Algoritmo di generazione delle chiavi
Gen potrebbe essere vista come una funzione che prende in input un 1, ossia un qualunque insieme formato
da un solo elemento e restituisce una chiave.
Però c'è una precisazione da fare: una funzione a uno stesso input fa corrispondere lo stesso output, ma nel
nostro caso sarebbe orribile.
Quindi Gen lo vediamo non come una funzione, ma come un algoritmo probabilistico, che a uno stesso
input, produce un output sempre differente ogni volta che lo uso.
Di solito in Gen succede: se k è l'insieme delle stringhe di bit lunghe
una probabilità uniforme (
, Gen ne prende una qualsiasi con
).
In realtà l'1 in input non serve a niente, in realtà Gen è una funzione con nessun parametro è solo per far
vedere che abbiamo un comportamento funzionale a livello formale.
Principio di Kerchoff
Prima di dare una definizione di sicurezza per uno schema di codifica, occorre capire cosa possa fare
l’avversario A.
Armi a disposizione dell'attaccante
La prima cosa che ci si chiede è cosa A conosce del cifrario: magari anche niente, A potrebbe voler cercare di
forzare lo schema senza conoscere le funzioni Gen,Enc e Dec. Questo è un atteggiamento sbagliato, infatti
per Kerchoff, A conosce il funzionamento interno di Gen,Enc e Dec e quindi l'unico elemento di segretezza
dello scenario è proprio la chiave.
Perché prendiamo questo principio come vero?
1. Perché se dimostriamo che lo schema è sicuro in uno scenario in cui valga questo postulato, allora lo
schema è sicuro anche in uno scenario in cui l'avversario non conosca il funzionamento di Gen,Enc e
Dec. Questo è giustificato dal fatto che la conoscenza di A può cambiare nel tempo;
2. Inoltre, cambiare la chiave è semplice, basta rieseguire Gen mentre cambiare lo schema di codifica è
molto più complicato, dovremmo infatti riprogettare sia Dec,Enc e Gen (ecco perchè facciamo in modo
che l'avversario conosca tutto lo schema di codifica).
Essendo il postulato Di Kerchoff un principio non si può dimostrare ma è validissimo.
In passato, il Principio di Kerchoff è stato ripetutamente ignorato, con conseguenze devastanti.
Declinazione principio di Kerchoff
Il principio di Kerchoff si declinerà nel fatto che non diremo mai che lo schema è sicuro se non esiste un
avversario generico (non conosce lo schema) che forza lo schema, non deve esistere proprio un
avversario (che pur conoscendo lo schema) non lo forza.
Tipi di Attacchi
Attacchi passivi
Chipertext-Only Attack
L'attaccante legge e memorizza certi crittogrammi
sfruttando il canale insicuro , li analizza con le
capacità computazionali a sua disposizione e cerca di forzare il cifrario, ossia tenta di tirare fuori lo schema
per forzare il cifrario. Questa è la situazione più svantaggiosa per l'avversario.
Known-Plaintext Attack
L'avversario A conosce un certo numero di coppie
corrispondente a
, dove
è il crittogramma
.
Quindi,oltre a certi crittogrammi, l'avversario conosce anche il testo in chiaro relativo a quei crittogrammi.
L'attacco è comunque passivo perché l'avversario sfrutta le informazioni acquisite guardando queste
coppie, per capire qualcosa dei prossimi crittogrammi (ovvio perché di questo
ha già il testo in
chiaro).
Un attacco del genere si può presentare quando le informazioni vengono prima trasmesse sul canale insicuro
crittandole e poi le informazioni vengono rese pubbliche e si continua ad utilizzare la stessa chiave per
crittare i messaggi, oppure quando un mittente diventa avversario.
Attacchi attivi
Sono difficili da implementare nella vita reale.
Chosen-Plaintext attack
In questo caso l'attaccante sceglie il testo in chiaro, è attivo in questo senso, cioè lui non vede passare delle
copie ma è lui che decide il messaggio in chiaro (la doppia implicazione ora ha senso nello schema iniziale).
Catturiamo questa cosa, dando la possibilità all'avversario di calcolare l'Encoding del messaggio senza
conoscere la chiave, è come se avesse a disposizione un oracolo che gli fornisce la criptazione.
è una funzione da messaggi a crittogrammi che ricaviamo da
ma fissando la chiave!
L'avversario può comunque invocarla senza conoscere la chiave (la chiave k è già fissata), anche non
conoscendo la chiave (grazie a questo oracolo) per quante volte desidera!
Questo scenario si verifica quando l'attaccante riesce a simularsi mittente in modo indiretto, e tentare di
vedere come vengono codificati i messaggi.
Chosen-Ciphertext attack
L'attaccante sceglie anche il testo cifrato (l'oracolo ora è per
).
Ma questa cosa è impossibile, perché l'avversario avrebbe la possibilità di decriptare qualunque
crittogramma, quindi avremmo già finito perché l'avversario avrebbe già vinto.
Se ha accesso a questo oracolo, l'attaccante prende i crittogrammi li decodifica e capisce tutto del messaggio,
quello che si può dire è che l'attaccante ha a disposizione
ma non può essere utilizzato sui
crittogrammi che l'avversario vuole decrittare ma su tutti gli altri si. Ovviamente l'attaccante non ha ancora
accesso alla chiave.
La cosa interessante è che ci sono degli schemi sicuri anche per questo attacco e la cosa è ottima perché
siamo pronti a fronteggiare qualsiasi situazione.
Cifrari storici
Cifrario di Cesare
I messaggi in
sono semplicemente testi in una qualunque lingua. L'insieme
delle chiavi è molto semplice
. Ossia la chiave semplicemente trasla le lettere del messaggio di k posizioni avanti rispetto alla
posizione nell'indice dell'alfabeto.
La chiave è quindi unica ed Enc non fa altro che costruire il crittogramma rimpiazzando ciascuna lettere
dell'alfabeto con la lettera che viene 4 posizioni dopo rispetto all'indice dell'alfabeto:
La funzione Dec funziona in modo speculare.
Il problema del cifrario di Cesare è proprio nella chiave, che è unica! Chiunque conosca Enc decodifica
facilmente qualunque messaggio!
Cifrario a rotazione
E' una generalizzazione dello schema di cesare dove
diventa
dove
è il sottostante
alfabeto.
Le lettere ora non shiftano di un numero di posizioni costanti, ma di
posizioni, ma applico comunque la
stessa rotazione ad ogni lettere del mio messaggio.
Ovviamente
.
Con questo cifrario l'avversario non ha un modo non ambiguo per decretare i messaggi senza problemi, ora
lo spazio delle chiavi non è più formato da un solo elemento; tuttavia lo spazio delle chiavi risulta sempre
troppo piccolo (
elementi) e ciò consente all'avversario di fare bruteforce!
L'avversario prova tutte le chiavi finché non ottiene ,a partire dal crittogramma, un testo di senso compiuto (e
funziona solo in questo caso)! Se usassimo questo attacco per decrittare un file compresso che non ha senso?
L'attacco non funziona più ma il cifrario non è comunque sicuro.
Dobbiamo quindi impedire all'avversario di fare bruteforce!
Cifrario a sostituzione monoalfabetica
Può essere visto come un’ulteriore generalizzazione dei due cifrari precedenti.
Lo spazio dei messaggi
è lo stesso, mentre lo spazio delle chiavi diventa l'insieme delle permutazioni
dei simboli dell'alfabeto (eliminando la permutazione identica).
è
Quello che fa
è applicare la permutazione, che vedo come chiave, carattere per carattere
(generalizzazione perché lo shift è una permutazione, ma ci sono permutazioni che sono molto lontane
dall'essere shift, la permutazione rende il tutto caotico).
Inoltre, ora la cardinalità dello spazio delle chiavi è il fattoriale sulla cardinalità dell'alfabeto, appena
l'alfabeto inizia ad essere lungo il bruteforce non è più possibile.
Il problema è che lo schema però non è sicuro a causa degli attacchi statistici, quindi l'idea che il caos possa
garantire la sicurezza è confutabile!
Attacchi statistici
Per il principio di Kerchoff, l'avversario non conosce la chiave, ma conosce
(spazio dei messaggi) e
conosce anche la probabilità che certe stringhe e certi simboli compaiano nei messaggi (ogni insieme ha
certe probabilità statistiche).
Se dei messaggi in
se sono in una certa lingua, le stringhe in esse non sono casuali, la stringhe in inglese,ad
esempio, hanno una probabilità di trovare una
molto alta e di trovare una
molto bassa.
Idea attacco statistico
L'idea è che l'avversario possa analizzare le frequenze
nell'alfabeto
di ciascun simbolo che compare
, all'interno crittogramma c, confrontandole con quella degli stessi simboli nei messaggi in
:
Da un lato abbiamo informazioni statistiche sullo spazio dei messaggi (probabilità della lettere a),
dall'altro ho le frequenze dei simboli in un messaggio, quello che si fa è matchare i due, ossia si cerca di far
corrispondere le sequenze
(dei messaggi) con le sequenze
(simboli della lingua), dove
è la probabilità
dell'occorrenza del simbolo con indice all'interno del messaggio.
Quando
cresce, la probabilità di successo cresce. Questo perché l'ultimo modello discusso era il cifrario a
sostituzione monoalfabetica, e sappiamo che al crescere della lunghezza dei crittogrammi cresce anche la
lunghezza del messaggio. Quindi, il messaggio diventa sempre più lungo e la statistica del messaggio
collassa a quella della lingua.
Attacco statistico su Cifrario a rotazione
Possiamo fare un attacco statistico anche su un cifrario a rotazione che non è dipendente dal senso
compiuto. Posso calcolare infatti il valore:
In pratica, moltiplico le probabilità di occorrenze di tutti i singoli simboli dell'alfabeto nel messaggio e ne
calcolo la sommatoria. Poi calcolo
L'idea è che quando j è la chiave utilizzata (ricordiamo che nel cifrario a rotazione la chiave è un numero),
allora
collassa verso
, questo perché se ci pensiamo
è la frequenza del simbolo i dell'alfabeto nel
crittogramma, quindi al crescere della lunghezza del crittogramma
Invece, quando j non è la chiave utilizzata il valore di
valore j (qualche chiave) le quantità
ed
collassa a
.
è più basso, quindi il criterio è cercare per quale
collassano.
Cifrario di Vigenère
Detto anche cifrario a sostituzione Polialfabetica, generalizzazione del cifrario a rotazione.
Il guaio principale del cifrario a sostituzione monoalfabetica è che gestisco un simbolo alla volta e
permetto gli attacchi statistici, devo fare in modo di gestire più simboli in modo diverso. Quello che si fa è
prendere una stringa come chiave, e ruoto, quindi, non più singoli caratteri ma pezzi di stringhe usando
questa chiave.
Lo spazio delle chiavi è l'insieme delle stringhe di lunghezza finita in
Sostanzialmente quello che succede è che la chiave
, ossia
.
è una stringa formata da m caratteri, e questa
chiave consente di ruotare ogni carattere all'interno della sottostringa
di cui il messaggio è
composto, ma questo avviene in maniera ciclica (guarda esempio sopra).
Tutte i caratteri della sottostringa del messaggio vengono "ruotati" in modo diverso, quindi gli attacchi
statistici non possono essere applicati perché si basano sul fatto che la permutazione di tutti i simboli sia
la stessa, invece qua c'è una stringa per ogni carattere. Inoltre, l'insieme delle chiavi è abbastanza ampio per
evitare il bruteforce!
Periodo della chiave
Ci domandiamo quindi: Il cifrario è sicuro? Purtroppo no, possiamo fare comunque attacchi di tipo
statistico, il problema è il periodo della chiave detto .
Il periodo della chiave non è altro che la lunghezza della chiave (nell'esempio era
).
Attacco statistico su Vigenère sfruttando periodo
Se il periodo fosse noto (numero di chiavi) diventa tutto molto semplice: posso applicare sulle
sottostringhe del messaggio le tecniche statistiche viste;
Se invece il massimo periodo
provare tutte le chiavi);
è piccolo, forzo il cifrario sul periodo per tentativi (che non vuol dire
Se invece, il massimo periodo
fosse troppo grande, questo non è vero ci sono delle tecniche come il
metodo Kasiski. Questo metodo si basa non solo sul fatto che alcuni simboli sono più frequenti di
altri, ma che più bigrammi o trigrammi sono molto frequenti (ad esempio in inglese "the" compare
molte volte). Vediamo un esempio:
In questo esempio ci sono 4 occorrenze di "the", queste occorrenze stanno a una distanza che è un
multiplo della lunghezza della chiave (che è beads in questo caso). Kasiski cerca, senza conoscere nulla
della chiave, di occorrenze multiple di bigrammi o trigrammi, in questi caso LLI compare due volte. Dopo
aver trovato queste occorrenze calcola il massimo comun divisiore tra le distanze di queste occorrenze
ed è probabile che questo sia proprio il periodo della chiave!
Infine c'è il metodo dell'indice di coincidenza (definito come la probabilità che due elementi scelti a caso
siano uguali). In pratica, per valori crescenti di
in posizione
numero naturale, tabuliamo i caratteri del crittogramma
ottenendo le frequenze
, che sono relative a
sottostringa non nell'intero crittogramma. Quello che mi aspetto è che quando
nella
è pari al periodo,
ottengo una sottostringa del crittogramma applicando sempre la stessa rotazione, certamente i
saranno i
non
ma saranno un riarrangiamento degli stessi. A questo punto basta calcolare:
e verificare per quali valori di
i valori di
e
sono vicini e a quel punto
sarà probabilmente il
periodo.
Morale dei cifrari storici
Da un lato lo spazio delle chiavi deve essere sufficientemente grandi a impedire brute-force, ma se lo spazio
delle chiavi è ampio abbiamo solo passato un minimo check, il cifrario non è di certo sicuro (condizione
necessaria ma non sufficiente).
Dall'altro la complessità descrittiva di un cifrario non dà nessuna garanzia sulla relativa sicurezza, non è
detto che se un cifrario sia complesso allora sia sicuro (infatti per il principio di Kerchoff, l'avversario conosce
Gen,Enc,Dec).
Principi della crittografia moderna
Uso di definizioni formali e rigorose (matematiche) della sicurezza di primitive e protocolli:
1. Non ci si può limitare a dare definizioni informali. Occorre essere in grado di eliminare ogni
ambiguità, altrimenti la nostra nozione di sicurezza sarebbe vacua;
2. Il rischio è quello di chiedere troppo al cifrario, però dobbiamo correrlo.
Precisione nella specifica delle sottostanti assunzioni:
1. Senza assunzioni non si riesce a dimostrare nulla sulla sicurezza di primitive e protocolli (per
dimostrare qualcosa, ossia la sicurezza, abbiamo bisogno di fare delle ipotesi).
2. Bisogna sempre essere rigorosi.
Prove di sicurezza scritte nel linguaggio della matematica:
1. Quando definizioni formali e assunzioni sono prive di ambiguità, la tentazione è quella di dedurre
direttamente la sicurezza dello schema dalle assunzioni.
2. Aneddoto storico: c'era uno schema di codifica a chiave pubblica la quale prova di sicurezza è stata
fatta di fretta ed è stato rifatto varie volte. La m orale è commettere errori nella dimostrazione è
molto facile perchè ci sono molti casi da dimostrare (anche se sono logicamente facili). Per risolvere
questo problema automatizziamo il processo (easy crypt).
Quando Formulare definizioni precise diventa cruciale?
In fase di Progetto: Se non sai a cosa stai puntando, come è possibile che tu vada nella giusta direzione?
E come è possibile che tu ti renda conto che l’obiettivo è stato raggiunto?
In fase di Utilizzo: Una definizione di sicurezza precisa può essere utilizzata per dimostrare che uno
schema esistente ha (o non ha) le proprietà di sicurezza desiderate;
In fase di Studio e Confronto:
Possono esistere diverse, più o meno stringenti, definizioni di sicurezza per lo stesso tipo di schema.
Un modo per scegliere tra schemi diversi diventa quello di confrontare le garanzie di sicurezza
offerte da ciascuno di essi.
La cosa migliore è dimostrare qualcosa di forte (sicurezza) con assunzioni deboli (sarebbe perfetto).
Formulare definizioni precise - Esempio
Nel contesto della crittografia a chiave privata e degli schemi di codifica, chiediamoci: quando un tale
schema si può considerare sicuro?
Ricordiamo che lo schema è formato da
.
Prima idea di definizione schema sicuro
Uno schema è sicuro quando nessun avversario A può determinare la chiave, date le informazioni a
sua disposizione (chipertext-only).
Non va bene, perché potrebbe esistere uno schema di codifica (secondo questa definizione) in cui
. Cioè il crittogramma generato da un messaggio con una certa chiave è proprio il messaggio
sicuro, quindi l'avversario non ha nemmeno la possibilità di decifrare k perché non dipende dal messaggio!
Ma l'attaccante non ha alcun bisogno di scoprire la chiave, perché l'algoritmo
genera un
crittogramma che corrisponde al messaggio in chiaro!
Vogliamo che l'attaccante non riesca a costruire il messaggio in chiari!
Seconda idea di definizione schema sicuro
Uno schema è sicuro quando nessun avversario A può ricostruire
a partire da
. Anche
questo non va bene, infatti uno schema che sarebbe ammesso dalla definizione, sarebbe quello in cui
l'avversario riesca a ricostruire gli ultimi 10 bit di
da
. Un pezzo del messaggio sarebbe, se
vogliamo, in chiaro!
Io voglio che l'avversario non riesca a ricostruire nessun pezzo del messaggio.
Terza idea di definizione schema sicuro
Uno schema è sicuro quando nessun avversario
riesce a determinare nessun bit di
a partire da
. Non ci siamo, infatti uno schema ammesso da questa definizione sarebbe quello in cui
l'avversario riesce a determinare se certi bit in
sono in una certa relazione tra loro [ad esempio
l'attaccante potrebbe riconoscere due bit uguali].
L'avversario potrebbe carpire informazioni quali il risultato di una partita di calcio (è un pareggio oppure no)
guardando l'uguaglianza tra bit conoscendo la struttura dei messaggi.
Quarta idea di definizione schema sicuro
Uno schema è sicuro quando nessun avversario A riesce a determinare nessuna proprietà (decidibile?)
di
a partire da
. L'avversario non deve capire assolutamente niente!
Siamo molto vicini alla nozione di schema sicuro, ma siamo ancora imprecisi.
Ad esempio, rispetto a questa definizione, cosa possiamo dire del cifrario a sostituzione monoalfabetica?
E' evidentemente insicuro, perché riusciamo a capire quante volte è ripetuto un certo simbolo (proprietà
decidibile).
Precisione nella specifica delle assunzioni
Assunzioni imprecise non possono essere validate né refutate.
Nonostante le assunzioni non siano né dimostrate né refutate, sono certamente studiate, e questo
studio porta a congetturare la loro verità.
In assenza di una specifica precisa, lo studio diventa difficile, fondamentalmente impossibile.
Schemi diversi possono essere confrontati.
Schemi la cui sicurezza si basi su assunzioni deboli sono ovviamente da preferire a schemi in cui le
sottostanti assunzioni siano molto forti.
Confrontare assunzioni diverse è possibile solo in presenza di una formalizzazione rigorosa.
Assunzioni sufficientemente deboli possono non essere influenzate da un attacco.
Sicurezza perfetta
In questa parte del corso studieremo un primo modo per formalizzare il concetto di sicurezza perfetta
per uno schema di codifica.
Mostreremo che, pur essendo molto forte, tale nozione di sicurezza è effettivamente implementabile.
La nozione sicurezza perfetta è eccellente, ma soffre di alcune severissime limitazioni:
Per questo, pur essendo stata introdotta e studiata ben prima degli anni Settanta, non ha mai preso
piede, se non in contesti estremamente specifici.
Utilizzeremo molto la probabilità e l'algoritmica.
Prepariamo la scena
Schema di codifica
La nozione di schema di codifica è la stessa, ossia una tripla di algoritmi
Nel contesto della sicurezza perfetta
può essere probabilistico ma
E' giustificabile pensare che a uno stesso messaggio, l'algoritmo
.
rimane deterministico.
faccia corrispondere crittogrammi
diversi.
Tuttavia perché
non può essere probabilistico? Vogliamo avere uno schema corretto,
deve sempre
restituire dei messaggi corretti, ossia quando decifriamo da crittogrammi diversi ma che derivano dallo
stesso messaggio, il messaggio in chiaro ricavato con
è sempre lo stesso e non uno diverso!
Sicurezza perfetta e probabilità
Nella sicurezza perfetta, parliamo di probabilità perché la scelta della chiave, codifica e decodifica sono
visti come processi probabilistici (processi in cui c'è incertezza sulla scelta della chiave, incertezza della
forma del crittogramma).
Dovremo definire quindi uno spazio di tutte le possibili combinazioni di scelte.
Come catturo questa situazione con la probabilità? Con le variabili casuali, ne bastano 3: K,M,C:
K corrisponde alla chiave utilizzata e la quale distribuzione dipende da
proprio perché è
quest'ultimo che la genera. La probabilità che una certa chiave dello spazio delle chiavi venga tirata fuori
dovrebbe essere uniforme:
;
Poi M, che cattura il messaggio del mittente. In questo caso qui non c'è un calcolo, è il mittente a
scegliere il messaggio. Da notare che la probabilità che capiti un certo messaggio deve essere
indipendente dalla sicurezza, infatti anche nel caso in cui il mittente invii sempre lo stesso messaggio
devo garantire la sicurezza dello schema;
Infine C, variabile casuale che corrisponde al crittogramma che è fortemente dipendente sia da M,K
ed
. E' dipendente nel senso che
mi dice come C assuma un certo valore basandosi su M e K.
Quantità probabilistiche cruciali
Possiamo ora dare delle quantità cruciali:
La probabilità che la chiave abbia un certo valore :
La probabilità che la chiave sia
Distinguiamo quindi
K
dato un certo messaggio
(messaggio), M (variabile casuale) ed
;
:
K
M
.
come spazio dei messaggi (stessa cosa per la
chiave).
Definizione di sicurezza perfetta
Ipotesi prima della definizione
La scelta della chiave e del messaggio sono da considerarsi indipendenti (vedendole come variabili casuali).
Cosa dire del messaggio prima e dopo la codifica (rapporto tra M e C, variabile casuali)?. Il valore di C
dipende da M ovviamente, ma in che senso? Vogliamo che conoscere il valore di C non modifichi la
conoscenza di M (vogliamo utilizzare la probabilità condizionata).
Definizione di sicurezza perfetta
Il senso della definizione è che se il crittogramma è
non so niente in più di
(che capiterà con una probabilità non nulla ovviamente),
di quanto invece so senza vedere .
In altre parole, se mi metto in una situazione in cui non vedo il crittogramma C (come variabile casuale perché
stiamo parlando di probabilità) e valuto la probabilità di M e poi non vedo più C la probabilità di M non
cambia.
Il tutto è molto elegante, tuttavia non cita il modello di calcolo, parliamo di algoritmi senza conoscerne le
caratteristiche vere e proprie.
Lemmi della definizione di sicurezza perfetta
Lemma 1
Questo lemma ci dice che non solo vedere il crittogramma non mi dice niente del messaggio (definizione
di sicurezza) ma anche vedere il messaggio non mi dice niente del crittogramma.
Per dimostrarlo useremo il teorema di Bayes che permette di invertire l'evento condizionato con il
condizionante.
Lemma 2
Questo lemma ci dice che fissati due messaggi la probabilità di vedere lo stesso crittogramma da tutte e
due è la stessa.
Stiamo dicendo che la probabilità di osservare un certo crittogramma
messaggio
dato
è la stessa di vedere un
. Cioè la possibilità di vedere in output lo stesso crittogramma a partire da due messaggi è la
stessa.
Il Cifrario di Vernam (One-time pad)
E' il cifrario concreto per cui vale la sicurezza perfetta ma che ci mostra anche i limiti della stessa.
Descrizione del cifrario
Sia lo spazio delle chiavi, che dei messaggi che dei crittogrammi è l'insieme di tutte le stringhe binarie di
lunghezza n (ossia
e non
altrimenti la lunghezza sarebbe arbitraria), dove n è uguale per tutti
e tre gli spazi. Gli abitanti di questi insiemi sono quindi stringhe binarie.
La probabilità di osservare qualunque chiave è quindi
. Questo perché semplicemente, stiamo lanciano n
volte una moneta (infatti scegliamo n volte o 0 oppure 1) e quindi la probabilità di vedere una qualunque
chiave in questo modo è proprio
e
.
sono definiti sull'operatore xor (operatore di disgiunzione).
L'operatore xor,algebricamente, ha delle proprietà sulle stringhe binarie:
1. E' un operatore associativo e commutativo;
2. E' impotente, ossia
produce 0.
3. Queste proprietà ci servono per dimostrare la correttezza del cifrario.
Il cifrario così generato è, senza dubbio, corretto
In particolare, in queste equivalenza abbiamo sfruttato l'associatività, la commutatività e l'impotenza dello xor
(nel passaggio
).
Limitazione cifrario di Vernam
Notiamo come, in questo cifrario, la lunghezza dei messaggi e delle chiavi abbiano la stessa lunghezza (se
critto un messaggio di un terabyte, dobbiamo utilizzare una chiave di un terabyte).
Limitazione molto forte, però in passato è stato utilizzato nel periodo della guerra fredda.
Ricordiamo inoltre che le chiavi si usano una volta sola (one time pad). Perchè è necessario usare le chiavi
una sola volta? Se utilizzo la stessa chiave più volte perchè perdiamo tutte le proprietà di sicurezza, non
possiamo usare la stessa chiave per cifrare messaggi diversi, un semplice attacco non-plaintex ci
permetterebbe di tirare fuori la chiave e di qualunque altro crittogramma che passa sul canale (basterebbe
fare lo xor per tirare fuori la chiave).
Questo cifrario è sicuro per codifiche singole (infatti si chiama one-time pad).
Limitazione Sicurezza perfetta
Purtroppo la limitazione del cifrario di Vernam è estendibile a tutti quei cifrari che garantiscono la sicurezza
perfetta.
Ossia se lo schema è sicuro abbiamo che la cardinalità dello spazio delle chiavi è maggiore uguale della
cardinalità dello spazio dei messaggi (vero anche per schemi non one-time pad).
La morale è che in certi contesti utilizzare un cifrario che ci limita in questo modo è impossibile (oppure
potrebbe essere l'unica soluzione possibile).
Caratterizzazione dell'Indistinguibilità
Finora abbiamo parlato della sicurezza perfetta senza parlare dell'avversario, ora vediamo nel concreto un
possibile scenario di attacco. In particolare caratterizziamo l'indistinguibilità, considerando un esperimento
, ossia una sorta di gioco, in cui il cifrario
e l'avversario
vengono messi uno contro l'altro.
Questo gioco viene visto come una sorta algoritmo!
dove
:
Sta per privato, ossia contesto della chiave privata;
E' parametrizzato su un avversario
Da notare che il parametro
e uno schema di cifratura
;
è quantificato "universalmente" sta a dire "per tutti gli A, non solo per un
certo attaccante" mentre il cifrario è quello che prendiamo in considerazione.
Il risultato è un valore booleano che mi dice se l'avversario di vincere (1) o perdere (0). L'algoritmo ha quindi il
compito di dirci quale probabilità ha l'avversario di vincere.
Descrizione Algoritmo
All'inizio l'avversario genera due messaggi
. Siccome non vogliamo restringerti su una certa
distribuzione di probabilità per i messaggi, vogliamo che sia l'avversario a decidere come costruire i
messaggi e ne costruisce due per complicare le cose (l'avversario quindi agisce). Il modo con cui
l'avversario produce A potrebbe anche non essere qualcosa di ammesso dalla teoria della calcolabilità (A
potrebbe anche essere una funzione non calcolabile);
Nella seconda istruzione utilizziamo
per generare una chiave. Tuttavia non crittiamo tutti e due i
messaggi, ma solo uno e lo decidiamo noi;
Infatti nella terza istruzione (
probabilità
) generiamo un valore probabilistico (sia
che
hanno
di uscire) e lo assegniamo a b.
Nell'istruzione successiva viene generato il crittogramma di uno dei due messaggi (scelto grazie a ) e lo
passiamo all'avversario;
A questo punto l'avversario deve capire quale dei due messaggi è stato crittato (sappiamo per ipotesi
che questi due messaggi hanno la stessa lunghezza per ipotesi di sicurezza perfetta, l'avversario
quindi non può distinguerli per lunghezza).
L'avversario produce un valore basato su
(ovviamente
discapito delle aspettative, vedremo che il valore
si ricorda i messaggi che ha generato). A
è generato casualmente, l'avversario infatti non può
fare di meglio (perché abbiamo due messaggi e tirare a caso garantisce la probabilità di successo pari a
).
Restituiamo quindi
che vale
se sono diversi e
se sono uguali (l'avversario ha indovinato).
Poniamoci delle domande sulle quantità probabilistiche
Ora possiamo chiederci quale sia la probabilità che l'esperimento abbia esito positivo per l'avversario con
probabilità massimale, ossia:
Ha senso chiedersi questa cosa? No, perché se fosse così il nostro cifrario avrebbe una grossa falla.
Ma allora avrebbe senso chiedersi, ad esempio, se:
Ovviamente no, perché a quel punto, per avere la probabilità massimale di vincita ci basterebbe
invertire quello che restituiamo di solito (rifletti bene su cosa stai leggendo!) !
Codifica indistinguibilmente perfetta
Quindi stiamo dicendo che la cosa migliore che l'avversario può fare, nel cercare di costruire a partire dalla
codifica di
è proprio tirare a caso.
N.b. Tutto ciò che abbiamo detto finora vale perché l'avversario non può inserire informazioni all'interno di un
messaggio (nonostante sia lui a generarli) che gli consentirebbero di riconoscere "dei pattern" all'interno del
crittogramma.
Teorema interessante
Dimostrazioni Sicurezza Perfetta
Dimostrazione Lemma 1
La dimostrazione ha due sensi:
1) Se lo schema è perfettamente sicuro allora vale che per ogni messaggio
2) Se vale che per ogni messaggio
...
... allora lo schema è perfettamente sicuro.
Ricordiamo che la nozione di schema perfettamente sicuro:
Prima implicazione
Ho come ipotesi la sicurezza perfetta, quindi ho:
Applico il teorema di Bayes che mi permette di esprimere una probabilità condizionata su una probabilità
condizionata ad essa duale:
quindi, mi basta moltiplicare gli estremi dell'uguaglianza per
e dividere gli stessi con
per ottenere che:
N.B. Abbiamo implicitamente detto che
è maggiore di 0, altrimenti non potremmo fare le
operazioni svolte (anche se era specificato nella definizione di sicurezza perfetta corretta, che valeva per
tutti quei crittogrammi che avevano una probabilità maggiore di 0 di comparire!).
Seconda implicazione
E' uguale ma al contrario, non vale la pena di perdere il tempo di dimostrarla!
Dimostrazione Lemma 2
Anche qui abbiamo due versi (non li specifico, basta leggere il lemma 2). Però bisogna fare attenzione ai
quantificatori (il lemma deve valere
).
Prima implicazione
Ho come ipotesi la sicurezza perfetta e devo dimostrare che vale quindi che ogni coppia di messaggi...
Ma se ho la sicurezza perfetta, so che vale il lemma 1, ma allora basta applicare questo lemma due volte:
Seconda implicazione
:
Devo dimostrare la sicurezza perfetta a partire dall'uguaglianza del lemma2. Ma questa uguaglianza, dato
che vale
, ci dice che la probabilità di vedere un certo crittogramma dato un qualunque
messaggio è una costante perché è uguale per qualunque messaggio.
Quindi se vale questa cosa:
vuol dire che esiste una costante
che corrisponde alla probabilità di vedere un certo crittogramma per
qualunque messaggio. A questo punto costruiamo una catena di uguaglianze che dimostrino che:
Per farlo possiamo decomporre la probabilità
in questo modo:
Questo è un classico passaggio in probabilità: prendiamo la probabilità che il messaggio sia proprio
moltiplichiamo con la probabilità condizionata che mostra che il crittogramma è proprio
messaggio è proprio
e facciamo la somma di tutti questi prodotti (per tutti i messaggi possibili).
A questo punto
è , ma siccome sappiamo che esiste un unico
che è quindi costante,
possiamo tirarlo fuori dalla sommatoria:
ma la sommatoria di tutte le probabilità di tutti i messaggi è proprio 1, quindi:
Ma allora
!
Dimostrazione Teorema Vernam
Dimostrazione
Per dimostrare che il cifrario di Vernam è sicuro devo dimostrare che l'oggetto
costante indipendente sia da
sia
è una
che da .
Per farlo utilizziamo lo xor e la variabile casuale
, dove sia
e
sapendo che il
. Ora sappiamo che il generico
nel cifrario di Vernam è
sono variabili casuali relative a messaggi e chiavi! Ma allora:
ora posso fare una cosa furba, se so che
posso eliminare il condizionante nella proprietà
condizionata:
ora c'è un'unica variabile casuale, che è
.
Ma ora, posso utilizzare le proprietà di commutatività e impotenza dello xor:
Sappiamo, per definizione del cifrario di Vernam che
, ossia la probabilità che possa
comparire una qualunque chiave è costante e non è di certo dipendente da
o da
, è sempre la
stessa! Ma allora:
Conclusione
Ma allora, questo vuol dire che la probabilità di vedere un crittogramma a partire da un qualunque messaggio
è una costante:
e con questo abbiamo dimostrato che il cifrario di Vernam è perfettamente sicuro (con questo abbiamo
dimostrato il lemma 2 in sostanza, ma il lemma 2 è in sse con la definizione di schema perfettamente sicuro,
quindi ci siamo!).
Dimostrazione limitazione Vernam
La dimostrazione la faremo per assurdo! Assumiamo che, per assurdo,
vale
ossia
(basta anche che
sia perfettamente sicuro ma non
abbia un elemento in meno di
).
Possiamo fare tutta una serie di scelte:
Possiamo scegliere la distribuzione dei messaggi (nella definizione di sicurezza perfetta non abbiamo
vincoli sulla distribuzione con la quale creiamo i messaggi) e scegliamo la distribuzione uniforme (tutti i
messaggi hanno la stessa probabilità di essere utilizzati);
Possiamo fissare un crittogramma
Ora, costruiamo un insieme di messaggi:
che abbia probabilità non nulla di apparire
.
questo insieme non è altro che la contro-immagine di
Ora mettiamo in relazione la cardinalità di
rispetto a .
con quella di
, quello che mostriamo è che:
è sicuramente vero perché ci sono sicuramente dei messaggi che sono la controimmagine di
ma non possono essere di più delle chiavi;
Sappiamo per ipotesi che
, ma allora
quindi ci sarà almeno un messaggio che sta in
Il fatto che esista un
è strettamente più piccolo di
ma non in
che non è nella contro-immagine di
[
, vuol dire che
].
ci da modo di evidenziare l'assurdo
rispetto alla sicurezza perfetta, infatti:
Per quanto riguarda
questa è sicuramente maggiore di 0 poiché abbiamo detto che la
distribuzione dei messaggi è uniforme (sarà
Mentre
che il nostro crittogramma è
.
proprio perché c'è un messaggio che ha probabilità 0 di uscire se sappiamo
(per tutto il ragionamento fatto poco fa).
Quindi la seconda condizione della sicurezza perfetta non è rispettata e quindi assurdo (infatti per la
sicurezza perfetta doveva valere che (
).
Schemi di codifica a chiave segreta
Come detto la sicurezza perfetta pone delle limitazioni fortissime, per questo viene indebolita, passando ad
una definizione che offre garanzie meno forti ma dando anche limitazioni meno forti.
Tra le altre cose, vedremo schemi di cifratura che utilizzano chiavi corte per criptare messaggi molto grandi
(ordine dei gigabyte), riuscendo quindi a superare le limitazioni poste da cifrari come il one-time pad.
Dopo di che, studieremo la pseudorandomicità, grazie alla quale potremo mostrare cose come se fossero
completamente random anche se in realtà non lo sono.
Ci concentreremo sul contesto degli schemi di codifica a chiave segreta!
Computational security vs Information-theoretic security
(libro)
Finora (prendendo in considerazione cifrari come one-time pad), abbiamo parlato di sicurezza perfetta o
information theoretic security e abbiamo garantito la sicurezza del nostro cifrario sfruttando il fatto che il
nostro avversario non possiede abbastanza informazioni per riuscire a forzare il cifrario (a prescindere dalla
potenza computazionale del nostro avversario). Tuttavia queste pone troppe limitazioni (lunghezza della
chiave).
Per questo, parlando di crittografia moderna si passa alla definizione di computational security, ossia non
si garantisce che il cifrario non possa essere rotto ma che per essere forzato, l'avversario debba impiegare
un quantitativo enorme di risorse temporali e computazionali, considerando anche il caso in cui l'avversario
dovesse usare il computer più potente del mondo. Queste garanzie sono meno forti rispetto alla sicurezza
perfetta, ma abbiamo anche meno limitazioni.
La cosa interessante è che, pur effettuando questo cambiamento, a noi va comunque bene! Per Kerchoff: "Un
cifrario dovrebbe essere praticamente, se non può essere matematica,indecifrabile", per avere uno schema sicuro
non dobbiamo quindi garantire la sicurezza perfetta ma basta garantire che questo non possa essere rotto
in un tempo ragionevole con ogni probabilità di successo.
Cambio di scena
E' ora di cambiare scenario, dobbiamo apportare dei cambiamenti allo scenario della sicurezza perfetta,
dobbiamo indebolirla per abbassarne le limitazioni.
Questo cambio di scena arriva con l'avvento della crittografia computazionale, nata alla fine degli anni
settanta!
Questo cambiamento avviene in due direzioni ortogonali tra loro:
Ci si concentra non su tutti gli avversari (come facevano con la nozione di indistinguibilità) ma solo su
avversari efficienti. Prima infatti, utilizzavamo una quantificazione universale sugli avversari (che
potevano anche non essere degli algoritmi e nonostante questo il one-time pad continuava a
funzionare), ora invece gli avversari sono funzioni non solo calcolabili ma efficienti. Utilizzeremo la
complessità computazionale.
Come detto prima, ora l'avversario può forzare il sottostante schema, ma la sua probabilità di
successo dev'essere molto piccola. Prima non c'era, nella teoria di Shannon (sicurezza perfetta) il
fatto di avere la probabilità condizionata uguale a quella non condizionata indicava che l'avversario non
poteva fare nulla, in questo caso può forzare lo schema ma ha una possibilità molto piccola di vincere
(vedi paragrafo precedente).
Queste due nozioni sono entrambe necessarie!
Effetti collaterali
Dobbiamo pagare lo scotto per imporre le due condizioni citate prima.
Primo effetto collaterale
Il primo effetto collaterale è che dobbiamo lavorare con le stringhe (nella sicurezza perfetta i messaggi,
crittogrammi e chiavi erano elementi generici).
Questa necessità è dovuta alla complessità computazionale (che è definita in base a problemi e funzioni
che lavorano su stringhe, perché altrimenti non avremmo modo di definire cosa sia un messaggio, una
chiave o una stringa). Come sappiamo ci bastano le stringe binarie per parlare di problemi computazionali,
perché qualunque problema (grafi, alberi, etc...) può essere codificato come stringa binaria. Inoltre, la
complessità computazionale richiede che si possa almeno misurare la lunghezza dell'input!
Secondo effetto collaterale
Il secondo effetto collaterale: le prove di sicurezza incondizionate sono impossibili, ci basiamo sempre su
assunzioni. Non potremo mai dare un risultato come il cifrario di Vernam (il cifrario è sicuro a prescindere),
dovremo dare sempre delle condizioni per garantire la sicurezza ("Il cifrario è sicuro se...").
Concetto di parametro di sicurezza
Non soltanto lavoreremo con algoritmi efficienti, ma lavoreremo con il parametro di sicurezza che è un
numero intero, definito a priori nello schema e condiviso dallo schema e dall'avversario. Questo parametro
si declina, di solito, nella lunghezza delle chiavi, ossia chiavi più lunghe corrispondano a valori più grandi del
parametro di sicurezza.
Due approcci alla nozione di efficienza di A
Come dimostrare la nozione di efficienza degli avversari? Abbiamo due approcci (useremo il secondo):
Approccio concreto
Nell'approccio concreto, uno schema crittografico è definito sicuro in senso
(la sicurezza è
parametrizzata su una coppia dove i parametri sono entrambi numeri (il primo naturale e il secondo
reale tipicamente)), quando ogni avversario che lavori in tempo al più riesce a forzare lo schema con
probabilità al più .
Ad esempio potremmo dire che uno schema è sicuro con
un tempo al massimo pari a
, ossia un avversario che lavora per
riesce a forzare il cifrario con una probabilità al più
. Da notare che
quando cresce uno cresce anche l'altro (quando ho più tempo cresce la probabilità di successo) ma
tipicamente è molto grande mentre è molto più piccolo.
Sembra una definizione ottima: se dimostriamo la sicurezza di uno schema con altissimo ed bassissimo
vuol dire che nessuno riuscirà in un tempo ragionevole a forzare il nostro schema (con certe assunzioni
ovviamente).
Il problema di questo approccio è che è molto legato all'hardware che l'avversario utilizza ( rende il
giudizio dipendente dall'hardware)e come sappiamo l'hardware potrebbe sempre migliorare...
Potremmo generalizzare considerandolo non come il tempo ma come il "numero di istruzioni che
un'architettura astratta deve eseguire per fare cosa". Ma poi dovremmo specificare quanto tempo le varie
istruzioni impieghino in un certo hardware (definizione fragile).
Approccio asintotico
Con questo approccio fissiamo a priori il concetto di efficienza e il concetto di errore accettabile o bassa
probabilità di successo (non parametrizziamo più su dei numeri concreti) e i giudizi di sicurezza dipendono
da un parametro globale , detto parametro di sicurezza.
Il concetto di efficienza, viene catturato dalla classe degli algoritmi PPT, mentre quello di bassa probabilità
di successo viene catturato con il concetto di funziona trascurabile. I due concetti lavorano molto bene
insieme, sono l'uno il duale dell'altro, e nella definizione di entrambi il concetto di polinomio è
fondamentale, ma quello che guida tutto è il concetto di PPT (probabilistic polynomial time).
Nonostante sembri il contrario, con questo schema non perdiamo contatto con la realtà, perché dopo aver
dimostrato la sicurezza dello schema, con questo approccio, possiamo fissare un hardware e rendere
concreti i giudizi di sicurezza. Questo perché possiamo concretizzare sia gli algoritmi PPT che la funzione
trascurabile (traduzione dal secondo approccio al primo per avvicinarci al mondo reale). La cosa interessante,
è che se seguiamo questo approccio per poi concretizzare gli elementi nel mondo reale c'è un
disaccoppiamento tra la prova di sicurezza (che forniamo con il primo approccio) e le garanzie che lo
schema di sicurezza ci fornisce (che estraiamo quando fissiamo un hardware).
Con questo approccio, siamo in grado di realizzare l'indipendenza del modello, definendo lo schema
sicuro se, ogni avversario
ha, nel forzare lo schema, una probabilità di successo trascurabile!
Algoritmi PPT
Un algoritmo probabilistico è un algoritmo che,oltre a tutte le istruzioni classiche (loop, assegnamenti...), ha
delle istruzioni che simulano il "lancio di una moneta" , ci saranno istruzioni tipo
, che sono
probabilistiche, che non restituiscono sempre lo stesso risultato. Ad esempio il one-time pad potrebbe
avere una funzione
molto simile alla seguente:
1
function Gen:
2
for i= 1 to n:
3
x[i] = flip()
4
return x
Supponiamo che
lunghezza
sia la lunghezza della chiave, alla fine il vettore
sarà proprio la chiave concreta, di
e formata da bit completamente randomici proprio grazie a
L'algoritmo utilizza la funzione
.
, come sorgente di randomicità. Ma ha senso dire che algoritmi concreti
abbiano accesso a una funzione di randomicità? La risposta è si, si assume che sia sempre possibile creare
una sorgente di randomicità.
Dove possiamo trovare dei bit totalmente casuali?
Da alcune grandezze fisiche accessibili dalla macchina. Per ottenere dei bit causali sarebbe misurare
la temperatura esterna, in maniera precisa e prendere il bit meno significativo (per le oscillazioni della
temperatura, che anche se minime vanno bene poiché prendiamo il bit meno significativo che
sicuramente varia);
Oppure da generatori di numeri casuali software, che generano uno stream di bit totalmente
imprevedibili sfruttando informazioni come "movimenti del mouse, tempo tra due battiti della tastiera,
tempo di accesso al disco..."
Quello che assumiamo è che le funzioni come
, ogni volta che le utilizziamo , non producano solo
randomiche, ma che siano i risultati prodotti siano anche indipendenti l'uno dall'altro.
Algoritmo PPT efficiente
Vediamo un grafo per capirci qualcosa:
Il polinomio
limita tutte le possibili scelte (in questo caso
due scelte perché ho una
) del
, infatti ogni ramificazione ha
cioè una toss coin, e l'insieme di tutte le possibili ramificazioni probabilistiche
è limitato da un polinomio nella dimensione temporale (questo a prescindere dai branch che scegliamo di
volta in volta). Vogliamo quindi un bound globale che valga per tutti i possibili branch delle scelte
probabilistiche.
C'è però anche un altro scenario: potremmo definire la seguente funzione foo
1
function foo():
2
while (flip()) do: //esco solo se flip restituisce false
3
4
skip;
return true
Questo algoritmo restituisce sempre
oppure
, ma non lo restituisce finchè
continua a restituire
. La computazione qui è teoricamente illimitata, ma se le probabilità di
50) tipicamente il numero medio di istruzioni eseguito è circa due.
sono bilanciata (50 e
A noi interessa solo il polinomio che limiti il tempo di calcolo indipendentemente dalle scelte
probabilistiche.
Questa definizione di algoritmo è la stessa che si usa nella definizione della classe di complessità
.
Perché algoritmi PPT? (Libro pag. 55)
Perché siamo interessati ad avere avversari che eseguano algoritmi PPT piuttosto che P? Ci sono due grandi
ragioni:
La prima è la randomicità che è essenziale per la crittografia (per avere chiavi randomiche etc...) e
perché le parti oneste e gli avversari devono essere probabilistici;
Un secondo motivo è che gli algoritmi probabilistici, avendo l'abilità di "lanciare una moneta"
potrebbero fornire un potere aggiuntivo! In realtà non è stato ancora dimostrato che avversari modellati
come algoritmi probabilistici polinomiali siano più forti di avversari modellati come semplici algoritmi
polinomiali, ma sicuramente se dimostriamo la sicurezza per avversari probabilistici la dimostriamo
anche per avversari che non lo sono!
Funzioni trascurabili
Una funzione trascurabile è una funzione positiva (restituisce sempre valori positivi) ma che tende a
rapidità molto alta (con rapidità maggiore di ciascun polinomio inverso).
con
La definizione ci dice, che per quanto noi scegliamo una funzione polinomiale , per ogni polinomio , la
funzione trascurabile
sarà sempre più piccola di
trascurabile, infatti anche se prendessimo
. Un polinomio inverso non può essere
ci sarebbe comunque un altro polinomio come
sicuramente più piccolo del primo!
Le tipiche funzioni trascurabili sono quelle che tendono più rapidamente a 0 dei polinomi inversi, che
hanno natura diversa rispetto ai polinomi, come le esponenziali inverse ,radici inverse o altre (
).
A noi piacciono particolarmente le funzioni esponenziali inverse, ci piace avere funzioni che portano
l'avversario ad avere probabilità di successo che vada verso lo 0 in modo estremamente velocemente. Ma
perché ci basta il solo esponenziale inverso e magari non un doppio esponenziali inverso?
Le proprietà di chiusura di
permettono di rendere la definizione di sicurezza robusta:
1. Se moltiplichiamo una funzione trascurabile per un polinomio, otteniamo ancora una funzione
trascurabile! Quindi non solo
[
trascurabile, ma anche
] (funzione trascurabile per definizione) è
è trascurabile, abbiamo anche questa chiusura all'insieme dei
trascurabili che è fondamentale.
2. Siamo chiusi anche per somma e prodotto di funzioni trascurabili con altre funzioni trascurabili.
Con queste proprietà di chiusura possiamo rendere le nostre definizione di sicurezza robuste.
Ma quindi perché ci fermiamo agli esponenziali inversi e, in generale alle funzioni trascurabili?
Esempio per capire importanza delle funzioni trascurabili
Supponiamo di avere un avversario
. Questo
,
con probabilità di successo
dove
è enorme
non è funzione trascurabile ma va comunque a 0 molto velocemente, quindi
perché non va bene?
Se c'è un algoritmo
tempo
che è
vuol dire che il suo tempo di calcolo è polinomiale. Chiamiamo questo
. Ma, allora, potremmo costruire un algoritmo
che esegue in parallelo
un numero di volte
pari a .
ha successo anche quando solo di una di queste esecuzioni ha successo! Se queste esecuzioni
indipendenti fossero indipendenti, quale sarà la probabilità che almeno una di queste abbia successo?
Sarà circa del tipo
che è circa . Quindi abbiamo ottenuto un avversario che agisce sempre in
tempo polinomiale (ma con processori paralleli) che ha probabilità di successo pari a .
Se invece avessi costruito una funzione trascurabile questa situazione non sarebbe stata possibile: le
possibilità di successo di
sarebbe stata circa
che è di certo lontana da !
Risorse limitate e attacchi possibili
Tutti questi rilassamenti li abbiamo fatti per allontanarci dalla sicurezza perfetta che è spesso infattibile nel
mondo reale. Infatti, nel mondo reale, avremo chiavi corte e messaggi lunghi e quindi le chiavi saranno in
numero molto inferiore rispetto ai messaggi.
Se la cardinalità dell'insieme delle chiavi è molto più piccolo della cardinalità dello spazio dei messaggi
, allora posso fare i seguenti attacchi:
1. In un contesto chypertext only, l'avversario vede un crittogramma e lo decritta con tutte le chiavi
possibili, ottenendo un numero di messaggi molto minore rispetto alla cardinalità dell'insieme di
messaggi (ottiene tutti i messaggi che potrebbero corrispondere al crittogramma, che sono molto meno
di tutti i possibili messaggi). In questo modo l'avversario sa già molte cose del messaggio che
corrisponde al crittogramma, in particolare sa che quel messaggio non può essere che uno dei
messaggi che ha ottenuto in questo modo. L'avversario può fare questa cosa perché il numero di
chiavi sono meno dei messaggi, e perché non abbiamo imposto all'avversario un limite sui tempi di
calcolo (che sarà esponenziale).
2. In un contesto known-plaintext, dopo aver osservato la solita coppa
messaggio cifrato) possiamo costruire una chiave
controllare se
, ossia se la chiave
(messaggio in chiaro,
in modo casuale (in un tempo molto basso) e
è proprio quella in grado di ricostruire il messaggio a
partire dal crittogramma. Ovviamente, la probabilità di successo è molto bassa perché la scelta della
chiave è random, ma l'avversario riesce comunque a fare qualcosa. In questo particolare caso one-time
pad non va bene perché basta fare lo
, infatti one-timepad è sicuro solo nel contesto cypher-text
only.
Servono quindi due cose per evitare questi attacchi: limitare il tempo di calcolo per evitare la prima
situazione (e ci riusciamo se modelliamo gli avversari come PPT) e se usiamo le funzioni trascurabili anche
la seconda situazione non è più possibile (perché la probabilità di vincere è talmente bassa che è
trascurabile!). Stiamo prendendo la strada giusta!
Il ruolo delle assunzioni
Registrazione 1:10:00 da risentire con le slide.
Abbiamo già detto che non c'è modo di dare delle prove di sicurezza che valgano in senso assoluto, senza
alcun ipotesi sul contesto. In particolare, quello che faremo sarà dimostrare la sicurezza di uno schema di
codifica dimostrando l'impossibilità di costruire degli avversari che soddisfino certi criteri:
In questo caso, stiamo dicendo che che per ogni
non vale che
possa forzare (
) lo schema di
cifratura. Quindi, l'avversario non riuscirà mai a forzare lo schema in tempo polinomiale (in quanto PPT) che è
equivalente a dire che non esiste un avversario che rompe lo schema poiché i nostri avversari sono tutti del
tipo PPT (porto fuori il not e nego il quantificatore esistenziale della formula logica).
Purtroppo al giorno d'oggi, per implicazioni nel mondo della complessità, non è possibile dare questa
prova di sicurezza per ogni cifrario e per ogni tipo di avversario senza assunzioni (poiché questo
implicherebbe, lato complessità, ad avere una prova che
). La cosa potrebbe funzionare ma solo per
contesti molto semplici.
Il meglio che si riesce a fare è condizionare la sicurezza ad opportune assunzioni che abbiano una forma
simile.
Prove per riduzione
Idea delle prove per riduzione
Invece di assumere che uno schema crittografico sia sicuro, la nostra strategia sarà assumere che un
qualche problema di basso-livello sia difficile da risolvere e poi provare che il nostro schema crittografico è
sicuro data la nostra assunzione!
La prova (per riduzione) che un certo schema sia sicuro finché un problema è difficile, procede,
generalmente, presentando una riduzione esplicita che mostra come convertire qualsiasi avversario
efficiente
che riesca a rompere lo schema con probabilità non trascurabile in un algoritmo efficiente
che
è in grado di risolvere il problema che abbiamo assunto essere difficile.
Il meglio che si riesce a fare è condizionare la sicurezza ad opportune assunzioni che, spesso e volentieri,
hanno la stessa forma dello statement che stiamo dimostrando. In un certo senso lo statement di
sicurezza vale, ogni volta che vale qualcosa di molto simile (Ogni B PPT non riesce a rompere un qualcosa,
allora vale che per ogni A...).
Una ipotesi come quella dell'implicazione (delle slide), potrebbe essere la fattorizzazione dei numeri primi (B):
"Per ogni algoritmo di tipo PPT è impossibile che B fattorizzi i numeri primi". A questo punto il cifrario è sicuro
se non riusciamo a fattorizzare i numeri primi!.
Definizione di prove per riduzione
Notiamo come tra la prima riga e la seconda riga ci sia un rapporto: il conseguente della prima
implicazione diventa l'antecedente della seconda e viceversa (il conseguente della seconda implicazione
è l'antecedente della prima) e poi abbiamo fatto una trasformazione con
(abbiamo scambiato e
negato).
La seconda riga ci dice che ogni volta che esiste un algoritmo
che rompe
algoritmo
, costruiamo un avversario ipotetico per
che rompe
. Quindi per garantire la sicurezza di
, possiamo costruire un
e
lo facciamo diventare un avversario per l'ipotesi.
Supponiamo che
: se vogliamo dimostrare la sicurezza di RSA, ipotizzeremo
che esista un avversario per RSA che riesca a romperlo e sulla base di questo costruiremo un algoritmo B che
diventa un fattorizzatore (in grado di rompere l'ipotesi, ma della prima implicazione!). Quindi in pratica,
stiamo ipotizzando l'ipotesi della seconda implicazione, dimostriamo la tesi e sfruttiamo questa tesi
per rompere l'ipotesi della dimostrazioni di sicurezza!
Questa tipologia di prove è chiamata dimostrazione per contrapposizione.
Sappiamo che se dimostriamo la seconda riga vale allora vale anche la prima (dalla sicurezza di
la sicurezza di
).
Si procede quindi costruendo un avversario per
a partire da un avversario per
come un problema di programmazione: costruiamo un algoritmo
qualcos'altro (rettangolo slide della Riduzione che fa parte di
da un algoritmo
Partiamo da un
essere
che è
come subroutine e poi fa
).
che fa cose e alla fine restituisce altre
che abbia le sembianze di un avversario per
che ha le sembianze di un avversario per
ha successo (ipoteticamente) allora anche
, vedendo il problema
che prende
ma non di
prende stimoli in input dall'esterno, li modifica e poi li passa ad
cose in output (frecce schema). Vogliamo costruire
che se
vale
a partire
. Quello che la prova ci chiede di dimostrare è
ha successo.
, e costruiremo un
e quindi anche la Riduzione dovrà certamente
(sicuramente ci saranno anche scelte probabilistiche).
Una nuova definizione di schema di codifica
Lo schema di codifica, a chiave privata
è sempre una tripla di algoritmi probabilistici polinomiali
:
1.
prima non prendeva in input nulla (prendeva ), qui invece prende in input il parametro di
sicurezza perchè deve sapere quanto lunghe sono le chiavi che deve generare.
In particolare,
prende in input una stringa nella forma
sicurezza e produce una chiave
tale che
, da interpretare come il parametro di
(vogliamo che la lunghezza di k sia maggiore del
parametro di sicurezza per evitare il bruteforce).
Perché passiamo
e non
? Perché vogliamo che
nella rappresentazione binaria di
ma in
e tutti gli altri algoritmi siano polinomiali, non
stesso! Visto che
passare , la cui rappresentazione binaria è lunga
L'algoritmo
è un algoritmo
, ma passiamo
non possiamo
la cui lunghezza è proprio .
che useremo sarà molto simile a quello visto prima nella sezione "Algoritmo PPT
efficiente" dove però la lunghezza del ciclo for è stata parametrizzata col parametro di sicurezza! Il fatto
che
sia un algoritmo randomico viene enfatizzato dal classico assegnamento probabilistico
, cosa che faremo anche con
2. L'algoritmo di criptazione
.
prende in input una chiave
produce un crittogramma. Come sappiamo anche
un messaggio in chiaro
e
può e deve essere randomico
ossia produrre a partire due crittogrammi diversi a partire dalla stessa coppia
.
,
3. L'algoritmo di criptazione
.
Spesso
prende in input una chiave
un crittogramma
e produce un messaggio
deve essere un algoritmo deterministico, per ragioni già dette, quindi
è definita solo per messaggi di lunghezza pari a
dove
.
è il parametro di sicurezza con cui
è stata generata. In tal caso diciamo che lo schema di codifica è definito per messaggi di lunghezza .
[Assumiamo polinomio]
Questo è una cosa strana,
dovrebbe produrre dei risultati per ogni stringa in input, ma vedremo che, in
certi schemi, questa cosa non è sempre possibile!
Per esempio con chiavi di sicurezza lunghe
massimo lunghi
bit, lo schema ci permette di lavorare solo con messaggi al
ad esempio.
Vedremo come estendere la cosa per messaggi di lunghezza variabile.
Adattare l'esperimento
Nozione di sicurezza per la crittografia computazionale
Dobbiamo capire come dare la nozione di sicurezza di uno schema di codifica nel senso della crittografia
computazionale:
Sicurezza Semantica :un primo modo sarebbe sfruttare la definizione di Shannon per la sicurezza
perfetta, tuttavia in quella definizione non c'è una menzione dell'avversario
, parlavamo di probabilità
e questo renderebbe le prove di sicurezza molto complesse, possibili ma complesse;
Prova di indistinguibilità: era la quarta caratterizzazione della sicurezza perfetta di Shannon;
Adattiamo l'esperimento all'indistinguibilità in presenza di un
interecettante
Questo nuovo esperimento viene definito per ogni schema di crittazione a chiave privata
e ogni avversario
per ogni valore di
parametro di sicurezza!
Prima abbiamo considerato un avversario intercettante (eavs=eavsdropping=interecettante) che osservava il
crittogramma generato dal nostro schema (era lui però a generare i messaggi) e che poi doveva indovinare a
quale messaggio corrispondesse quel crittogramma.
Prima di parlare delle differenze vediamo l'algoritmo del gioco:
Ora abbiamo il parametro di sicurezza che è globale, lo possono utilizzare sia l'avversario che lo
schema;
Come al solito nel secondo punto l'avversario genera una coppia di messaggi ma basandosi sul
parametro di sicurezza;
Al terzo passo vogliamo imporre all'avversario di produrre due messaggi di lunghezza identica, questo
perché la lunghezza del messaggio è l'unica cosa che potrebbe differenziare i crittogrammi;
Se i messaggi generati dall'avversario sono della stessa lunghezza procediamo esattamente come prima.
Perché vogliamo due messaggi della stessa lunghezza? Perché altrimenti chiederemmo troppo allo schema
di codifica (lo schema dovrebbe trovare il modo di rendere indistinguibili dei crittogrammi generati da
messaggi ad esempio lunghi 3 byte e 4 gigabyte!).
Se lo schema
è uno schema di codifica a lunghezza fissata con parametro di lunghezza , l'esperimento
visto ora deve essere modificato per garantire che
.
Definizione sicurezza per nuovo schema codifica
(Sicurezza attacchi passivi)
Il nostro schema di sicurezza, definito per contesti a chiave privata, è sicuro contro attacchi passivi (eav = chi
sta a sentire, l'avversario riesce solo a sentire ciò che passa nel canale di comunicazione senza interagire in
maniera attiva).
Con questa definizione stiamo dicendo che la probabilità di successo dell'avversario deve essere uguale ad
più qualcosa che è trascurabile nel parametro di sicurezza.
C'è sempre
nella probabilità di successo, perché un avversario con questa probabilità di successo è facile
da costruire e lo facciamo in tempo costante perché deve effettuare solo un lancio di moneta per indovinare il
bit finale.
Ovviamente ora l'avversario deve avere una probabilità di successo maggiore rispetto a quella dell'avversario
triviale poiché abbiamo rilassato la nozione di sicurezza perfetta, ma questo vantaggio è insignificante...o
meglio trascurabile! (
).
Come costruire uno schema sicuro
Per capire come costruire uno schema sicuro, partiamo da one-time pad (che sappiamo essere sicuro). Il
problema di one-time pad, a cui vogliamo ovviare è la lunghezza della chiave, vogliamo fare in modo di
poter utilizzare una chiave
La chiave
che sia corta e allungarla per poterla usare come nel one-time pad.
deve essere più corta della lunghezza del messaggio e per usarla come in one-time pad abbiamo
bisogno di una funzione
che allunghi la chiave, in questo modo passiamo da
lungo un
passiamo ad una chiave (generata da G(k)) che è espansa ed è lunga come il messaggio
Ovviamente
piccolo,
.
non è solo una funzione che aggiunge semplicemente dei bit alla chiave , ossia non è un
algoritmo che estenda semplicemente la chiave, potrebbe anche fare qualche trasformazione sui bit che
aggiunge.
Verso la pseudocasualità
Cosa è ragionevole chiedere all'algoritmo
L'algoritmo
?
deve essere deterministico perché viene usato sia da
che da
, se fosse non
deterministico non avrebbe senso perché lo schema non potrebbe essere corretto.
L'algoritmo
deve essere efficientemente calcolabile, altrimenti l'intero schema che stiamo
costruendo diventerebbe problematico dal punto di vista computazionale.
Vogliamo che
possa espandere la stringa in input facendola diventare sempre più lunga. Ad esempio
possiamo chiedere un polinomio perché in tempo polinomiale lo posso produrre, di certo non posso
produrre un output esponenziale perché
impiegherebbe un tempo esponenziale e ricordiamo che
tutti gli algoritmi devono essere polinomiali.
Ci interessa anche qualcos'altro: vogliamo che l'output prodotto da
abbia tutte le proprietà che ci
aspettiamo da una chiave, ossia che sia una stringa più o meno casuale.
Controesempio del punto precedente: se
, tutte le proprietà precedenti sono soddisfatte, ma
ovviamente questa cosa non garantirebbe la sicurezza! Infatti posso costruire
su input
produce in output la coppia di messaggi
;
come segue:
su input
(crittogramma) prodotto con la chiave
uguali agli ultimi
bit di . Se è così allora
espansa con
verifica se i primi
bit di
è sicuro che il crittogramma è il secondo (
1, altrimenti 0. Questo è vero perché se
allora
siano
) e restituisce
viene utilizzato per codificare sia la prima
parte che la seconda parte del messaggio, ma la chiave è la stessa!
L'avversario vincerebbe sempre, avrebbe probabilità di successo pari a 1!
Ovviamente l'avversario oltre a conoscere
e
conosce anche
(però
dovrebbe essere non
invertibile in tempo polinomiale).
In conclusione,
deve tradurre stringhe casuali in input, in stringhe più lunghe che siano sempre
casuali... vogliamo che
sia un generatore pseudocasuale!
Generatore pseudocasuale
Vogliamo che
generi una stringa che pur non essendo casuale, sia indistinguibile da una casuale agli occhi
di un avversario efficiente!
La definizione sostanzialmente dice che dato fattore di espansione polinomiale, voglio che
data una
stringa
!
in input (che sarà la chiave) produca in output una stringa binaria di lunghezza
Commento terzo punto definizione
Per ogni algoritmo
che è
e che vediamo come un distinguitore, ossia un'entità che ha il compito
di distinguere stringhe prodotte da
e non prodotte da
, esiste una funzione trascurabile tale che vale:
Stiamo dicendo che non può essere possibile la costruzione di un distinguitore polinomiale
grado di produrre in output
(
che sia in
ha indovinato) con probabilità significativamente diverse, nel caso in cui
riceva in input una stringa casuale
e qualora prenda in input una stringa pseudocasuale prodotta con
(deve avere più o meno la stessa possibilità di indovinare se la stringa passata in input sia stata prodotta da
sia nel caso in cui la stringa sia veramente casuale o pseudocasuale).
Questa discrepanza tra le probabilità è non nulla ma deve essere bassa nel senso delle funzioni
trascurabili (la differenza tra le due probabilità deve essere trascurabile).
Nel contro esempio di prima siamo riusciti a costruire
Se
, allora
definito come segue:
=
Questo
.
, dove
messaggio.
ha probabilità molto alta di vincere contro
darà sempre in output , quando invece
, perché quando
riceve in input l'output di
prende una stringa casuale restituisce
(C'è anche la
probabilità che la stringa casuale abbia la prima parte uguale alla seconda, ma questa è veramente molto
bassa, circa
). Quindi la differenza tra le due probabilità
molto alta ed è molto lontana da essere una
funzione trascurabile.
I generatori pseudocasuali
Ci serve in crittografia per ovviare ai problemi di one-time pad, l'idea era quella di estendere la chiave casuale
di partenza preservandone la casualità. Dobbiamo però capire se la definizione è applicabile alle situazioni
di interesse.
Sui generatori pseudocasuali
La pseudocasualità non ha niente a che fare con la casualità, infatti se ho
stringhe (lunghe
ad esempio
) prodotto in output da
, allora
, mentre le possibili stringhe lunghe
, allora il generatore restituisce in output
mentre le possibili stringhe lunghe
sono esattamente
, vi sono soltanto
sono
. Nel senso che
stringhe lunghe
.
Osserviamo, quindi, che ci sono tantissime stringhe che sono nel codominio di G ma che non si trovano
nell'output di
:
Quindi la probabilità che una generica stringa nel codominio compaia nell'output di
è bassa.
potrebbe essere, quindi, una funzione non iniettiva (anche così stupida da produrre sempre la stessa stringa
in output). Queste considerazioni combinatorie, sono importanti ai fini del distinguitore!
Un distinguitore
può vincere contro
stringhe lunghe , date in input a
meno è tra gli output di
di
con probabilità molto alta, semplicemente provando tutte le possibili
e controllare se la stringa, che
deve giudicare essere pseudocasuale o
! La probabilità che sia tra queste è molto piccola (perché , come detto, l'output
non comprende tutte le possibili stringhe generabili della lunghezza su cui è definito) e quindi
l'avversario riesce molto spesso a capire se la stringa è prodotta da
oppure è totalmente casuale!
Però, per fare questo,
ha bisogno di molto tempo, lo fa in tempo esponenziale deve provare tutte le
stringhe lunghe , cioè
, eseguire su ciascuna di esse
per poi controllare se tra le stringhe prodotte in
output c'è quella che sta cercando! La probabilità di vittoria di
è
, che è molto alta.
Non c'è nessuna prova di esistenza di generatori pseudocasuali senza assunzioni, ma ci ritroviamo con
questo fatto perché la pseudocasualità è stata studiata prima dell'avvento della crittografia.
Morale: bisogna garantire che
sia molto grande per evitare i brute force di
.
Il primo schema di codifica sicuro
Che forma ha lo schema di codifica sicuro indotto da un generatore pseudocasuale?
Dato un generatore pseudocasuale
=
con fattore di espansione , possiamo definire uno schema di codifica
, come segue:
L'algoritmo
su input è
,dove
), effettua una istruzione
è il parametro di sicurezza (l'algoritmo lavora con chiavi lunghe
volte (producendo ogni volta o 1 o 0) e la stringa in output sarà la
concatenazione di tutti i risultati dei
(prodotta con costo lineare in ). Ogni stringa di lunghezza
ha probabilità di essere prodotta pari a
e
(guarda sezione precedente);
sono uguali a quello di one-time pad, con la differenza che, prima della codifica e della
decodifica, la chiave viene espansa passandola al generatore (il mittente espande la chiave prima di
inviare, e il ricevente espande la chiave per poter effettuare la decriptazione).
deterministico, perché altrimenti ricevente e mittente eseguirebbero
è ovviamente
sulla stessa chiave ottenendo
un risultato diverso.
Ricordiamo che i messaggi del cifrario hanno lunghezza fissata (ricordiamo che in one-time pad i messaggi
hanno tutti la stessa lunghezza) e quindi il cifrario
è definito per messaggi di lunghezza fissa pari a .
La correttezza è facile da dimostrare:
.
A differenza del one-time pad, dove la sicurezza è incondizionata, in questo contesto dimostreremo la
sicurezza dello schema se solo se esiste un generatore (condizione)!
Gestire messaggi di lunghezza variabile
Come detto lo schema
espansione di
appena visto permette di gestire messaggi di lunghezza fissa, pari al fattore di
.
Per quanto il fattore di espansione sia grande (è un polinomio) la cosa non va bene in quanto fisso,
vogliamo poter gestire avversari che riescano a creare messaggi di lunghezza che arbitraria.
Come fare? Dobbiamo generalizzare il concetto di generatore pseudocasuale!
Generatore pseudocasuale a lunghezza variabile
L'idea è che con un seme costante (chiave), non vogliamo ottenere una certa stringa di una certa lunghezza,
ma vogliamo poter usare il nostro seme per ottenere stringhe via via più lunghe.
Vogliamo, quindi, che l'algoritmo deterministico polytime
prende anche una stringa nella forma
, non prenda solo un il seme (di lunghezza ) ma
, dove non è il fattore di sicurezza ma è la lunghezza dell'output
desiderato (Es. 4 giga).
ora è una funzione a due parametri ma sempre calcolata in tempo polytime. La parte pseudocasuale è
ereditata dalla nozione di generatore pseudocasuale a lunghezza fissa.
Commento primo punto definizione
Cosa succede se aumento il secondo parametro cosa succede? Il generatore pseudocasuale, essendo
deterministico estenderà semplicemente la stringa trovata prima a parità di seme!
Commento secondo punto definizione
Possiamo sempre definire un generatore pseudocasuale a lunghezza fissa, definendo un certo fattore di
espansione
detto
.
Tutto questo serve per gestire una variazione nel cifrario, dove ora posso gestire messaggi di lunghezza
arbitraria passando la lunghezza di
al generatore pseudocasuale a due parametri (
può essere
qualunque cosa).
Cifrario con generatore a lunghezza variabile
Dato un generatore pseudocasuale a lunghezza variabile
è facile generalizzare la costruzione
Proprietà di sicurezza
Le proprietà di sicurezza di questa nuova costruzione sarà: ogni qual volta
è un generatore
pseudocasuale a lunghezza variabile il cifrario è sicuro, quindi uguale a prima!
ad esso:
Lemma
Stiamo semplicemente dicendo che da un generatore pseudocasuale a lunghezza fissa possiamo sempre
costruirne uno a lunghezza variabile!
Parentesi sui cifrari di flusso
I cifrari di flusso,sono erroneamente chiamati cifrari, in realtà generatori pseudocasuali! Infatti, questi
espandono la casualità dell'input in una pseudocasualità nell'output e lo fanno a lunghezza variabile.
I cifrari di flusso non sono di certo schemi crittografici ed è possibile dimostrare che le proprietà di
sicurezza da esso catturate sono quelle che si catturano con un generatore pseudocasuale a lunghezza
variabile crea un cifrario sicuro.
Le codifiche multiple
Finora abbiamo utilizzato la chiave per una singola codifica...ma vorremmo usarla per effettuare codifiche
multiple! Anche il nostro concetto di sicurezza si basava su questo fatto (si pensi all'esperimento
dove l'avversario, un'unica volta produceva due messaggi e la chiave veniva utilizzata per produrre un unico
crittogramma).
Dobbiamo modificare l'esperimento affinché questo tenga conto della possibilità per l'avversario di
osservare codifiche multiple (fatte con la stessa chiave) che chiameremo
.
In realtà la struttura del nuovo esperimento è identica alla precedente, ma:
All'avversario è permesso produrre due vettori di messaggi
e
L'avversario non decide quindi un solo messaggio ma due,tre, quattro... e ne fornisce due versioni!
Ovviamente il numero di messaggi nel vettore ha la stessa lunghezza così come la lunghezza dei
messaggi stessa.
Il cosiddetto challenge chypertext diventerà un vettore
codifiche di
o
Come al solito, richiederemo, che per ogni
ottenendo la definizione di sicurezza rispetto a
Non sicurezza dello schema
che sarà un vettore delle
.
esista trascurabile, tale per cui:
.
.
Tuttavia, lo schema
rispetto a
non è sicuro rispetto a
, neanche quando
è pseudocasuale (ma è sicuro
)... per lo stesso motivo per cui one-time pad, perché la chiave la posso usare una volta
sola!
Il one-time pad non è sicuro se uso la chiave per codifiche multiple. Il problema è che, nell'esperimento, la
chiave viene generata una volta sola, ("la sessione di scambio di messaggi usa un'unica chiave").
Contro esempio
Cerchiamo di capire quale attacco (su
) è possibile sfruttando il fatto che utilizziamo la stessa
chiave per codifiche multiple.
Supponiamo che i vettori dei messaggi siano formati anche solo da due messaggi:
.
Come fa l'avversario a vincere? Se ho la stessa chiave (di lunghezza ), i crittogrammi generato dai
messaggi in
sono uguali, mentre nel caso di
sono diversi (l'avversario ricorda i messaggi che crea).
Con questo accorgimento vince con probabilità 1! Questo funziona perché stiamo ragionando su
, dove
non è probabilistico ma è deterministico e basato su un generatore pseudocasuale.
Teorema Enc deterministico
In realtà questo attacco vale per ogni schema di codifica in cui
modo di rendere
sia deterministico, bisogna fare in
probabilistico. Infatti si può dimostrare che:
Ma allora i generatori pseudocasuali sono inutili nel caso di codifiche multiple? No, bisogna solo cambiare la
nozione di schema di codifica.
Cosa dobbiamo introdurre nella nozione di schema di codifica? L'idea è che lo schema di codifica, e in
particolare
deve avere uno stato interno, deve essere stateful, deve avere uno stato interno!.
Schema di codifica con stato Sincrono
A partire da
produco uno stream pseudocasuale. Poi uso una prima porzione per codificare il primo
messaggio, poi la seconda etc... In questo modo l'attacco di prima non è possibile perché ogni messaggio
viene codificato con una parte di chiave diversa che uso per gli altri messaggi.
In questo modo stiamo utilizzando uno schema di codifica stateful, che si deve ricordare dove il
generatore pseudocasuale era arrivato in precedenza, praticamente siamo in una sessione sincrona (che
implica la consistenza).
E' un modo operativo per usare i generatori pseudocasuali nel contesto delle codifiche multiple e in
particolare utilizzato nel campo delle reti wireless.
Schema di codifica con stato Asincrono
Per evitare che il protocollo sia stateful, possiamo fare in modo che il generatore pseudocasuale non dipenda
solo dalla chiave ma anche dal vettore di inizializzazione. Ogni qualvolta invoco
non lo invoco solo sulla
chiave, ma anche su un vettore di inizializzazione, che espanderà la chiave fino alla lunghezza di del
messaggio
E' cruciale che
di cui dobbiamo generare il crittogramma.
rimanga pseudocasuale anche quando IV è noto, questo perché l'unica speranza, per
mantenere un protocollo non stateful che sia corretto, consiste nell'inviare IV assieme al crittogramma
ma in chiaro! In questo modo il ricevente sarà in grado di decrittare il crittogramma (altrimenti non
riuscirebbe).
Stiamo implicitamente dicendo che
non è solo un generatore pseudocasuale ma che deve continuare ad
esserlo anche con questo IV noto (cosa non ovvia).
DIMOSTRAZIONE TEOREMA CIFRARIO SICURO ATTACCHI
PASSIVI
La prova della sicurezza di
data la casualità di
ipotizzando che per ogni avversario per
che abbia successo contro
la facciamo per contrapposizione(o riduzione) ,
che abbia successo contro quest'ultimo, esista un distinguitore
(dalla negazione della tesi risaliamo alla negazione dell'ipotesi).
Costruirò lo schema pezzo per pezzo in modo tale che chiunque legga il seguente documento possa
comprendere.
STEP 1
Definiamo quindi un avversario
che vinca contro
tale per cui,
, dove
è una funzione non-trascurabile perché altrimenti l'avversario non vincerebbe. Con
indichiamo la probabilità che l'avversario vinca! Ma vogliamo sfruttare la cosa per dimostrare che il
distinguitore
possa battere
.
Il segreto sta nel fatto che vogliamo che
(stiamo ingannando
, abbia l'impressione di essere nel gioco ma in realtà sta in
). Vedremo che il famoso crittogramma su cui
prodotto proprio da un sotto-modulo di
tira a caso (ripensa all'esperimento) è
(tra poco lo vediamo)!
Quindi partiamo definendo lo scheletro della dimostrazione:
Dobbiamo ora capire con cosa si interfaccino questi oggetti! Sappiamo che il distinguitore si aspetta una
stringa
di bit di lunghezza
[
], che come sappiamo o è pseudocasuale o casuale, il compito di
è proprio quello di riconoscere di quale tipologia sia la stringa
essere
STEP 2
.
e restituisce in output un bit che può
L'avversario ha sicuramente un rapporto più complesso, infatti prende in input il fattore di sicurezza
produce due messaggi di stessa lunghezza
,
, poi attende di ricevere il crittogramma che è stato
prodotto a partire da uno dei due messaggi e produce un bit in output che indica, secondo l'avversario, a
quale messaggio appartiene il crittogramma (classica storia).
Come detto vogliamo che
non si accorga che in realtà sta interagendo con
, deve avere la stessa visione
dell'esterno che avrebbe nel gioco perché altrimenti non potremmo usare l'ipotesi su
vince contro
allora
vince contro
(ogni volta che
)!
Dobbiamo quindi garantire che lo schema sia quello che abbiamo appena descritto!
STEP 3
La prima cosa da fare è trovare
è
ma possiamo ricavarlo dato che abbiamo la lunghezza del messaggio
e tipicamente è invertibile, ossia dal
STEP 4
riesco a ricavare
utilizzando la sua inversa.
che
Quindi ora
prende in input
e riesce a produrre due messaggi
Ricordiamoci che siamo nel contesto dell'esperimento
che sono entrambi lunghi
nel senso di
modo che il distinguitore, critti uno dei due messaggi, con la stringa
.
, quindi dobbiamo fare in
, (che interpreto come chiave) ed
effettuandone lo xor! Tuttavia, quello che va specificato, è che per decidere quale messaggio crittare (o
o
), si utilizza la funzione
che restituirà in output un bit che verrà utilizzato per indicare il messaggio
da crittare (da questo capiamo che
Il passaggio cruciale è che interpreto
è un algoritmo
)!
come la chiave!
STEP 5
A questo punto l'avversario, basandosi sul crittogramma appena creato, genera un bit
confronto il bit
Quindi
e il bit
e il risultato sarà la negazione dello xor tra i due (
è sostanzialmente un algoritmo che usa
Dimostriamo che
e a questo punto
).
come sub-routine.
abbia successo
Abbiamo costruito tutta questa roba ma il punto cruciale è dimostrare che
possa vincere:
,
Quindi vogliamo che la differenza, in valore assoluto, tra la probabilità che
casuale è effettivamente tale (
indovini se una stringa
) e la probabilità che una stringa pseudocasuale sia
effettivamente tale (
), deve essere uguale a una funzione
che non è trascurabile (per
dimostrare il teorema)!
Dobbiamo quindi distinguere due casi: uno nel quale passiamo una stringa casuale a
e l'altro in cui gli
passiamo una stringa pseudocasuale.
Caso 1: Passiamo a
una stringa casuale
In questo caso, la probabilità che
vinca, sarà esattamente uguale , per come abbiamo costruito il
distinguitore, alla probabilità che l'esperimento vinca! Ma la cosa cruciale è che l'avversario sta interagendo,
non con lo schema di codifica che uso del generatore pseudocasuale (dato che stiamo usando una stringa
che è davvero casuale) ma con il one-time pad!
Quindi il distinguitore restituisce 1 se e solo se l'esperimento restituisce 1, il distinguitore è basato
sull'esperimento! In realtà non abbiamo mai provato come si comporti il one-time pad se utilizzato
nell'esperimento, però è uno schema perfettamente sicuro, quindi la probabilità di vittoria
dell'esperimento (e del distinguitore) sarà
Caso 2: Passiamo a
!
una stringa pseudocasuale
Per lo stesso ragionamento fatto prima
ora però ragioniamo su una stringa pseudocasuale in input e sullo schema di codifica che non è one-time
pad perché stiamo utilizzando una stringa pseudocasuale, quindi l'avversario
sta interagendo proprio con
! Ma per ipotesi sappiamo che questo oggetto:
dove
è una funzione non trascurabile.
Conclusione dimostrazione
Ma allora:
e quindi? Abbiamo finito, perché se
non è trascurabile allora nemmeno le differenze tra le probabilità
non è trascurabile, in questo modo il vantaggio dell'attaccante
ma allora lo schema
, quando
diventa l'insuccesso del distinguitore
è generatore pseudocasuale è sicuro!
Sicurezza contro attacchi CPA
...
Parleremo ora di sicurezza contro attacchi attivi (chosen plaintext attack), sempre nell'ambito della sicurezza
a chiave segreta, dove l'avversario ha accesso al canale sicuro non solo in lettura ma anche in un scrittura! Ora
l'avversario può decidere i messaggi che lo schema di codifica invierà sul canale.
Come detto, questo tipo di attacchi potrebbe sembrare poco realistico, però non è propriamente vero, ci
sono dei casi in cui questi potrebbero verificarsi! In ogni caso se dimostriamo la sicurezza del nostro schema
di codifica anche contro attacchi attivi tanto meglio!
Vediamo se questi attacchi sono teoricamente possibili, ma prima formalizziamo questo tipo di attacchi.
: modifichiamo l'esperimento
Dobbiamo modificare l'esperimento
, finora l'avversario poteva interagire in modo limitato con
l'esperimento: dopo aver costruito i messaggi, osservava il crittogramma
e doveva capire quale dei due
messaggi fosse stato cifrato dall'esperimento.
Per quanto riguarda la sicurezza contro attacchi
l'avversario deve avere certe capacità sullo schema di
codifica...ma come le concretizziamo? L'avversario è in grado di utilizzare un oracolo: ogni qual volta
l'avversario produce
ed
e anche quando analizza il crittogramma
ha accesso a un oracolo per
.
Cosa vuol dire avere accesso a un oracolo?
Vuol dire avere la possibilità di chiamare la funzione
dell'algoritmo, senza conoscere la struttura di
(come se fosse una
in qualunque momento dell'esecuzione
(ma sa quello che fa) e senza conoscere
). Il costo di calcolo di
soprattutto
lo consideriamo costante, perché dal punto di vista
dell'avversario non c'è nessun lavoro da fare (fa solo una chiamata ad un metodo).
L'avversario può quindi invocare l'oracolo per
corrispondente crittogramma
su un qualunque messaggio
ottenendo il
.
Per il resto l'esperimento è uguale a
ma ora lo chiamiamo
.
Problema dell'esperimento
Se lasciamo che
sia deterministico, l'avversario vince sempre! Questo perché può accedere all'oracolo
sia quando genera i messaggi
sia quando sta valutando il
. Basta infatti che, dopo
aver generato i due messaggi chiami l'oracolo sui due messaggi e si salvi le informazioni. Il crittogramma che
gli arriva è sicuramente uno dei due crittogrammi generati grazie all'oracolo e quindi vince sempre! Però
questa cosa vale se
è deterministico, ossia ogni volta che lo chiamiamo su un certo messaggio, su una
certa chiave ci restituisce sempre la stessa stringa in output!
Fondamentale: l'oracolo è per
, il che vuol dire che non utilizza l'algoritmo
, ma l'algoritmo
che utilizza una specifica chiave che verrò utilizzata anche dal gioco per criptare uno dei due messaggi! Se la
chiave non fosse la stessa, in questa situazione l'avversario non vincerebbe sempre!
L'avversario vince sempre se
è deterministico, ma allora bisogna cambiare qualcosa...
essere probabilistico!
Definizione di sicurezza rispetto ad attacchi CPA
deve
Condizioni necessarie alla sicurezza CPA
Lemma 1: rapporto tra esperimenti
Lo dimostreremo sempre per contrapposizione (se è sicuro rispetto per
, dove ho l'oracolo,
sicuramente lo schema è sicuro nel caso in cui l'avversario non abbia a disposizione l'oracolo!).
Lemma 2:
deve essere probabilistico
Come lo dimostriamo?
Neghiamo la l'ipotesi, se
non fosse probabilistico è deterministico, ma se lo fosse, allora basterebbe che
l'avversario produca due messaggi diversi qualunque, produca i crittogrammi e li confronti con quello
prodotto dall'algoritmo (la cosa che abbiamo detto prima).
Teorema: Generalizzare la nozione di sicurezza rispetto ad attacchi
CPA
Se ricordiamo, il one-time pad non rimaneva sicuro verso codifiche multiple proprio perché la chiave poteva
essere utilizzata una sola volta.
Possiamo però generalizzare la nozione di schema sicuro contro attacchi attivi (CPA) per codifiche multiple...
ma non dobbiamo fare niente! Qualunque schema di codifica CPA-sicuro rimane tale anche in presenza di
codifiche multiple.
Non dobbiamo fare niente perché il grosso del lavoro lo abbiamo fatto quando siamo riusciti a gestire
codifiche singole sicure per CPA.
Tutte queste appena viste sono condizioni necessaria...dove sono le condizioni sufficienti? Dobbiamo
costruire dei cifrari CPA-sicuri!
Costruire un cifrario CPA-sicuro
Cerchiamo di capire se esistono degli schemi di codifica CPA-sicuri. Per costruire un cifrario CPA-sicuro ci
viene in aiuto la pseudocasualità, ma non nel modo in cui l'abbiamo sfruttata per i generatori!
I generatori pseudocasuali non sono il modo giusto per ottenere cifrari CPA-sicuri per vari motivi, ma
principalmente perché sono deterministici (quindi dovremo comunque iniettare delle scelte probabilistiche,
perché vogliamo che
sia probabilistico) e di conseguenza
non ha alcuna speranza di essere CPA-
sicuro (non ha una delle condizioni necessarie).
Potremmo prendere però un'altra strada: potremmo aggiungere alla chiave un pezzo di stringa casuale e
passarlo al generatore...ma questo non funzionerebbe! Non funzionerebbe perché, per permettere al
ricevente di decrittare il messaggio la stringa casuale la devo passare in chiaro (come facevamo con l'IV)! Ma
se passo in chiaro la stringa assieme al crittogramma sicuramente una parte del messaggio rimarrà scoperta!
Bisogna passare alla nozione di funzione pseudocasuale. In cosa si differenzia rispetto ad un generatore
pseudocasuale?
Il generatore pseudocasuale espande stringhe casuali in stringhe pseudocasuali, la funzione
pseudocasuale è la generalizzazione del generatore pseudocasuale all'ordine superiore di funzione.
Quello che è pseudocasuale non è la stringa ma la funzione!
Funzioni pseudocasuali: Introduzione
Funzioni binarie parziali
L'idea è che non lavoriamo più con funzioni da stringhe a stringhe ma con funzioni da coppie di stringhe a
stringhe (ho due input non più uno). L'idea è che il primo argomento sia la chiave e il secondo
argomento no . Queste funzioni devono essere parziali, ossia una funzione che non è definita su tutti i valori
del dominio ma solo su alcuni valori del suo dominio.
"Lavoreremo quindi con funzioni binarie parziali, ossia funzioni da
."
Funzioni binarie parziali che preservano la lunghezza
Considereremo solo funzioni parziali binarie che preservino la lunghezza! Vuol dire che questa è una
funzione estremamente regolare ed è definita solo se
, ossia i due input devono avere la stessa
la lunghezza, ma anche l'output, ossia la stringa binaria restituita avrà la stessa lunghezza.
"Una funzione parziale binaria
in tal caso
Ovviamente il
preserva la lunghezza se e solo se
è definita se e solo se
"
sta per chiave, ed è possibile rendere la Funzione pseudocasuale unaria e non più binaria
fissando una certa chiave
:
"Data una funzione parziale binaria
.
che preserva la lunghezza, indichiamo con
".
Funzione binaria parziale efficientemente calcolabile
la funzione
e
Quello che vogliamo è che, fissata la chiave
scelta in maniera casuale, la funzione
sia indistinguibile da
un oggetto veramente casuale, ma non più da una stringa casuale ma da una funzione casuale rispetto
all'avversario efficiente (per avversari inefficienti possiamo sempre fare qualcosa, avevamo anche
dimostrato che i generatori pseudocasuali non esistono se l'avversario è inefficiente, perché se l'avversario
ha tutto il tempo del mondo potrà sempre fare qualcosa).
"Una funzione parziale binaria è efficientemente calcolabile se e solo se esiste un algoritmo polytime che la
calcola!"
N.B. Stiamo parlando ancora di funzioni, siamo ancora nel mondo degli algoritmi efficienti e deterministici
(dobbiamo ancora definire la parte di randomness).
Se ben ricordiamo, per quanto riguarda i generatori pseudocasuali, volevamo che una stringa prodotta dal
generatore pseudocasuale fosse indistinguibile da una stringa prodotta in maniera totalmente casuale, qua
vogliamo la stessa cosa ma per le funzioni!
Concetto di funzione casuale
Ricordiamo che per definire la casualità di tirare fuori un certo elemento da un certo insieme utilizziamo la
distribuzione uniforme (come facevamo quando tiravamo fuori una chiave dallo spazio
).
Ci chiediamo quanto sia grande l'insieme delle funzioni binarie parziali che agiscono su stringhe binarie
lunghe . Consideriamo quindi lo spazio delle funzioni binarie da
:
Su questo insieme sembrerebbe che non possiamo generare una distribuzione uniforme (cioè
tiriamo fuori a caso una delle possibili funzioni binarie parziali), tuttavia questo spazio è finito ed ha
cardinalità
, perché ogni tale funzione si può vedere come specificata tramite una tabella di
valori binari con
righe e
colonne.
1
2
...
n
00....0
0
1
...
0
00....1
1
0
...
0
....
...
...
...
...
11....1
1
1
...
1
Dove ogni cella ci dice il valore restituito dalla funzione in corrispondenza di una certa stringa. La cella
restituisce il risultato della funzione sulla stringa
.
Il rapporto tra funzione binaria e questo tipo di tabelle è ovviamente biunivoco (nel senso che anche la
tabella da sola descrive il comportamento di una funzione).
Perché lo spazio di queste funzioni è proprio
? Perché tutte le tabelle hanno
ogni cella possiede un bit! Ma quante tabelle ci sono?
ossia
righe ed
colonne e
.
Questo numero è molto più grande della stringhe binarie che possiamo ottenere da un generatore
pseudocasuale, ossia
.
Ora abbiamo scoperto che questo insieme, nonostante sia enorme è comunque finito ha senso pensare
alla distribuzione uniforme su tale spazio, la quale assegna probabilità
Date queste premesse possiamo capire cosa sia una funzione pseudocasuale.
a ciascuna di tale funzione!
Funzioni pseudocasuali: La definizione
La definizione ricorda molto quello del generatore pseudocasuale, ma ci sono delle differenze sostanziali.
C'è il solito gioco del distinguitore, ma in questo caso, al distinguitore stiamo dando l'accesso a
oracolo [
]! Il distinguitore può evocare questo oracolo sulla funzione
parziale binaria con chiave fissata) ed inoltre gli passiamo
come
(ossia la nostra funzione
(parametro di sicurezza), perché vogliamo
che il distinguitore si renda conto di quanto lunghe possono essere le stringhe che può passare all'oracolo
cioè . Da notare che ci deve essere un unico distinguitore che lavori per tutti gli .
Ma l'idea di base è che l'idea non deve comportarsi in modo diverso se accede ad una funzione
pseudocasuale o ad una funzione veramente casuale!
La
piccola, infatti, è proprio una funzione casuale.
Bisogna dire cosa sia
binarie lunghe
e non
Invece
nella definizione di
... ma è una scelta in maniera casuale tra tutte le stringhe
(da notare che stiamo definendo solo
in maniera totalmente casuale su uno spazio grande
).
è scelta tra tutte le funzioni
molto più grande rispetto a
(
vs
in modo casuale, quindi viene presa da uno spazio
)!
Nonostante gli insiemi da cui vengono scelti
ed
accorgersi di questa cosa (in quel caso la funzione
siano estremamente diversi, il distinguitore non deve
sarà veramente pseudocasuale)!
Esistenza di funzioni pseudocasuali
Come per i generatori pseudocasuali, l'esistenza di funzioni pseudocasuali non è nota in senso assoluto.
Tuttavia, è possibile dimostrare che si può costruire un generatore pseudocasuale da una funzione
pseudocasuale e viceversa! Da funzione a generatore è intuibile, perché la funzione è molto più potente del
generatore.
Ci si pone ora un nuovo quesito: cosa sono queste funzioni pseudocasuali? Dove vengono usate? I cifrari a
blocco come
o
sono costruiti in modo da soddisfare l'assiomatica delle funzioni pseudocasuali ed
altri assiomi.
Purtroppo non possiamo dimostrare le proprietà di pseudocasualità di
intrinseche. Il problema è che
e
o
ma sono date come
hanno la lunghezza del messaggio e della chiave fissata, invece
nella funzione pseudocasuale parliamo di lunghezza della chiave e del messaggio arbitrarie, non l'abbiamo
mai fissata!
Cioè non abbiamo mai detto che
va bene se
Per questo motivo la sicurezza di
o
bit.
usiamo l'approccio concreto infatti, non l'approccio asintotico
che utilizziamo noi.
Schema di codifica CPA-sicuro (indotto da una funzione
pseudocasuale)
Continuiamo ad essere ispirati da one-time pad ma in modo completamente diverso!
La svolta nella definizione è che alla funzione pseudocasuale non passo il messaggio ma una stringa
casuale
invoco
lunga
bit (il messaggio ha la stessa lunghezza della chiave ricorda) che genero ogni volta che
, ogni volta diversa (inseriamo la randomess) e la passo in chiaro nel crittogramma!
E' necessario metterla in chiaro perchè poi
deve conoscere questa stringa casuale perchè altrimenti non
potrebbe richiamare la funzione pseudocasuale con chiave fissata
I crittogrammi sono quindi formate da coppie
bit fatto tra il messaggio e l'output di
per poter rigenerare il messaggio!
. Dove il crittogramma è dato dallo xor bit a
dove ovviamente
.
La chiave di volta è la generazione della randomness ogni volta che chiamiamo
abbiamo definito un
in questo modo
che è probabilistico!
Delucidazioni su definizione di
.
Ulteriori osservazioni e teorema da dimostrare
Se l'avversario
avesse accesso a
allora vincerebbe sempre, ma non ha accesso completo a
ma solo a
.
La cosa che non ci piaceva della sicurezza perfetta era che la lunghezza della chiave era uguale a quella del
messaggio, ma qui è uguale! Però abbiamo ottenuto una garanzia migliore rispetto a prima; il cifrario è
comunque corretto perché passiamo
in chiaro. Quindi lo schema di codifica
è almeno corretto.
DIMOSTRAZIONE TEOREMA
La dimostrazione strutturalmente non è così diverso dal teorema che abbiamo dimostrato per
, solo che
qui parliamo di funzioni pseudocasuali invece che di generatori.
Prima sfruttavamo il one-time pad, mentre ora sfruttiamo un nuovo cifrario che si basa su una funzione
casuale piuttosto che pseudocasuale!
Supponiamo di lavorare con
che è uno "schema di codifica" simile a
ma in cui
funzione casuale anziché una chiave! E questa funzione sarà quella che utilizzerà
sceglie una
ogni qualvolta verrà
utilizzato.
Per questo abbiamo detto che
è uno "schema di codifica" (per modo di dire) perché
particolare non è polytime, perché non è possibile costruire una funzione
fa troppa roba,in
in tempo
polinomiale in ,.Infatti per costruire una funzione casuale di questo tipo vorrebbe dire riempire le celle della
tabella vista prima che sono
e già questo richiedete tempo esponenziale!
Dimostrazione: Punto 1
La prima cosa da dimostrare è:
Cioè la probabilità di successo dell'avversario non è tanto diversa se utilizziamo, nell'esperimento CPA, un
cifrario che si basa su una funzione pseudocasuale e uno che fa uso di una funzione casuale. La differenza tra
queste due cose è trascurabile.
Ma questo differenza è la definizione funzione pseudocasuale!
Ma allora la prova sarà per riduzione! Non faremo altro che costruire a partire dall'avversario
distinguitore
per la funzione
un
, che abbia probabilità di successo identica alla differenza che dobbiamo
dimostrare. In questo modo, la dimostrazione discenderà dall'ipotesi di funzione pseudocasuale (se le
"differenze" sono uguali, allora sono entrambe inferiore di qualcosa che è trascurabile!).
Costruiamo D
è un avversario nel senso dell'esperimento
sicuramente produrrà i messaggi
, riceverà
, quindi si aspetta in input il parametro di sicurezza,
e produrrà il classico bit finale. Ma in ogni momento può
fare uso dell'oracolo!
Quindi il distinguitore
intercetta tutte le richieste di
una socket). Ma quando
messaggi, quindi
invoca l'oracolo cosa sta facendo? Sta cercando un Oracolo per cifrare dei
dovrò ingannare
e fargli credere di essere l'oracolo, e può farlo perchè se ben
ricordiamo la definizione di funzione pseudocasuale
alla funzione casuale
.
verso questo Oracolo (che possiamo vedere come
di cui parleremo poi! Quindi
ha accesso alla funzione pseudocasuale
deve pensare di avere a che fare con
ma anche
di
o di
Quando
chiama
per crittare un messaggio (fase di generazione) quello fa il distinguitore
una stringa casuale
e chiamarci l'oracolo (che è ricordiamo
è generare
o ), una volta che l'oracolo ci ha risposto,
non deve andare perso, infatti va messo insieme al crittogramma (altrimenti non si potrebbe decrittare) che
va restituito ad
(basta che vedi le definizioni di
e , e vedi che si comportano esattamente così, l'oracolo
è corretto).
A questo punto
reagisce come sempre, genera i due messaggi
ed
ed il distinguitore si comporta
esattamente come l'esperimento, sceglie quale dei due messaggi crittare, quindi genera una nuova stringa
casuale
, contatta l'oracolo e genera il crittogramma relativo al messaggio scelto che poi invia ad
Per il resto l'esperimento procede come sempre e alla fine
.
restituirà il classico bit che ci dirà se l'avversario
ha vinto oppure no.
Sfruttiamo D e la tipologia di Oracolo
Come abbiamo detto, l'oracolo chiamato da D o è la funzione pseudocasuale
questo è possibile perché
possiamo dire delle cose:
o la funzione casuale
(e
ha accesso ad entrambi per definizione). A seconda se questo oracolo sia
o
Se l'oracolo che "passiamo" a
Questo è vero perché
è la funzione pseudocasuale
(su un
casuale) possiamo dire che:
vede esattamente quello che vedrebbe nell'esperimento! La visione che ha A
dell'ambiente, quando a
passiamo la funzione pseudocasuale è esattamente quella che vedrebbe
nell'esperimento, dove il cifrario utilizzato è proprio quello che fa uso della funzione pseudocasuale!
Quindi le probabilità di vittoria sono le stesse!
Se l'oracolo che "passiamo" a
è una funzione casuale allora
Il concetto è lo stesso di prima ma dove abbiamo la funzione casuale e il cifrario che fa uso della
funzione casuale.
Cosa dovevamo dimostrare? Dovevamo dimostrare che
Ma abbiamo appena dimostrato due uguaglianze con gli elementi della differenza in senso assoluto che
stiamo dimostrando, ed inoltre sappiamo per ipotesi su
funzione pseudocasuale, che
.
Sappiamo quindi che l'oggetto
si differenzia dall'oggetto
per
qualcosa che è trascurabile! Quello che viene ora da chiedersi é:cosa ci rimane da dimostrare?
Dimostrazione: Punto 2
Vogliamo dimostrare che
Dove
è CPA-sicuro!
è il numero di query che
polinomio perché
esegue ad
è un avversario PPT, ma
(interrogazioni del'oracolo), che sarà per forza un
è una funzione trascurabile, quindi
è ancora una
funzione trascurabile (per le proprietà di chiusura dell'insieme dei trascurabili).
Perché se dimostriamo questa cosa riusciamo a dimostrare la tesi? Se dimostriamo questa cosa, stiamo
dicendo che
non può vincere con probabilità molto superiore ad
cifrario che si basa sulla funzione casuale, ossia dimostrando che
che
, nell'esperimento che testa
che è il
è CPA-sicuro! Inoltre, sappiamo già
non è molto diverso da
perché lo abbiamo dimostrato
con le uguaglianze di prima, in particolare differiscono per qualcosa che è trascurabile...ma questo vuol dire
che anche
è CPA-sicuro, e quindi abbiamo finito.
In pratica stiamo dimostrando la sicurezza CPA di
passando per
.
Dimostrazione concreta
Possiamo immaginare che questa disuguaglianza sia vera per
in quanto è molto difficile da forzare per
l'avversario (è basato su funzioni casuali). Per come abbiamo costruire l'esperimento e in particolare il
distinguitore, se l'oracolo è una funzione casuale quello che succede è che passiamo, ogni volta che
invochiamo l'oracolo, una stringa casuale
alla funzione casuale che genera una stringa casuale con la quale
ill distinguitore critta il messaggio.
[Riguardare la costruzione dell'esperimento D e pensare a cosa succeda quando l'oracolo è una
funzione casuale]
In ogni caso, quello che c'è di importante è che:
Ora
(oracolo di
), dal punto di vista di
è probabilistico (c'è sempre la
prodotta dal distinguitore
che genera randomness ed inoltre l'oracolo del distinguitore è proprio una funzione casuale) e che le
stringhe generate dall'oracolo di
sono tutte indipendenti tra loro (di certo invocare l'oracolo su un
messaggio e poi invocarlo su un altro ancora non influenza la struttura dei crittogrammi).
Ricordiamo che
è passato in chiaro all'avversario, e quindi
conosce certi punti del dominio della
funzione casuale, ma questo gli dà la possibilità di capire altri valori del dominio della funzione? No,
proprio perché è una funzione casuale, non c'è modo che
riesca a capire qualcos'altro. La stessa cosa
non potremmo dirla delle funzioni pseudocasuali, infatti sappiamo solo che è indistinguibile una
funzione casuale, ma non possiamo dire altro, per questo sfruttiamo
piuttosto che
.
Evento Repeat
La prova procede individuando un evento probabilistico chiamato
. Questo evento è fantastico per
l'avversario ed è definito come segue:
"Il
(quello passato all'avversario durante il gioco) è tale per cui
è una tra
le stringhe restituite dall'oracolo (come primo argomento)".
Perché
è contento se avviene
? Perché lui ha già invocato l'oracolo sul messaggio
,
probabilmente in fase di generazione dei messaggi, ed ha ottenuto dall'oracolo il crittogramma
e quindi sa che
Ma se avviene
è legato ad
anche se non è in grado di scomporlo.
vuol dire che anche il
corrisponde a
, ma
passato in chiaro e l'avversario l'ha già visto passare, l'unica cosa che deve fare è fare
questo modo ottiene l'oggetto
, ma ha già
è
ma in
, quindi se l'avversario è furbo riesce a salire al valore di
e
quindi vince sempre!
Cosa succede se
non vale? Se non vale, allora l'avversario si trova tanti
fare niente, infatti l'avversario conoscerebbe molti punti del dominio di
diversi e non riuscirebbe a
ma senza avere un
che ricompare
nel crittogramma, non può capire nulla di , per la funzione è strettamente casuale e non pseudocasuale!
Quindi se
non vale può solo tirare a caso per tentare di vincere!
Ultima parte dimostrazione punto 2 sfruttando evento Repeat
Ora quello che dobbiamo sfruttare il calcolo delle probabilità:
Infatti
è un evento probabilistico che fa parte dell'esperimento, è quindi valido scomporre la
probabilità di vittoria dell'avversario in questo modo, ossia la probabilità di vittoria dell'avversario è data
dalla somma tra la probabilità di vittoria quando vale
Ora, sappiamo per certo che
cosa che l'avversario può fare è tirare a caso.
e quando non vale!
perché come abbiamo detto, l'unica
Ma qual'è la probabilità di
? Be' sicuramente è minore uguale di
(un congiunto ha una probabilità più ampia della congiunzione di due eventi probabilistici!).
Quindi quello che possiamo dire è che:
Ma quant'è la probabilità di
contenga una
? E' uguale alla probabilità che uno dei crittogrammi generati dall'oracolo
che poi si ripresenta nel
.
Visto che, l'oracolo viene chiamato al massimo un numero polinomiale di volte
chiamata è indipendente (visto che l'oracolo è
che la probabilità che proprio una certa stringa binaria
casuale è proprio
e che ogni
che è probabilistico e che sfrutta una stringa casuale ) e
casuale di lunghezza
esca fuori in maniera
, otteniamo:
[Le probabilità indipendenti si sommano, non si moltiplicano!]
Gestire messaggi di lunghezza variabile
Il cifrario
è CPA-sicuro ogniqualvolta F è FP, ma può gestire solo messaggi di lunghezza pari alla
lunghezza della chiave. Quindi soffre delle stesse limitazioni di one-time pad, anche se fornisce molte più
garanzie (nel nostro caso
è probabilistico).
Esiste però un modo per generalizzare ogni cifrario CPA-sicuro
per messaggi di lunghezza
per messaggi di lunghezza
ad un cifrario
, dove è un polinomio:
La cosa che cambia è
, infatti il nuovo
non fa altro che applicare
ad ogni pezzo del messaggio
che ha lunghezza pari a
utilizzando sempre la stessa chiave . In questo modo possiamo gestire messaggi
lunghi fino a
Questa operazione ha senso farla perchè
, che appartiene a
che è CPA-sicuro, è probabilistico e quindi
applicarlo con la stessa chiave a pezzi diversi del messaggio va bene, perché ogni messaggio verrebbe
comunque criptato in modo diverso.
Se facessimo questa cosa su
sarebbe un disastro perché non era nemmeno sicuro verso le codifiche
multiple (e avevamo ovviato con i metodi operativi) ma nei cifrari CPA-sicuri questo va bene (perché
ricordiamo che i cifrari CPA-sicuri per codifica singola, lo sono anche per codifiche multiple!).
Da tutto quello che abbiamo detto deriviamo che:
Permutazioni pseudocasuali
Spesso le funzioni pseudocasuali non bastano, perché vogliamo non solo che
ma anche facilmente invertibile, che sia possibile da
Vogliamo calcolare l'inversa di
messaggio (input di
e da
ricostruire
sia un funzione calcolabile
. f è definita come:
in maniera efficiente, per risalire dal crittogramma (risultato di
) al
).
Quindi quello che vogliamo è che
sia una permutazione! Una permutazione in un insieme
nient'altro che una funzione biiettiva da
(stringhe binarie lunghe ) è
a
non è
. Tuttavia, il numero di permutazioni nell'insieme
che è comunque un numero inferiore al numero di funzioni nell'insieme
!
N.B. Tutte le permutazioni sono funzioni ma non viceversa, infatti sono di meno! Tutte le funzioni biiettive in
un certo insieme non sono sicuramente tutte le funzioni di un certo insieme in cui comprendiamo anche
quelle non biiettive!
La nozione di permutazione pseudocasuale è uguale a quella di funzione pseudocasuale dove però è
richiesto che anche l'inversa di
sia efficientemente calcolabile, quindi è una funzione pseudocasuale
biiettiva.
Quello che faremo sarà confrontare la permutazione pseudocasuale con una permutazione casuale!
Permutazioni fortemente pseudocasuali
Talvolta però gli avversari (distinguitori) non hanno solo accesso a
ma anche per alla sua inversa (che
esiste sempre), e questa cosa vale anche per la funzione casuale per la funzione casuale. In tal caso ha senso
richiedere che:
ottenendo così la nozione di permutazione fortemente pseudocasuale.
Quindi si richiede che la permutazione pseudocasuale sia indistinguibile da una permutazione casuale
pur dando all'avversario accesso non solo alla funzione casuale ma anche alla sua inversa! Questa non
è una cosa banale perché avere accesso all'inversa, potrebbe fornire una leva al distinguitore per poter
distinguere le due permutazioni.
Cifrari a blocco e permutazioni fortemente pseudocasuale
I cifrari a blocco sono spesso pensati per essere permutazioni fortemente pseudocasuali, infatti in alcuni
dei cifrari a blocco, la decifratura è fatta con la funzione inversa della cifratura!
Infatti i cifrari a blocco potrebbero essere anche solo funzioni casuali o permutazioni non fortemente
pseudocasuali.
Questo si riflette nei modi operativi dei cifrari a blocco della prossima sezione!
Modi operativi (dei cifrari a blocco)
La costruzione
, è solo uno dei moltissimi modi con cui, a partire da una funzione casuale, una
permutazione casuale o una permutazione fortemente casuale possiamo costruire un cifrario ossia uno
schema di codifica per messaggi di lunghezza maggiore di
(messaggi più lunghi della chiave).
Il termine modo operativo indica proprio questo, e in letteratura esiste un'infinità di modi operativi
Nei modi operativi, si procede sempre suddividendo il messaggio
messaggi
ciascuno di lunghezza pari a
da crittare in una sequenza di sotto
, assumendo quindi che
sia un qualche multiplo di
.
Electronic Code Book (ECB)
Ad ogni porzione del messaggio viene applicata
e poi si uniscono i crittogrammi che formeranno il
crittogramma finale. Questa costruzione non è sicura contro attacchi passivi! Infatti basta prendere un
messaggio in cui due componenti siano uguali e un altro in cui due componenti siano diverse (pensa
all'esperimento contro attacchi passivi).
Questo non è sicuro perché lo schema è deterministico, infatti sto usando
che è pseudocasuale
(potrebbe essere funzione o permutazione) ma deterministica per crittare... è pur sempre una funzione!
Quindi sto facendo codifiche multiple con la stessa chiave, che su due componenti del messaggio identiche
fornirà due componenti del crittogramma identiche!
Cipher block chaining (CBC) Mode
In questo caso abbiamo un vettore di inizializzazione che è random e che viene inviato in chiaro al
ricevente.
Quello che succede è che il vettore di inizializzazione, che avrà la stessa lunghezza delle porzioni del
messaggio, viene messo in xor con la prima porzione del messaggio e il risultato viene passato a
e il
prodotto finale è la prima porzione del crittogramma.
Dopodiché si continua con lo stesso modo ma
diventa l'IV per la seconda porzione del messaggio e così
via...
Dal punto di vista di chi vuole decrittare, basta che
quindi basta fare la funzione inversa su
otteniamo
sia invertibile tanto l'IV viene passato in chiaro,
per ottenere l'oggetto
, lo mettiamo in xor con l'IV e
. [Ricordiamo che lo xor è sempre invertibile]
Per il resto questo modo operativo è CPA sicuro, ogni qualvolta
è una permutazione casuale (la
dobbiamo poter invertire).
Problema del modo operativo
Questo schema è sequenziale, non posso calcolare
da
, perché c'è un legame causale tra
e
a partire da
e parallelamente calcolare
, bisogna aspettare quindi che
abbia finito di calcolare
poi continuare (quindi è meno efficiente).
Se ci fosse la possibilità di pre-calcolare delle informazioni sarebbe sicuramente meglio!
Output feedback (OFB) Mode
a partire
e
Non c'è una gran differenza rispetto allo schema precedente, ma lo schema fondamentale è che il legame
causale non è più tra crittogrammi, ma dipende da IV e da
, quindi possiamo calcolare lo stream
pseudocasuale ancora primi che arrivi effettivamente il messaggio!
N.B.
deve essere non idempotente, deve essere indistinguibile da una funzione casuale che non sono
sicuramente idempotenti. Idempotenza = "proprietà delle funzioni per la quale applicando molteplici volte
una funzione data, il risultato ottenuto è uguale a quello derivante dall'applicazione della funzione un'unica
volta."
Counter mode (CTR) MODE
Mette insieme le nozioni di tutti gli schemi precedenti: è uno schema sicuro e schema parallelo.
Il vettore di inizializzazione in questo caso è
che sta sempre in chiaro (C è sempre lungo come la porzione
del messaggio). La dipendenza è eliminata aumentando il contatore ad ogni messaggio che crittiamo. Siccome
si utilizza
pseudocasuale, il comportamento della stessa su
,
... è completamente differente!
Commento su OFB e CTR
Dal punto di vista di
, è importante che
una volta che il ricevente ha
sia invertibile? No, infatti:
si
Nel caso di CTR una volta che ha
si ricostruisce lo stream utilizzando
che è deterministica e
pseudocasuale e lo utilizza per derivare le porzioni del messaggio a partire dalle porzioni del
crittogramma;
Nel caso di OFB la stessa cosa, dove però la ricostruzione dello stream viene fatta a partire da IV e
utilizzando sempre
.
Tutto questo è sicuro perché, per il principio di Kerchoff, l'avversario ha accesso agli algoritmi, quindi
l'avversario conosce
ma non
(cioè
applicata ad una certa chiave, ce ne sono infiniti, quindi non può
conoscerli di certo tutti).
Schema riassuntivo capitolo 3
Abbiamo fatto generatori pseudocasuali, schemi sicuri contro attacchi passivi, funzioni pseudocasuali e
schemi CPA-sicuri.
Abbiamo detto che questi concetti non esistono in senso assoluto ma li mettiamo in reazione tra loro:
SCHEMI DI AUTENTICAZIONE A CHIAVE
SEGRETA
Cerchiamo di capire perché l'autenticazione è diversa dalla confidenzialità.
Confidenzialità e autenticazione
Nel caso della confidenzialità l'avversario poteva interagire con il canale sicuro nei modi più disparati, ma in
particolare lo scopo era leggere dal canale insicuro delle informazioni.
Mentre nel caso dell'autenticazione l'obiettivo dell'avversario è un altro, non ci interessa nemmeno che il
messaggio sia in chiaro, l'avversario non ne guadagna niente a leggere dal canale...guadagna nello scrivere sul
canale! Lo scopo è quindi che il mittente invii un'informazione al ricevente, che può anche essere resa
pubblica, l'importante è che non sia alterata rispetto all'originale (vogliamo che i messaggi siano autentici) !
Ricordiamo che la confidenzialità e l'autenticazione sono problemi ortogonali, possiamo avere un
sistema che garantisce la confidenzialità e non l'autenticazione e viceversa!
Autenticazione
Come detto, vogliamo garantire che il ricevente sia sicuro dell'integrità e dell'autenticità del messaggio che
riceve.
La proprietà di autenticazione e di confidenzialità sono distinte: non è detto che risolvere il problema della
confidenzialità risolva automaticamente il problema dell'autenticazione.
Prendiamo come esempio lo schema
, che genera crittogrammi sfruttando un generatore pseudocasuale
. Un ipotetico avversario potrebbe facilmente costruire da
messaggio del tutto simile a
, il crittogramma per un
.
Attacchi simili al precedente, riguardano anche cifrari a blocco come
il primo blocco) e pure
(dove una modifica di
influenza
(dove una modifica al blocco influenza solo il blocco ).
Inoltre, i crittogrammi (pur essendo confidenziali) sono facilmente alterabili da parte dell'avversario! Infatti
un avversario potrebbe cambiare gli ultimi bit, ottenendo un crittogramma che corrisponde ad un messaggio
leggermente diverso! Il messaggio rimane sempre confidenziale ma viene alterato su dei bit che magari sono
fondamentali (pensiamo all'ammontare di una transazione bancaria) e il ricevente non ha nessun modo per
capire che non sia autentico (se utilizziamo come cifrario
)!
Ovviamente non è detto che l'avversario riesca a modificare il messaggio in maniera sensata, dovrebbe
conoscerne la struttura e non sempre questo è possibile.
Dobbiamo quindi introdurre una nozione di senso compiuto? No, ci basterà che il messaggio generi un
qualunque messaggio autenticato per considerare il cifrario insicuro nel senso dell'autenticazione anche
perché non c'è un limite chiaro tra il senso compiuto e il non senso compiuto (si pensi alle fake news).
Message authentication Codes
In realtà nell'autenticazione non ci sono più le attività di criptaggio e decriptaggio dei messaggi perché
abbiamo detto che anche se il messaggio fosse in chiaro ci andrebbe comunque bene. I messaggi vengono
invece taggati o etichettati come autentici o non autentici e si fa la verifica sull'autenticità del
messaggio. Si dice che il mittente "produca un certificato di autenticazione per il messaggio" e che il
ricevente "verifichi se il tag è autentico".
Quindi, esattamente nello stesso senso in cui i cifrari sono lo strumento atto a garantire la confidenzialità, i
o
sono lo strumento atto a risolvere il problema
dell'autenticazione.
Definizione
Un
Una tripla
L'algoritmo
,
e
di algoritmi
prende in input una stringa nella forma
sicurezza e produce in output una chiave
L'algoritmo
tali che:
tale che
prende in input un messaggio
, da interpretare come il parametro di
;
, una chiave
e produce un
(che è una stringa
binaria);
L'algoritmo
(verify) prende in input una chiave , un messaggio
e un
, producendo un
booleano in output (il tag è biono per quel messaggio).
Correttezza
Un
La correttezza del
dei casi problematici.
è corretto quando
.
viene definita facendo riferimento allo scenario tipico di utilizzo, non diciamo nulla
I
in ambiente a chiave pubblica diventano firme digitali, il tag non è altro che un certificato di
autenticità e infatti viaggia in chiaro assieme al messaggio!
Definire la sicurezza di un MAC
L'idea è che l'avversario non deve poter creare un tag valido (senza conoscere le chiavi) e quindi
l'avversario non deve forgiare (ossia produrre un tag valido per) un qualunque messaggio
di sua scelta,
senza conoscere la chiave!
Come al solito ci mettiamo in uno scenario pessimistico di attacchi attivi:
L'avversario ha accesso ad un oracolo per
mentre forgia il messaggio (ci mettiamo nel
contesto degli attacchi attivi). Precisiamo che,
chiave
non è l'algoritmo
, ma è 'algoritmo con la
fissata!. Se l'avversario ha accesso a questo oracolo può facilmente generare un messaggio
valido (basta passarlo all'oracolo)! Vogliamo dimostrare che l'avversario non riesca a forgiare un
messaggio valido nemmeno in questo caso che è il peggiore possibile per noi crittografi!
Questo scenario sembra irreale, ma in realtà si potrebbe suppore che l'avversario abbia accesso a
perché vede tantissime coppie
passare sul canale e, in certi casi, potrebbe anche avere il controllo
su ciò che passa sul canale (immaginiamo come se fosse una terza parte che interagisce con Alice);
In ogni caso, anche se questa situazione di accedere all'oracolo è assurda la utilizziamo per dare una
buona definizione di sicurezza. In maniera prudente, se dimostriamo la sicurezza per una cosa così
forte, sicuramente dimostriamo la sicurezza che per casi più deboli.
Considereremo però che la coppia
ottenuto all'accesso all'oracolo non sia da considerarsi una
forgiatura corretta;
L'avversario come al solito deve essere
.
Definire la sicurezza di un MAC (nuovo esperimento)
Come sempre l'esperimento è parametrizzato sul parametro di sicurezza e procede come segue:
Viene generata una chiave;
All'avversario viene passato il parametro di sicurezza e una socket attraverso cui può richiamare
l'oracolo. Il suo compito è quello di produrre una coppia
;
Nel caso della confidenzialità c'era un secondo round per l'avversario...ora no! Semplicemente
l'esperimento è favorevole per
dei messaggi con cui
quando il messaggio che lui produce non è in
ha interrogato
, dove
è l'insieme
(perché abbiamo detto che se accediamo all'oracolo non
sono da considerarsi una forgiatura corretta) e dove ovviamente il messaggio prodotto è autentico.
Definizione di sicurezza
La probabilità di vittoria dell'avversario deve essere trascurabile! Il compito dell'avversario non è di capire
quale codifica gli è stata passata, ma è quello di produrre in output in qualcosa molto più complesso (
di lunghezza
e messaggi di lunghezza tipicamente uguali a , ma anche maggiori), quindi
non può tirare a
caso (perché se tirasse a caso la probabilità di vittoria sarebbe enormemente bassa) e la probabilità che
generi un messaggio autentico deve essere trascurabile.
Commenti definizione
La definizione della sicurezza è molto forte:
Concretamente un avversario è interessato a forgiare messaggi di senso compiuto;
Qui invece consideriamo come buono il fatto che l'avversario forgi un messaggio qualunque!
Inoltre, l'avversario ha accesso a questo oracolo
!
Da un altro punto di vista però, la definizione sembrerebbe un po' debole:
C'è una classe di attacchi che la nostra definizione non prendere in considerazione e sono gli attacchi
;
In tali attacchi, la stessa coppia
viene spedita più volte sul canale, la prima da un utente legittimo,
le altre dall'avversario (l'avversario memorizza la coppia e la invia più tardi). Questa cosa è possibile
perché il
non dipende dal tempo;
Concretamente, questo problema viene disaccoppiato e vien gestito ad un livello più alto dello stack
(iso-osi) tipicamente a livello applicativo con meccanismi quali
e
.
Costruire un MAC sicuro
Abbiamo definito la definizione di un
sicuro ma dobbiamo ancora definirne uno! Un modo di costruire
Mac è sicuramente quello di sfruttare le funzioni
, ma le vedremo più avanti, ora vediamo un altro
modo.
Il primo esempio di
sicuro che daremo si basa su un concetto che conosciamo già, ovvero quello di
Funzione Pseudocasuale.
Concretamente quindi
genera una chiave casuale (stringa lunga ) prendendola con probabilità
uniforme tra quelle possibili (che sono
e
) e quindi è un algoritmo genuinamente casuale;
si comportano come già descritto. Va specificato solo che c'è un'espressione booleana
particolare (quella con ) che però è banale, significa solo che questa restituisce
sono uguali,
se i due oggetti
altrimenti.
Inoltre va detto che
diventa esattamente la funzione pseudocasuale, non sarà nient'altro!
Semplicemente chiama la funzione pseudocasuale a chiave fissata sul messaggio;
Il
semplicemente controlla che il
ricevuto sia uguale a quello tirato fuori dalla funzione
pseudocasuale applicata al messaggio (la funzione è pseudocasuale ma deterministica, quindi tiriamo
fuori sempre lo stesso output dallo stesso input).
Ovviamente
Infine,
non è più uno schema di codifica ma un
.
sono deterministici, ma a noi ci va bene, perché siamo nel contesto
dell'autenticazione!
Essendo che il
è unicamente una funzione pseudocasuale (che è indistinguibile da una funzione
casuale), l'avversario non sarà in grado di sfruttare tag già visti per capire qualcosa sul funzionamento di
e riprodurlo perché ogni stringa prodotta apparirà come indipendente dalle altre, totalmente casuale!
Quindi per l'avversario sarà difficilissimo costruire un valore valido diverso da quelli tirati fuori interrogando
l'oracolo (infatti anche tirasse fuori un numero polinomiale di stringhe binarie (
), non riuscirebbe a tirare
fuori nessuna proprietà perché sono generate da una funzione pseudocasuale). Nelle funzioni casuali è
impossibile predire l'output da una certa stringa di output, e le funzioni pseudocasuali sono indistinguibili
quindi...
Teorema
Difetto dello schema di autenticazione
Il brutto di questo schema è che la chiave deve essere lunga quanto il messaggio, perché la funzione
pseudocasuale prende un input di una certa lunghezza e ritorna un output della stessa lunghezza (ritorniamo
ai difetti della sicurezza perfetta e del one-time pad).
In particolare, questo è un
a lunghezza fissa (dove i messaggi che riesce a gestire sono lunghi ), dopo
la dimostrazione parleremo dei messaggi a lunghezza variabile!
Dimostrazione Teorema Mac sicuro se F pseudocasuale
La dimostrazione sarà ancora per riduzione, passando attraverso la costruzione di un
però
non è pseudocasuale ma casuale!
Costruzione MAC con funzione pseudocasuale
idealizzato, in cui
Costruiamo un
casuale
e
come
costruito in modo del tutto simile a
ma in cui
genera una funzione
la applica al messaggio in input. Cosa possiamo dire della probabilità che un avversario
riesca a forzare
dal punto di vista di
Come mai? L'unico modo che
?
ha di forzare
è invocare il
di
un numero polinomiale di volte e
poi deve produrre un tag diverso da tutti quelli già visti grazie all'oracolo per un certo messaggio sempre da
lui prodotto.
Siccome la funzione generata da
è pienamente casuale e quindi tutti i tag prodotti dall'oracolo sono
indistinguibili, cioè tutte le stringhe generate dall'oracolo sono totalmente casuali, non hanno proprietà che le
legano, l'unico modo che ha l'avversario per generare il tag (diverso da tutti quelli visti) è tirare a caso! Infatti
l'avversario non riesce a capire nessuna proprietà di
proprio perchè gli output sono totalmente casuali!
Se la funzione fosse pseudocasuale il discorso sarebbe diverso, ma ora la funzione è totalmente casuale. La
probabilità di vittoria dell'avversario non può essere
che è la probabilità di generare proprio il tag
appartenente al messaggio generato tirando a caso! Ricordiamo che siamo nel contesto in cui il tag è lungo
quanto il messaggio, la probabilità di tirare fuori casualmente un tag lungo
può essere superiore
(parametro di sicurezza) non
.
Idea della dimostrazione
Ora che abbiamo costruito questo
ideale, lo utilizziamo come Pivot per dimostrare che:
Ossia dimostriamo come al solito, che il comportamento del
che utilizza una funzione pseudocasuale
non si comporta molto diversamente da quello in cui la funzione è casuale e che in particolare questa
differenza sia trascurabile!
Ma se è vero che:
e riusciamo a dimostrare che è vero:
allora sarà sicuramente vero che:
Questo perché se la differenza tra i due casi è trascurabile e se la probabilità che l'avversario vinca contro il
che fa uso della funzione casuale è anch'essa trascurabile, allora per forza la probabilità di vittoria
dell'avversario contro il
che fa uso della funzione pseudocasuale è trascurabile! Se così non fosse
avremmo rotto l'ipotesi
.
Dimostriamo
per riduzione
Per dimostrare che:
dobbiamo come al solito andare per riduzione, costruiremo un distinguitore a partire dall'avversario
però si comporta in modo diverso perché siamo in
, dove
.
Fase 1 costruzione distinguitore
L'avversario deve credere di essere in un esperiemento e quindi si comporta esattamente come in
,
riceve un parametro di sicurezza, chiama l'oracolo su vari messaggi, per ognuno dei quali riceverà un tag e alla
fine deve produrre una coppia
di messaggio,tag dove il tag deve essere diverso da tutti quelli ricevuti e
visti dall'oracolo.
Fase 2 costruzione distinguitore
L'interfaccia di riduzione, è molto più semplice dei casi precedenti. Come detto, vogliamo fare in modo che
l'avversario
pensi di essere nell'esperimento, quindi è banale che il parametro di sicurezza venga inviato dal
distinguitore direttamente all'avversario.
Per quanto riguarda le richieste all'oracolo,
le inoltra direttamente all'oracolo
che corrisponderà al
a funzione, casuale o pseudocasuale e inoltrerà all'avversario tutti i tag ricevuti in questo modo.
Fase 3 costruzione distinguitore
Dopo che
ha generato la coppia
, cosa succede? Come inganniamo
? Richiamiamo nuovamente
l'oracolo, e controllo se il tag generato per quel messaggio è effettivamente valido (ricordiamo che l'oracolo è
), sostanzialmente sto imitando il verify.
Fase 4 costruzione distinguitore
Ma è sufficiente questa cosa? No, abbiamo detto che l'avversario deve pensare di trovarsi nell'esperimento,
ma l'esperimento alla fine controlla se la coppia (m,t) restituita dall'avversario, contiene uno dei messaggi
che l'avversario ha inviato all'oracolo per conoscerne il tag! Perchè se ben ricordiamo, il messaggio generato
dall'avversario doveva essere differente da tutti quelli inviati all'oracolo, ma per fare questo dobbiamo avere
una base di dati in cui tenere traccia di tutte le richieste fatte all'oracolo e vedere se il messaggio generato e
proprio in una delle richieste. Se questo non è vero possiamo inviare un bit di risposta che indica se
l'avversario ha vinto o ha perso!
Ultimo punto dimostrazione (2) e Fine dimostrazione
Osserviamo che per come abbiamo costruito
vale che:
questo è sicuramente vero perché l'avversario pensa sempre di essere nell'esperimento per come abbiamo
costruito
, sia che
abbia accesso ad un oracolo che è una funzione pseudocasuale o una funzione casuale
e quindi le probabilità di successo dell'avversario sono le stesse (ovviamente cambiando il
a seconda
della funzione).
Ma sappiamo che, per ipotesi di funzione pseudocasuale (per ipotesi sappiamo che
è pseudocasuale!) che:
ma allora per semplici uguaglianze vale anche che:
ma allora abbiamo finito (per le ragioni spiegate nell'idea della dimostrazione).
Gestire messaggi di lunghezza variabile: Introduzione
Per gestire quindi messaggi di lunghezza variabile, utilizzando sempre la stessa chiave (dove i messaggi
possono essere molto più lunghi della chiave) potremmo utilizzare un paradigma già visto, prendiamo un
messaggio lo scomponiamo in un numero
di blocchi ognuno di stessa lunghezza e autentichiamo ognuno di
questi blocchi (è un modo operativo sostanzialmente).
Dato un messaggio
Autenticare
si potrebbe provare a procedere come segue:
, ossia autentichiamo il risultato dello xor eseguito su tutti blocchi del messaggio.
Tuttavia questa strategia è fallace, infatti all'avversario basta produrre un messaggio suddiviso in
blocchi che abbia lo stesso xor che tagghiamo per vincere sempre (ricordiamo che ha accesso
all'oracolo);
Un altro modo sarebbe quello di autenticare ciascun blocco
separatamente, per poi prenderne lo
xor, piuttosto che autenticare il risultato dello xor:
. Questa strategia è
sicuramente migliore della precedente ma anche questa è fallace. In tal caso però, un avversario
riuscirebbe a forgiare facilmente un messaggio che è esattamente come il precedente, ma dove i
blocchi sono stati permutati:
Questo risulta possibile perché lo
, dove
è una permutazione.
è un'operatore commutativo, infatti
l'avversario ha accesso all'oracolo del
, e dato che
è fatta;
Tentativo fuori slide: se tentassimo di taggare i singoli blocchi del messaggio e prendessimo questa
semplice concatenazione come
finale? Non va bene perché l'avversario potrebbe chiamare l'oracolo
su un messaggio lungo, eliminare l'ultima parte del tag e generare un un tag valido su un messaggio che
è semplicemente il prefisso del messaggio precedente, ed è quindi diverso (genero tag catturando il
prefisso di un tag generato su un messaggio uguale più lungo).
Esempio:
L'avversario genera
e ci chiama l'oracolo che genererà
. E a quel punto, per l'avversario è facile forgiare un
messaggio con
valido (senza chiamare l'oracolo), infatti basta prendere il prefisso del messaggio
prefisso del messaggio e restituirli, in particolare basta restituire la coppia
, il
;
Tentiamo quindi una quarta strada: autentichiamo ciascun blocco
sequence number , ossia
separatamente
al
. In questo modo la tecnica delle
permutazioni non funziona più, tuttavia anche qui l'attaccante potrebbe forgiare altri
messaggi...utilizzando i prefissi!
Gestire messaggi di lunghezza variabile: Algoritmo
Occorre quindi mettere assieme tutta una serie di idee diverse, ossia suddivisione di blocchi, sequence
numbers e randomizzazione.
"Dato un
" per messaggi a lunghezza fissa, possiamo costruire un nuovo
per messaggi di lunghezza variabile come segue:"
Spiegazione sull'algoritmo
Algoritmo
:
Come detto
è la lunghezza dei messaggi che il
a lunghezza fissa è in grado di gestire;
Per prima cosa l'algoritmo spezza il messaggio in
blocchi, ognuno dei quali è esattamente lungo
Poi ci salviamo la lunghezza del messaggio
,lo facciamo perchè poi andremo a concatenare
;
questa variabile assieme ai blocchi per evitare il trucco dei prefissi visto prima;
Generiamo un'unica volta una stringa binaria casuale
lunga esattamente
. Questa stringa
casuale viene inserita per motivi tecnici, se non ci fosse ci sono degli attacchi possibili;
Si fa un ciclo for sui blocchi del messaggio e si calcola il tag (sfruttando l'algoritmo
lunghezza fissa) per ognuno di loro, utilizzando
del
a
come la chiave (parametro in input) e concatenando la
stringa casuale , la lunghezza del messaggio , l'indice di iterazione (che funge da sequence number)
e il blocco del messaggio
.
Quello che si ottiene in output è un
stringa casuale
formato da insieme di
(uno per blocco del messaggio), e la
(altrimenti non ci sarebbe speranza di poter verificare l'autenticità del messaggio) che
viaggia in chiaro così come i tag.
Algoritmo
:
Come al solito si scompone il messaggio in
blocchi con le condizioni di prima;
Si calcola ;
Si cicla sui blocchi del messaggio verificando l'autenticità dei vari blocchi (sfruttando l'algoritmo
del
a lunghezza fissa). Come si nota dall'algoritmo riusciamo a ricavare tutto quello che ci serve
per verificare la validità del tag; Secondo me manca un pezzo sul Vrfy, perché prende in input solo
chiave e messaggio ma ci vorrebbe anche il tag, ossia
.
Se almeno uno dei blocchi ha un tag non valido, ritorno 0 (
) altrimenti 1 (
);
Problemi di questa costruzione
Come vediamo nell'algoritmo
, la funzione
che corrisponde alla funzione pseudocasuale
(per costruzione di
) viene calcolata
volte (ed è costosa come sappiamo);
La chiave utilizzata
è sicuramente, ma abbiamo un problema con la lunghezza del
molto lungo, più lungo del messaggio! In particolare è lungo
mentre il
, infatti questo è
è lungo
. Questo è vero
perchè, il MAC a lunghezza variabile sfrutta il MAC a lunghezza fissa, che abbiamo imposto essere come
un MAC per messaggi lunghi , quindi anche la quantità
che passiamo a
, ha per forza
lunghezza ; Quindi per inviare messaggi lunghi un giga utilizzeremo un tag lungo 4 giga (non buono);
Teorema sicurezza del MAC a lunghezza variabile
Dimostrazione teorema sicurezza MAC a lunghezza
variabile
Se
è una
Ricordiamo la forma di
e
sicuro, allora
è anch'esso sicuro.
:
Idea della dimostrazione ed eventi probabilistici
Vogliamo dimostrare che:
Per fare questo introduciamo degli eventi probabilistici:
: "Lo stesso
occorre almeno due volte, tra quelli prodotti dall'oracolo in
".
Nell'esperimento succedono molte cose che lo rendono un processo probabilistico e può succedere
che tra tutte le richieste che
stesso . Ma questo
fa all'oracolo (che in questo caso è
bassa, proprio perché la scelta di
: "Se
), ce ne sia una che produce lo
prodotto dall'oracolo è puramente casuale, quindi la probabilità di
è molto
è casuale!
è l'output di
ottenuto forgiando una stringa nella forma
in
, allora esiste un tale che
diversa da tutte quelle in cui
non è
chiama l'oracolo
." In altre parole, l'avversario se avviene questo evento riesce a forgiare un tag diverso da tutti
quelli ottenuti chiamando l'oracolo (se questa cosa accade l'avversario è molto intelligente). Dato che
non l'ha ricavato dall'oracolo lo può sfruttare per forzare il
!
Costruzione della dimostrazione
Definiremo la probabilità della vittoria dell'avversario verso
come la somma di eventi tra loro disgiunti
basandoci proprio sull'avvenimento di questi due nuovi eventi probabilistici:
Dato che siamo interessati a dare delle limitazioni superiore possiamo sfruttare il classico
e dire che tutta quella somma di prima è
di:
Lemmi intermedi
Lemma 1
Abbiamo detto che l'evento che si verifichi
ha una probabilità molto bassa, la probabilità che capiti due
volte la stessa stringa scelta casualmente è sicuramente trascurabile.
Dimostrazione Lemma 1
Quale è la probabilità che un certo numero di chiamate polinomiali producano una collisione? Sfruttando il
teorema del compleanno (che vedremo più avanti in questo documento) è
che è trascurabile poiché
è
un polinomio! Per delucidazioni rapide guarda qui.
Lemma 2
Se nessuno dei due eventi probabilistici (che sono vantaggiosi per l'avversario) avviene allora la probabilità di
vittoria dell'avversario è . Lo diamo per scontato senza dimostrazione.
Ultimo passo della dimostrazione
Per dimostrare quindi che
, dobbiamo dimostrare che
!
Tutto il lavoro fatto finora era una preparazione per mettere in evidenza l'evento
dimostrazione si basa sul costruire un avversario
per
a partire da
Come al solito poi facciamo la prova per riduzione: diamo ad
in realtà si trova dentro
.
. La
in modo tale che
l'impressione di trovarsi in
ma
La costruzione CBC-MAC
Per come abbiamo costruito lo schema, la chiave è più corta del messaggio ma il
è più lungo del
messaggio, e questo per motivo (ma anche per il costo del calcolo del ma anche per l'invio del messaggio)
dobbiamo cambiare lo schema. Fortunatamente, ci viene in aiuto
generiamo il
, grazie a questo modo operativo
!
Algoritmo
:
Calcoliamo il numero di blocchi in cui dividere il messaggio che sarà polinomiale nella lunghezza di
(non possiamo permetterci di avere blocchi troppo grandi);
Suddivido quindi il messaggio in blocchi lunghi .
A questo punto si applica il metodo CBC. Si genera un vettore di inizializzazione
fissato, ed è cruciale che lo sia e per l principio di
formato da una stringa di
con un valore
tutti lo conoscono. In questo caso è
zeri ma poteva essere qualunque altra cosa, basta che sia nota e che
dipenda dal parametro di sicurezza ;
Il nome
non è casuale, infatti è il primo tag, della classica catena formata dal modo operativo CBC.
Ad ogni iterazione del ciclo for (che parte da indice 1) non faccio altro che prendere il tag precedente (si
parte da
) e chiamare la funzione pseudocasuale
sul risultato dello xor tra
ed
.
Alla fine non invio tutti i tag, ma solo uno...l'ultimo! In questo modo il tag inviato è esattamente lungo
quanto la chiave
Algoritmo
!
:
In ricezione, semplicemente controllo se la lunghezza del messaggio è quella corretta, se non lo è allora
posso già dire che il messaggio non è valido. Se invece lo è allora non devo fare altro che ricalcolare
sulla chiave e il messaggio ricevuti e confrontarli con (geniale).
Nel caso in cui i
siano diversi ritorno false altrimenti true;
Siamo stati in grado di ovviare al problema della lunghezza dei
volte la funzione pseudocasuale
, infatti siamo in
ma non all'efficienza persa calcolando
e non possiamo pre-calcolare le varie
perché c'è
una dipendenza con i tag precedenti!
Teorema
Questa prova è molto difficile proprio perché dobbiamo utilizzare solo l'ultimo tag in invio e dimostrare che
funziona solo con lui è difficile.
Ci sono modi diversi per costruire Mac sicuri a partire dalle funzioni pseudocasuali:
Vedremo che le funzioni HASH utilizzate per costruire Mac sicuri non si basano sulla pseudocasualità ma su
qualcosa di molto diverso.
Le funzioni Hash
Sono funzioni che comprimono stringhe lunghe in stringhe corte in modo che ci siano meno collisioni
possibili (ci sono sempre ragioni di cardinalità).
Una collisione per una funzione Hash
è una coppia
tale che
con
, quindi stringhe
diverse che vengono compresse nella stessa stringa dalla funzione Hash.
Come detto, le collisioni esistono sempre per ragioni di cardinalità, perché, ad esempio, potremmo mappare
stringhe binarie lunghe
(che sono
) in stringhe lunghe
dove
(che sono
).
Vogliamo evitare che tante stringhe diverse vengano mappate nello stesso valore, non solo per sicurezza ma
anche per efficienza (la hash table è formata da coppie chiave-valore, se tanti valori vengono inseriti nella
stessa chiave il costo in tempo della ricerca è elevato).
Cosa vogliamo dalle funzioni Hash
Vogliamo che le collisioni non solo siano in minor numero possibile ma che in un certo senso, queste
collisioni siano impossibili da determinare...da avversari efficienti! Ovviamente se queste collisioni sono
impossibili da determinare a noi va bene, d'altronde vogliamo che queste funzioni vengano utilizzate per
generare tag e vogliamo che l'avversario non capisca le proprietà dei messaggi guardando coppie (m,t)[per
l'esperimento]!
Per minor numero possibile si intende "spalmare" i valori corrispondenti alla funzione hash in maniera più
"equiprobabile" possibile!
Poi vogliamo che questa impossibilità di determinare collisioni valga anche per avversari costruiti in modo
specifico per trovarle. Con questa affermazione stiamo parafrasando il principio di Kerchoff: l'avversario può
conoscere la funzione hash e potrebbe essere progettato unicamente per trovare collisioni su quella
specifica funzione, ma nonostante ciò non deve riuscire a determinare le collisioni in maniera efficiente!
Definizione funzione Hash
Una funzione Hash è vista come una funzione
:
Il primo parametro è una chiave , che però è resa pubblica. In questo modo siamo in grado di usare
l'approccio asintotico e di parlare di avversario efficiente. La chiave è pubblica in quanto è una sorta di
"seme".
A volte queste seme definisce il nome della funzione (un nome generico potrebbe essere
concreto reale
, esempio
), ma a volte non è così.
Il secondo è una stringa
che è quella che la funzione
cerca di comprimere.
Funzioni hash: definizione formale
restituisce una chiave
senso che di solito
da cui
(parametro di sicurezza) possa essere efficientemente calcolato nel
è proprio lungo
o qualcosa del genere;
Il punto cruciale è il secondo, la funzione
restituisce in output una stringa
per qualunque input
a prescindere dalla lunghezza,
.
Come sappiamo lo scopo dell'hashing è restringere stringhe binarie lunghe in stringhe piccole e
supponiamo che esista un polinomio
siano lunghe
ogni
e
(dove
è definita solo quando
parametri implicito in , allora
Come al solito indichiamo con
tale per cui tutte stringhe
parametro di sicurezza implicito in
(messaggi) su cui andiamo a lavorare
). Se questo polinomio
(cioè quello che abbiamo detto poco fa), ed
viene detta funzione hash a lunghezza fissa.
la funzione che, su input , restituisce
Funzioni hash resistenti alle collisioni
.
per
è il
Le funzioni hash possono essere definite sicure in modi diversi, in particolare quello a cui vogliamo riferirci è
l'impossibilità dell'avversario di trovare una collisione in maniera efficiente! Questa nozione di sicurezza
viene chiamata col nome di "resistenza alle collisioni" ed è la più forte e restrittiva. Si basa sul seguente
esperimento:
L'idea della sicurezza è "una funzione hash è resistente alle collisioni se nessun avversario efficiente è in
grado di trovarne"!
All'inizio
ad
genera una chiave
dove
che non solo conosce quindi
è il parametro di sicurezza implicito nella chiave e poi la invia
(infatti è pubblica avevamo detto perché siamo nell'autenticazione)
ma conosce anche il parametro di sicurezza implicito in .
L'avversario genera due messaggi
;
Il risultato è positivo per l'avversario se i due messaggi sono diversi e se la funzione Hash applicata al
primo restituisce la stessa stringa che restituisce la funzione Hash applicata al secondo (se ha trovato
una collisione).
Ovviamente occorre che
ed
siano definite! Infatti per come abbiamo definito le funzioni hash,
queste potrebbero essere definite solo quando i messaggi hanno una certa lunghezza prefissata
parametrizzata su , quindi l'avversario non può produrre due messaggi su cui
non è definita!
N.B. Le funzion hash concrete sono implementazione di questo schema dove fissiamo la chiave (Es. MD5)
Definizione formale
Anche qui l'avversario se tira a caso ha bassissime probabilità di vittoria: infatti se tirasse a caso dovrebbe
generare due messaggi di una certa lunghezza (Es.
) in maniera totalmente casuale e le probabilità che la
funzione hash sia uguale su questi due messaggi (se è una buona funzione hash) è veramente bassa!
Tutto questo è vero perchè l'avversario è
, infatti se non lo fosse potrebbe utilizzare il bruteforce e
generare tutte le possibili stringhe e trovare una collisione (che in una funzione hash esiste sempre). Tuttavia,
se la funzione hash è costruita in maniera furba (è una buona funzione hash) le collisioni col bruteforce non
sono individuabili in tempo polinomiale!
Note sulle funzioni hash
Nelle funzioni hash concrete, l'output ha lunghezza fissata, invece per quanto riguarda funzione hash astratte,
quelle che stiamo definendo usando l'approccio asintotico, la lunghezza dell'output non è fissata, è pari ad
, che è un polinomio che cresce all'aumentare del parametro di sicurezza . Cioè non c'è niente che ci dice
che il polinomio
sia una costante, ma se lo fosse non potremmo ottenere la resistenza alle collisioni,
infatti un avversario
potrebbe in un tempo molto grande ma costante trovare una collisione!
Ci sono anche altre definizioni di sicurezza per le funzioni hash (questa appena vista è la più forte nel senso
dell'avversario, lui a partire solo dalla chiave deve costruire la collisione, e quindi per noi è più forte).
Nozioni più deboli di sicurezza per le funzioni Hash
La definizione vista poco fa è molto forte, perché l'avversario parte solo da una chiave e deve trovare una
collisione. Vale la pena però vedere altre due definizioni, via via più deboli rispetto alla precedente.
Resistenza alla Seconda Preimmagine
Introduciamo questa nozione parlando di un famoso attacco ad
in cui l'avversario è riuscito a trovare
una collisione in un documento post script partendo da un altro documento post script. In pratica, l'avversario
è riuscito a costruire un documento post script che collideva con il precedente e che aveva certe
caratteristiche. Questo attacco non rientra nella categoria precedente, infatti l'avversario non parte più solo
da una chiave ma anche da un documento, quindi la definizione di sicurezza è un po' più debole,
l'avversario sta facendo qualcosa di più facile.
Quello che abbiamo detto è esattamente la definizione di sicurezza relativa alla resistenza alla seconda
preimmagine: Data una chiave
che
ed un messaggio , deve essere impossibile per
costruire un
tale
.
Resistenza alla Preimmagine
A differenza della seconda preimmagine, all'avversario non passo in input l'oggetto per cui cerca una
collisione, ma gli do il risultato della funzione Hash applicato all'oggetto per cui sta cercando una collisione!
"Dati
e
Da notare che
", deve essere impossibile per
costruire
tale che
."
potrebbe essere anche , non abbiamo imposto il vincolo della diversità, però la funzione
hash non è invertibile quindi a partire da
l'avversario non dovrebbe poter ricostruire .
Le nozioni di sicurezza viste sono via via più deboli, perchè:
Se trovo un attacco per la resistenza alla pre-immagine, posso costruirne uno per la resistenza alla
seconda preimmagine;
Se trovo un attacco per la resistenza alla seconda pre-immagine, posso costruirne uno per la
resistenza alle collisioni.
Per rendersi conto di questo basta fare un paio di prove per riduzione, infatti questo concetto è proprio
quello che sta alla base delle prove per riduzione, la negazione della non esistenza è l'esistenza!
Essendo che la sicurezza alle collisioni è più forte della resistenza alla seconda immagine (stessa cosa
per B verso C), questo implica che se non esiste un avversario
alle collisioni non esiste nemmeno un avversario
efficiente che possa rompere la resistenza
efficiente che possa rompere la resistenza alla seconda
pre-immagine! Ma se riesco a trovare un' avversario per B allora ne posso trovare uno per A!
Prova per riduzione che da
Dimostriamo per riduzione che se trovo un'avversario per B (per la resistenza alla seconda pre-immagine),
posso costruirne uno per A (resistenza alle collisioni), gli avversari li chiamiamo proprio B e A con ovvi
riferimenti.
Un avversario
per A (resistenza alle collisioni), ovviamente deve trovarsi all'interno dell'esperimento
, quindi prende in input una chiave
messaggi
dove
è il parametro di sicurezza implicito e genera due
.
Tuttavia l'avversario
per B (resistenza alla seconda pre-immagine) deve pensare di trovarsi in un suo
particolare esperimento, e sappiamo che questo avversario richiede due cose in input: una chiave
messaggio . Questo messaggio
verrà semplicemente generato da
Questo verrà poi inoltrato passato a
e in uscita dall'esperimento.
e un
, in maniera totalmente casuale.
Qui non bisogna fare dei grandi ragionamenti probabilistici, la probabilità di vittoria di
di vittoria per
diventa la probabilità
.
N.B. Possiamo fare lo stesso ragionamento possiamo fare per
Attacchi birthday
Come abbiamo detto è sempre possibile effettuare un "brute force" su una funzione hash poiché le collisioni
esistono sempre, tuttavia se questa è ben costruita il tempo per trovare una collisione deve essere superiore
ad un tempo polinomiale. Vale quindi la pena chiedersi come effettuare un "brute force" su una
funzione hash e quale sia la relativa complessità.
Possiamo ad esempio subito dire che la complessità è esponenziale? Ma in che senso?
Consideriamo in tal senso uno scenario in cui la chiave
sia già fissata e in cui l'avversario
voglia trovare
una collisione in
. Allora l'avversario per fare bruteforce cosa dovrebbe fare? Dovrebbe
testare tutte le stringhe lunghe
fino a che non trova una collisione?
Be' la cosa che deve fare l'avversario è scegliere in modo casuale
di esse, sperando di trovarne due sulle quali
Se
stringhe in
, testare
su ognuna
restituisce lo stesso valore:
, c'è la certezza di trovare una collisione per ragioni di pura combinatoria, ossia se testo ad
esempio
stringhe in
, essendo che l'insieme di output della funzione hash
è lungo
,
troverò per forza una ripetizione. Tuttavia il tempo impiegato sarebbe esponenziale nella lunghezza
dell'output della funzione hash, che è un polinomio definito sul parametro di sicurezza! Attacco
non adatto ad un avversario efficiente!
Se invece
allora possiamo sfruttare il teorema del compleanno.
Teorema del compleanno: definizione
Sostanzialmente ci dice quanta probabilità abbiamo di trovare due valori casuali identici in un insieme, ossia
una collisione!
Problema nel ragionamento e caso peggiore per l'attaccante
Nel ragionamento fatto c'è però qualcosa che non va. Infatti non è detto che l'output prodotto da
sia
totalmente casuale (uniformemente distribuito), anche nel caso in cui l'input che gli passiamo lo sia!
Perchè ci interesserebbe che l'output della funzione hash sia uniformemente distribuito? Perchè sarebbe il
caso peggiore per l'attaccante! Infatti se l'output di
non fosse casuale, ma anzi fosse concentrato in
pochi valori, be' allora la collisione l'avversario la trova subito! Se invece l'output fosse totalmente casuale
allora dobbiamo applicare il teorema del compleanno!
Supponiamo quindi che il comportamento di
scegliamo
sia più vicino possibile a quello di una funzione casuale. Se
(il numero di query che facciamo alla funzione hash ha come limite inferiore la metà
della cardinalità dell'output) e la cardinalità dell'insieme dell'output della funzione hash,
è pari
, allora per
il teorema del compleanno, l'avversario avrà probabilità di successo pari a :
Per trasformazione asintotica stiamo dicendo che la probabilità di vittoria dell'avversario è pari a una
costante ed è quindi indipendente da
e da . Ma se è indipendente da
e da allora è molto facile portarla
vicino a .
La morale è che, grazie alla nozione teorica del teorema del compleanno, anche se l'attaccante si trova nel
caso peggiore, riesce a vincere con una probabilità alta anche quando
dell'output della funzione hash
!
Grazie a Wikipedia, sappiamo che se
circa il
è molto più piccolo della cardinalità
la probabilità di vittoria (e quindi di trovare una collisione) è
.
Esempio concreto Birthday Attack
Supponiamo di utilizzare
, funzione hash con output pari a 128 bit (cardinalità
deve scegliere, sfruttando la nozione del teorema del compleanno,
pari a
), allora l'avversario
bit.
Migliorare i Birthday Attack
Possiamo migliorare i birthday attack in due modi:
Riducendo la complessità di spazio, per implementare questo attacco abbiamo bisogno sia di spazio
che tempo esponenziale, dobbiamo tenere traccia di tutti i risultati, dovremo avere una grande tabella
per tenere traccia di tutti i valori della funzione hash trovati. Nel caso peggiore la tabella è grande come
le query che facciamo che sono tipicamente in numero esponenziale.
Rendendo l'attacco dipendente dal contesto, in questo modo l'attacco potrebbe trovare collisioni tra
messaggi di senso compiuto (e non solo tra stringhe casuali).
La Trasformazione di Merkle-Damgård
E' possibile fare in modo che una funzione hash per messaggi di lunghezza fissa possa gestire messaggi
di lunghezza variabile...continuando ad essere resistenti alle collisioni?
Si, c'è un modo formale che si chiama trasformazione di Merkle-Damgård, l'idea è molto semplice,
riapplicare la funzioni hash su vari blocchi del messaggio.
Supponiamo che
sia una funzione hash per messaggi di lunghezza
output stringhe binarie di lunghezza
partire da questa (
(dove
che produca in
è il parametro di sicurezza implicito nella chiave). Costruiamo a
) nel modo seguente:
L'algoritmo dapprima suddivide il messaggio in
blocchi, dove
è pari all'arrotondamento per eccesso
del rapporto tra la lunghezza del messaggio e il parametro di sicurezza;
A questo punto il messaggio viene scomposto in
blocchi, dove ognuno è lungo esattamente come il
parametro di sicurezza (nel caso ci sia qualcuno che la lunghezza minore facciamo padding);
Definiamo un nuovo blocco
assegnandogli come valore la lunghezza del messaggio (abbiamo già
visto fare questa cosa nei modi operativi, inserivamo la lunghezza del messaggio nella sequenza di
encoding per evitare attacchi sui prefissi).
Definiamo un
fissato a
, che deve essere passato in chiaro. Deve essere fissato perché altrimenti la
funzione hash non sarebbe deterministica!
Dopodiché facciamo un ciclo for e costruiamo ogni volta un nuovo vettore di inizializzazione, applicando
la funzone hash
sulla chiave
e sulla concatenazione dell'IV precedente e sul blocco del messaggio
(ricordiamo che il blocco del messaggio è lungo
così come l'IV, siamo coerenti con quello che abbiamo
detto)!
Il risultato sarà proprio l'ultimo inizialization vector così formato che avrà lunghezza pari a
Schema riassuntivo:
Teorema sulla sicurezza della funzione hash per messaggi di
lunghezza variabile
E' sorprendente la dimostrazione del seguente teorema:
Limite della funzione hash per messaggi di lunghezza variabile
Sicuramente non può gestire un messaggio di lunghezza qualsiasi, in particolare non può gestire messaggi più
lunghi di
perché non potremmo rappresentarli in una stringa lunga
Ma siccome
bit (in output).
possiamo sceglierlo concretamente, possiamo avere stringhe più o meno lunghe (
....
veda un po' lei!).
Hash-and-Mac
Possiamo utilizzare le funzioni hash per costruire dei
, ma non da sole! Nel paradigma Hash-and-Mac,
si fa uso delle funzioni hash e dei cifrari a blocco (cioè le funzioni psedocasuali) per costruire dei
che
siano adatti a messaggi di lunghezza variabile.
Perché vale la pena di parlare di questa costruzione? Perché è una costruzione che rimane
dal punto di
vista teorico, ma è molto più efficiente se la utilizziamo bene nella pratica!
Infatti, avevamo visto che i cifrari a blocco, per come li abbiamo visti nei modi operativi, non sono molto
efficienti perché li chiamiamo molte volte e sappiamo che sono costosi. Invece in questo nuovo paradigma
le funzioni casuali (che sono i cifrari a blocco ricordiamo) vengono chiamate una volta sola sul risultato
della funzione hash! Quindi non facciamo altro che utilizzare la funzione hash per comprimere il messaggio
e poi ci si applica il
del cifrario da cui partiamo (che viene chiamato una sola volta e che corrisponde
alla funzione pseudocasuale).
In questo modo riusciamo a rendere un
sicuro per messaggi di lunghezza fissa, capace di gestire anche
messaggi di lunghezza variabile!
Definizione formale Hash-and-Mac
Dato un
e una funzione hash
, definiamo il
come segue:
su input
risultato di
ritorna (la codifica di) una coppia di chiavi
dove
è il risultato di
e
il
;
ritorna
Come al solito,
;
ritorna
se e solo se
.
Le chiavi utilizzate in hash-and-mac in realtà sono coppie di chiavi: una chiave viene utilizzata nella funzione
hash e l'altra viene utilizzata per la vera e propria autenticazione.
Le funzioni hash in questo caso servono solo per ridurre l'input, non costruiamo uno schema di
autenticazione da 0, ci deve essere un
già definito e sicuro per messaggi di lunghezza fissa.
Sulla base della sicurezza di un MAC sicuro e dalla base della resistenza alle collisioni della funzione hash,
creiamo un qualcosa di sicuro. Questa costruzione
è molto più utilizzata nella realtà rispetto a
(che è
molto forte perchè non ha ipotesi) perché pur richiedendo l'ipotesi che esista una funzione hash resistente
alle collisioni sono molto più efficienti perché chiamiamo
solo una volta.
Teorema sicurezza
HMAC
Questo schema si basa solo sull'esistenza di una funzione hash resistente alle collisioni, che è una cosa
abbastanza naturale! Definizione da libro:
Spiegazione
In
c'è bisogno di usare più di uno strato di hashing, vediamo lo schema:
Come al solito si passano i blocchi del messaggio da autenticare alla funzione hash,esattamente come
facevamo con la trasformazione vista precedentemente (in pratica stiamo modificando
), dove però ci
sono due livelli in più: uno prima di iniziare l'hashing e uno dopo!
Iniziamo dicendo che
stiamo definendo mentre
e
sono delle costanti mentre l'
è casuale.
è la chiave del
è la chiave della funzione hash e che farà parte della chiave del
che
che stiamo
definendo!
Non possiamo limitarci a usare la parte classica ma ci serve una parte iniziale e finale aggiuntiva, perché
senza questi ulteriori livelli di hashing dei semplici attacchi contro il
che otteniamo sarebbero
facili da costruire.
In particolari i blocchi tratteggiati devono avere proprietà di pseudocasualità, nel senso dei generatori
pseudocasuali: il risultato dello
tra la chiave
con
e
deve essere qualcosa che è pseudocasuale
nel senso dei generatori pseudocasuali.
La dimostrazione della sicurezza non è ovvia!
Dimostrazione sicurezza Hash-and-Mac
Hash-and-Mac diverso da HMAC ricordiamo!
Ricordiamo che questo schema fa uso sia di un
sicuro e di una funzione
collisioni. Supponendo che la funzione hash sia
funzione
e
resistente alle
sicuro su una certa chiave sia
, allora la
, infatti, come ci aspettiamo, la funzione mac di Hash-and-Mac
deve prendere una chiave che in realtà è una coppia di chiavi
pseudocasuale) e
dove
la chiave della funzione hash. Gli algoritmi
è la chiave del
e il
(funzione
sono i soliti.
Teorema da dimostrare
Se
è sicuro e
è resistente alle collisioni, allora
è sicuro!
Idea della dimostrazione ed eventi probabilistici
Questa prova è leggermente diversa dalle altre, di solito da un'assunzione dimostravamo un'altra prova di
sicurezza, ora invece ci basiamo su due assunzioni per dimostrarne una! Infatti abbiamo ipotesi di sicurezza
sia sul cifrario
che sulla funzione hash
che sfrutteremo entrambe.
Dovremo quindi costruire due prove per riduzione! Costruiremo prima un avversario contro
e uno contro
. Per capire il tutto facciamo un ragionamento preliminare sull'avversario e su un evento probabilistico:
Consideriamo un avversario
per
e sia
l'esperimento di nostro interesse.
Consideriamo ora un evento probabilistico per indicare le possibilità di vittoria dell'avversario;
Consideriamo l'evento probabilistico
prodotto in output da
(collisione) definendolo come segue: "Se
(che possiamo considerare non tra quelli in
risposta alle query fatte da
) esiste
tale che
è il messaggio
, ossia quelli dati dall'oracolo in
". L'avversario è quindi riuscito a
trovare una collisione! Ovviamente questo evento deve avere una probabilità trascurabile perché
è
resistente alle collisioni.
Cosa dobbiamo dimostrare
Innanzitutto, le probabilità che l'avversario
sul fatto che l'evento
vinca l'esperimento
sono basate proprio
avvenga o meno:
Dato che siamo interessati a limitazioni superiori possiamo come al solito sfruttare il trick probabilistico è dire
che:
Questo avviene per due motivi:
, in quanto se è vero che esiste una collisione possiamo
anche scordarci di MacForge, ci basta isolare coll, perchè se coll avviene l'avversario vince;
, in questo caso non ha molto senso isolare
, perché possiamo
sfruttare il fatto che non ci siano collisioni per mostrare che della sicurezza si occupa il
.
Ma allora cosa dobbiamo dimostrare? Che entrambi i termini
sono trascurabili.
Lemma1
è trascurabile!
Non facciamo altro che costruire un avversario
. Tuttavia
verifica
per
tale che la probabilità di successo di
è stato definito sulla base di
il messaggio è prodotto in output da
quando è applicato ad
, quindi costruiremo
N.B. In tutte le immagini relative al lemma 1, la scatola interna è di
a partire da
e non di
sia proprio
, infatti quando si
.
come invece c'è scritto!
Prova per riduzione Lemma 1 - Punto 1
si aspetta in input una chiave
compenso
e senza chiamare oracoli produce in output due messaggi
essendo un avversario relativo a
(e non ad
, in
) ha un comportamento
complesso, si aspetta un parametro di sicurezza, interagisce con l'oracolo molte volte e alla fine produce una
coppa
.
Ma allora questo vuol dire che ci sarà un'oracolo che sarà gestito proprio da
!
Anticipiamo già da ora che inoltrare il parametro di sicurezza da C in A è triviale poiché
è il parametro di
sicurezza implicito nella chiave .
Prova per riduzione Lemma 1 - Punto 2
Dobbiamo costruire un oracolo per fare in modo che
fa delle richieste le fa alla funzione
ricaviamo una chiave
sembri di essere nell'esperimento, quindi quando
quindi ricreiamo la funzione
che diventerà la chiave dell'oracolo
su cui
! Da
chiamiamo l'algoritmo
farà delle query.
Ma manca qualcosa? Be' decisamente si! Dobbiamo ricreare esattamente l'ambiente dell'esperimento
, ma allora prima di essere sottoposti al
i messaggi devono essere hashati!
Prova per riduzione Lemma 1 - Punto 3
Però ci accorgiamo di qualcosa: la
restituita da
parte in modo tale che la probabilità di vittoria di
ossia quando
non è la
restituita da
. Dobbiamo costruire questa
sia esattamente pari alla probabilità che avvenga
ha trovato una collisione, quindi passiamo
,
alla funzione hash e vediamo se tra tutti gli
hash abbiamo calcolato ce ne sta uno uguale. Se questo è vero allora vuol dire l'avversario ha trovato una
collisione e restituiamo il risultato della collisione in output se c'è!
Giusto per chiarire le funzioni hash sono le stesse!
Analizzando il modo in cui abbiamo definito
La visione di
subroutine di
ci rendiamo conto che:
è la stessa di
in
produce una collisione esattamente quando vale
ma sappiamo per ipotesi sulla funzione hash
trascurabile, ma allora anche
;
evento probabilistico.
che è resistente alle collisioni che
è
lo è!
Lemma 2
In questo caso sfrutteremo la sicurezza di
dall'avversario
, costruiremo quindi un avversario per
, chiamiamolo
a partire
.
Prova per riduzione Lemma 2 - Punto 1
Stiamo costruendo un avversario
per
cioè per il
, mentre
è un avversario per
e lui si
aspetta che le sue richieste passino prima da una funzione hash e poi al mac. Quindi la parte delle richieste
del
va inoltrata all'esterno da
ma la parte dall'hash deve essere gestita internamente.
Per gestire internamente quindi le chiamate alla funzione hash, devo generare a partire dal parametro di
sicurezza una chiave
e poi chiamare la funzione hash.
Prova per riduzione Lemma 2 - Punto 2
Ma cosa succede quando
però
genera la coppia
? Tipicamente
lavora su messaggi a lunghezza fissa,
non lo è, ma è a lunghezza variabile quindi per ovviare al problema ci applico la funzione hash sopra
e faccio in modo che
inoltri il messaggio hashato e il tag. Ci si applica il mac sopra e se il messaggio hashato
genera un tag identico a quello prodotto da
allora
vince altrimenti no.
Prova per riduzione Lemma 2 - Punto 3
E' evidente che
sia un buon avversario nel senso polytime, ma bisogna capire quando è che vince
a quando vince
. Purtroppo le due probabilità di vittoria non sono identiche: abbiamo perso di vista le
collisioni! Infatti è possibile che
questo caso solo
produca una forgiatura che per due messaggi diversi
vincerebbe e non
Ma noi sappiamo per ipotesi su
, quindi la probabilità di vittoria di
che
collida! Ma in
è proprio data da:
è trascurabile, ma allora anche
lo è!
Lemma 2 dimostrato e fine dimostrazione.
rispetto
Costruire oggetti pseudocasuali e funzioni
hash in concreto
Costruiremo oggetti pseudocasuali e funzioni hash concrete (non più in maniera astratta), perché queste
sono le due primitive fondamentali nel contesto della crittografia a chiave privata, sia per l'autenticazione
che per la confidenzialità (per costruire cifrari e MAC sicuri).
Dobbiamo trovare un modo per costruire questi oggetti, abbiamo due strade possibili:
Dimostrare come tali oggetti esistano in senso teorico sulla base di assunzioni sulla difficoltà di
certi problemi problemi computazionali (cosa che faremo nel prossimo capitolo). Ad esempio la
difficoltà nel fattorizzare i numeri interi in numero primi. Questa strada va bene dal punto di vista
strettamente teorico, tuttavia non possiamo sfruttare gli oggetti costruiti in questo modo concretamente!
Perché pur avendo la certezza teorica di aver creato qualcosa di sicuro, sono tremendamente
inefficienti!
Descrivere primitive concrete che, seppur non dimostrabilmente pseudocasuali (o resistenti alle
collisioni), sembrano avere delle buone caratteristiche di sicurezza (ce ne occuperemo in questo
capitolo). Cosa vuol dire buone caratteristiche? Vuol dire che passano certi test, ad esempio
è
progettato in modo tale che abbia l'effetto valanga (la vedremo).
In entrambi i casi ci sono assunzioni, ma l'assunzione nel caso concreto è molto più complessa e molto
meno studiata, semplicemente assumo che un cifrario che costruisco sia pseudocasuale perché ha passato
certi test.
In conclusione l'approccio concreto non fornirà le stesse sicurezze che avremmo utilizzando l'approccio
asintotico (prima o poi magari un attacco per quello cifrario/MAC lo troveranno).
Cerchiamo di capire perché scegliamo la seconda strada
Se le primitive di cui parleremo in questo capitolo non possono essere dimostrate avere le proprietà di
pseudocasualità (o resistenza alle collisioni) che cerchiamo...che senso ha studiarle? Ha senso studiarle
perché possiamo considerare il fatto che queste primitive soddisfino le proprietà di sicurezza come
un'assunzione!
Per rafforzare la differenza tra l'approccio concreto e l'approccio teorico: "Assumere AES sicuro è
fondamentalmente diverso da assumere che la fattorizzazione è un problema difficile" (AES è uno schema
vecchio di 20 anni mentre il problema è stato studiato da centinaia di anni!).
Lungo il percorso parleremo non solo di primitive concrete ma anche di modelli per tali primitive e ciò ci
permetterà di dare condizioni necessarie (ma non sufficienti) alla loro sicurezza.
Costruire dei Cifrari di flusso
Come sappiamo sono un altro nome per i generatori pseudocasuali a lunghezza variabile e
concretamente sono costituiti da una coppia di algoritmi
in cui:
L'algoritmo
inizializza lo stato interno, a partire da una chiave (e opzionalmente da un vettore di
inizializzazione
L'algoritmo
). Il numero degli stati interni è tipicamente finito.
, a partire dallo stato interno, produce in output un singolo bit, modificando
contemporaneamente lo stato interno.
Applicando iterativamente
allo stato ottenuto tramite
si possono ottenere stream di bit
arbitrariamente lunghi e vorremmo che gli algoritmi così ottenuti fossero più possibile simili a generatori
pseudocasuali a lunghezza variabile (per decidere la lunghezza basta semplicemente decidere il numero di
iterate di
a partire da
).
Non è detto ovviamente che l'output che generiamo sia pseudocasuale, ad esempio potremmo costruire
come un algoritmo che produce sempre il bit
senza modificare lo stato interno, be' allora non c'è
modo di ottenere qualcosa di pseudocasuale.
Quello che vorremmo ottenere dalla stringa generata dai cifrari di flusso concreti è che sia indistinguibile da
stringhe casuali nel senso della pseudocasualità (sostanzialmente
non deve sempre produrre gli
stessi pattern di output).
Ovviamente per raggiungere questo obiettivo, ci vuole che il numero di stati sia abbastanza grande, finito ma
grande! Perché se ci pensiamo il numero di stati detta il periodo dei "pattern" della stringa in output.
Registri a scorrimento retroazionato lineare
Sono un particolare tipo di cifrario di flusso in cui:
Lo stato è costituito da
bits
. Non diciamo cosa sia
e come questo trasformi la
chiave nello stato interno, per ora ce ne fottiamo;
calcola un bit come lo
di alcuni dei bit dello stato che diventerà il nuovo valore di
scorrere tutti i bit verso destra producendo in output il valore del bit meno significativo
valore appena calcolato in
, fa
e inserisce il
.
Come detto spostando i valori verso destra viene prodotto come output
facciamo spazio a sinistra per un
nuovo bit che sarà il risultato dello xor tra i due bit dell'array selezionati. Tuttavia vanno specificate un po' di
cose:
Quali indici di bit metto in xor per costruire il nuovo bit più significativo, non è lo stato a deciderlo ma
proprio
. Questi indici dei blocchi i quali valori dei bit vengono in xor sono detti coefficienti di
feedback.
La chiave è data dallo stato iniziale e dai coefficienti di retroazione;
I registri a scorrimento retro-azionato lineare non sono sicuri, ma sono d'ispirazione per altri cifrari.
Insicurezza di questo tipo di registri
Ma perché i registri a scorrimento retro-azionato lineare (chiamati anche
) sono insicuri?
Il comportamento futuro, di un
, è completato determinato dal suo stato, cioè se conosciamo lo stato
sappiamo tutto. In questi registri non esistono non più di
dallo stato
stati e un LSFR che cicli sui
stati diversi
è detto a lunghezza massima.
Parentesi sullo stato
Se lo stato fosse composto da tutti 0, ci sarebbe sempre 0 in output per come abbiamo costruito
e
quindi qualunque proprietà di pseudocasualità sarebbero impossibili da ottenere.
Perché a lunghezza massima?
Come abbiamo detto il massimo numero di stati è
proprietà di pseudocasualità, ossia
, se evitiamo l'unico stato che ci farebbe perdere le
, ma cicliamo su tutti gli altri
stati, allora abbiamo ottenuto il
massimo periodo possibile per questo registro (il periodo è dato dagli stati che possiamo visitare). C'è
sicuramente un periodo perché il numero di stati è finito, e quindi prima o poi ritorneremo ad uno stato già
visto e a un bit già prodotto! Siamo comunque soddisfatti, il periodo è molto lungo!
Proprietà
Gli
a lunghezza massima
a lunghezza massima hanno ottime proprietà statistiche,e sono gli unici ad essere interessanti
da un punto di vista crittografico perché:
Hanno un periodo molto lungo;
La probabilità di produrre in output ciascuna sequenza di bit lunghe 4 è la stessa per ciascuna di
sequenza di bit lunghe 4 (probabilità uniforme).
E' ottimo quindi per difendersi da attacchi statistici, a meno che non si facciano attacchi statistici su periodi
lunghi (cioè
) che sono comunque duri da fare.
Debolezza di LSFR
A partire dai primi
output
è possibile ricostruire completamente lo stato iniziale (ossia
) e i coefficienti di feedback (tramite un sistema di equazioni lineari, nel senso dell'algebra booleana,
che si sa risolvere efficientemente, in particolare in tempo polinomiale in ).
Quindi dopo appena
passi possiamo dire tutto della storia futura di questo cifrario a blocco, perché
conosciamo lo stato e i coefficienti di feedback. Questa cosa accade perché ci limitiamo alle funzioni
lineari, nel senso dell'algebra booleana, dobbiamo fare qualcosa di non-lineare, invece di avere un semplice
xor possiamo fare qualcosa di più complicato lato feedback e/o semplicemente mettere una funzione non
lineare nel bit in output e fare quindi qualcosa di complicato lato output.
Ecco perché gli
sono d'ispirazione, si parte dal loro modello e si aggiunge la non-linearità sempre nel
senso dell'algebra booleana.
Trivium
Cifrario di flusso relativamente recente per cui non c'è stato modo di trovare un attacco efficiente.
Questo tipo di cifrario di flusso prendere ispirazione dal registro retro-azionato ma impone della nonlinearità sia lato feedback sia lato output.
Ora lo stato interno è formato da tre registri ed il feedback è molto complesso (rete di xor ed and).
Il trucco sta nel creare periodi lunghi con operazioni di feedback complesse in modo non-lineare.
Ovviamente non si può dimostrare che questo cifrario di flusso sia sicuro se vale una certa assunzione
sull'algebra booleana, però sembra che abbia ottime proprietà di sicurezza e soprattutto è molto efficiente!
Nota interessante: Trivium è estremamente efficiente se è implementato in hardware (i registri sono dei
semplici flip-flop). Fare un circuito con una rete logica a basso livello che implementa Trivium è triviale e
come detto è efficiente dal punto di vista dei tempi.
Parentesi sulla differenza tra cifrari a flusso/blocco e
schemi di codifica
Un cifrario di flusso è diverso da uno schema di codifica, è una primitiva che può essere utilizza per
costruire uno schema di codifica ma non uno schema di codifica! Infatti un cifrario di flusso è un semplice
algoritmo e non una tripla di algoritmi. In particolare, il cifrario di flusso prende stringhe corte e le
espande in stringhe lunghe preservandone le caratteristiche di pseudocasualità agli occhi di avversari
efficienti.
Stesso discorso vale per i cifrari a blocco, un cifrario a blocco non è altro che una funzione pseudocasuale (o
anche permutazione fortemente casuale...credo) che può essere utilizzato per costruire schemi di codifica!
RC4
Passiamo ora a dei cifrari di flusso implementati via software (trivium è meglio se implementato hardware), ci
sono molte primitive messe a disposizione, tra le quali
.
E' un registro retro-azionato, solo che ora lo stato interno consiste in un vettore di byte detto
pari a
, che rappresenta una permutazione dell'insieme
sempre composto quindi da una coppia di algoritmi
di lunghezza
, assieme a due valori
. E'
, dove il primo produce lo stato interno e
il secondo produce in output questa volta un byte e non un bit (ne produce 8 in realtà)!
Commento definizione
Come stato utilizziamo un vettore di stato
di byte lungo
, che è molto grande, infatti almeno
apparentemente ci sono
bit utilizzabili, tuttavia non è proprio così, infatti non sfruttiamo questi byte
appieno! Questa sequenza di
byte rappresenta una permutazione, quindi non tutti i possibili valori di
questi
byte sono ammissibili...sono ammissibili soltanto quei valori che corrispondono a permutazioni
(non ci devono essere ripetizioni!).
Come sappiamo un byte= 8 bit, quindi i valori possibili per un byte sono compresi nell'intervallo
dobbiamo evitare quindi quelle configurazioni in cui il
ad esempio sia ripetuto
,e
volte (non sarebbe una
permutazione)! Una permutazione sarebbe quella in cui il valore in posizione prenda il valore in posizione
ad esempio.
I valori
sono dei puntatori, degli indici al vettore .
In RC4
da un valore iniziale per
mentre
serve per produrre un bit di output e modificare lo
stato interno .
Descrizione algoritmi
ed
Nota
Tutte le operazioni, sono operazioni in modulo 256 (anche se non è specificato lo si dà per scontato).
Descrizione
Nel caso di
per RC4
la chiave è lunga
byte (
particolare, ma come abbiamo detto
), e l'output è lo stato iniziale. Non succede niente di
deve essere una permutazione dell'insieme. All'inizio
viene definita
come la permutazione triviale, ma poi nella seconda parte dell'algoritmo avvengono alcuni swap!
Ovviamente se su una permutazione
non si introducono delle ripetizioni.
facciamo degli swap questa rimane ancora una permutazione, poiché
In particolare si fa lo swap tra gli elementi
e
, dove però viene ricavato in modo furbo: viene ricavato
sommando l'indice precedente, con il valore del byte in
e con il valore che sta in
. E' una roba strana,
non c'è una dimostrazione che questa roba apporti sicurezza, però si suppone che apportino confusione
allo stato.
Descrizione
In
per RC4
ovviamente non produciamo un bit ma un byte in output e questo deve modificare lo stato
interno!
In particolare, il byte in output da
non sarà altro che il valore di
in un certo indice. Il valore in
questo indice ovviamente viene preso dopo che lo stato interno è stato modificato!
Cosa succede in particolare:
1. Si incrementa l'indice in modo lineare e l'indice in maniera caotica;
2. Si fa uno swap tra
ed
;
3. Si calcola un indice sommando i valori dei byte in
4. Si restituisce l'elemento
ed
;
;
Conclusioni su RC4
Esistono una serie di attacchi statistici che impediscono ad RC4 di essere considerato sicuro. RC4 ha dei
problemi forti relativi ai primi byte emessi in output: è più probabile che certi valori escano fuori piuttosto che
altri. Questa potrebbe non sembrare una cosa problematica ma in realtà lo è!
Immaginiamo ad esempio di utilizzare RC4 in
, cosa naturale, se il primo byte è più orientato ad essere 0
piuttosto che 1, l'avversario qualcosa del messaggio lo può capire.
Concretamente, questo attacco è avvenuto: infatti RC4 era il cifrario di flusso su cui era basato WEP, e la
debolezza di questo standard era proprio il fatto che RC4 avesse dei problemi relativi alla probabilità dei valori
dei bit.
Spesso i cifrari di flusso sono creati sulla base di euristiche (in questo caso si gioca sulla permutazione per
creare confusione per rendere difficile il lavoro dell'avversario). Però questa roba sul lungo periodo non ci da
garanzie.
I Cifrari a blocco, Concretamente
Ci sono un insieme di modelli e di idee che hanno resistito all'analisi degli esperti crittografi, la situazione è un
pochino migliore rispetto ai cifrari di flusso.
Cerchiamo di contestualizzare concretamente i cifrari a blocco. Siamo ovviamente interessati alle funzioni
nella forma
e cercheremo di costruirle in modo che siano pseudocasuali.
Queste funzioni prendono in input una chiave lunga
(che è una costante e non un parametro di sicurezza),
una stringa lunga e produce una stringa lunga , dove:
può essere diverso da ma entrambi sono costanti (approccio concreto).
Proprio perché siamo nell'approccio concreto, non dobbiamo più chiederci se esista un attacco polinomiale, ci
interessa un qualcosa di concreto ed efficiente, qualcosa che non prenda troppo tempo per essere eseguito
(ad esempio se
è lunga
, ci interessa che gli attaccanti non riescano a distinguere la funzione
funzione casuale in un tempo minore di
).
da una
Attacchi considerati
Consideriamo gli attacchi che conosciamo già (ciphertext-only, known-ciphertext,chosen-plaintext,
chosen-ciphertext), in particolare ci interessano quelli in cui l'attaccante ha a disposizione tante coppie di
(testo in chiaro, testo cifrato) e sulla base di queste coppie tenterà di distinguere la funzione che ha di
fronte da una funzione casuale, ma lo fa cercando di determinare la chiave (infatti gli attacchi sono quasi
sempre
).
Per il principio di
, determinare la chiave è sufficiente a forzare il cifrario che diventa ,quindi,
facilmente distinguibile da una funzione casuale.
Volendo l'avversario potrebbe fare solo un bruteforce sulle chiavi, avendo a disposizione molte coppie testoin-chiaro/testo-cifrato.
Ogni iterata del bruteforce sulle coppie testo-in-chiaro/testo-cifrato, riduce lo spazio delle chiavi possibili e in
pochi passi ci possiamo ritrovare in un insieme singleton (rimane solo una chiave possibile). Se però, ogni
passo di riduzione , ossia ogni iterata, ha costo esponenziale ovviamente il bruteforce non è possibile
(quindi basta che la chiave sia molto grande).
Substitution permutation network
La cosa carina rispetto ai cifrari di flusso, è che esistono dei modelli per i cifrari a blocco che sono attuali pur
essendo molto vecchi. Uno di questi modelli è la substitution permutation network o
che è
fondamentalmente un circuito combinatorio senza uno stato interno.
In questo modello, al messaggio in input, viene applicata iterativamente (per un numero di volte
abbastanza grande, tipicamente 16,32, etc...) una trasformazione che è ottenuta componendo tre fasi:
Mixing con la chiave: ossia i bit del messaggio e della chiave (una parte o una espansione di essa)
vengono messi in xor;
I cosiddetti
bit in al più
. Sono delle trasformazioni che mappano pochi bit in pochi bit, tipicamente al più
bit. Sono tipicamente delle funzioni booleane definite in un certo spazio. Ci sono molti
gradi di libertà per definire la funzione (o le funzioni) di trasformazione che ogni S-BOX implementa. La
cosa fondamentale è che le
non agiscono bit per bit ma su blocchi di bit, a differenza
dell'azione di mixing con la chiave.
Una permutazione. Questa permutazione è tipicamente fissa e non dipenda dalla chiave. Come ci
aspettiamo i valori dei bit del messaggio non vengono modificati ma solo permutati.
Da un lato c'è una fase che sfrutta la chiave (che l'avversario non ha a disposizione), ossia il mixing con la
chiave, ma poi ci sono delle fasi che per quanto complesse l'avversario conosce.
Schema SPN
Il messaggio in input ha lunghezza
bit che mettiamo in xor con una parte della chiave. A questo punto
dividiamo il tutto in gruppi di 8 bit e li passiamo a varie S-Box, concateniamo i risultati e facciamo una
permutazione sul risultato della concatenazione!
N.B: Le
possono essere funzioni tutte diverse ma anche la stessa funzione, nessuno lo vieta.
Due linee guida per la costruzione di SPN
Nella costruzione delle SPN non esistono principi in grado di garantire la sicurezza del cifrario ottenuto.
Ci sono delle linee guida che sono necessarie (evitiamo gli attacchi triviali) ma non sufficienti (potremmo
sempre trovare un attacco pur rispettandole).
Gli
devono essere invertibili. Questa cosa ha a che fare non tanto con la sicurezza quanto
più con la correttezza! Lo scopo, come sempre, è quello di ricavare dall'output di una SPN, il messaggio
originale. Vediamo cosa succede dal basso verso l'alto (seguendo lo schema):
L'output è invertibile ai bit intermedi in quanto una permutazione è per definizione invertibile;
Se le
non fossero invertibili non potremmo risalire ai bit intermedi!
Una volta invertite le s-box e aver ricavato i bit intermedi è triviale ricavare il messaggio di input
(basta mettere in xor i bit intermedi e la chiave).
Occorre garantire l'effetto valanga:
Una modifica di un bit dell'input di una
deve propagarsi in almeno due bit dell'output;
Così facendo, se il numero di round (della SPN) è sufficientemente alto (tipicamente
), una modifica di un bit nel messaggio si ripercuoterà potenzialmente su
tutti i bit dell'output.
L'effetto valanga è una caratteristica non solo utile, ma necessaria alla pseudocasualità. Quello che
vogliamo è che il cifrario a blocco sia una permutazione fortemente pseudocasuale e che quindi sia
indistinguibile da una permutazione casuale. Vogliamo quindi che il cifrario si comporti in modo
caotico, che un cambiamento in un bit nell'input potenzialmente apporti cambiamenti a tutti i bit
dell'output.
Domanda interessante: perché c'è bisogno della permutazione finale
Domanda: perché c'è bisogno della permutazione finale? Perché, anche utilizzando
costruite in
modo particolarmente furbo, se non ci fosse, un piccolo cambiamento nell'input si ripercuoterebbe solo
su una piccola parte dell'output.
Esempio di utilità S-BOX e permutazione ad ogni round SPN
Supponiamo di avere due messaggi di 8 bit rispettivamente
ed
che differiscano solo nell'ultimo bit,
cerchiamo di capire qual'è la differenza applicando una SPN ad entrambi!
Come è possibile vedere dallo schema, che è comunque molto chiaro, grazie alla s-box e alla permutazione,
fatta ad ogni round (in questo caso 3 che corrisponde proprio al logaritmo in base due di 8 bit grandezza del
messaggio in input), il numero di bit influenzato dell'output cresce esponenzialmente! (garantiamo
l'effetto valanga).
Conclusioni
Queste condizioni sulle SPN sono necessarie ma non sufficienti perché garantiscono che il cifrario eviterà certi
attacchi (ma magari nel futuro ne troveremo uno!).
Le reti di Feistel
Le reti di Feistel sono un modello del tutto simile alle SPN, ma con alcune peculiarità interessanti. Ad ogni
round il sottostante messaggio
viene suddiviso in due sotto-messaggi di egual lunghezza
round avremo che il nuovo messaggio
dove
. Dopo il
sarà tale che:
è una funzione che tipicamente dipende dalla chiave ed è detta Mengler function. In sostanza la
nuova parte sinistra del messaggio diventa la vecchia parte destra e la nuova parte destra diventa lo xor tra la
vecchia parte sinistra e il risultato della Mengler function applicata sempre alla vecchia parte sinistra!
Ovviamente la mengler function deve dipendere da una chiave altrimenti non c'è possibilità di avere una
qualunque forma di sicurezza.
Schema rete di Feistel con 3 round
Dettagli su rete di Feistel
Da notare che la mengler function
agisce sulla metà dei bit dei quali è composto il messaggio e non su
tutti;
Il principale vantaggio di questa rete è che la funzione
di Mengler, può essere non invertibile, infatti si
riesce comunque a risalire all'input (si pensi di avere
ed
Ma se le
e di risalire la rete di Feistel).
sono indicizzate, vuol dire che stiamo applicando funzioni di Mengler diverse? No, la funzione
che applichiamo è sempre la stessa, è solo per far vedere che la funzione dipende dalla chiave e che la
mengler function in round diversi viene applicata a porzioni di chiavi diverse.
La rete di Feistel è utilizzata per fare i round del DES.
Cos'è la Mengler function?
Di base può essere qualunque funzione, ma nei casi concreti (come
singolo round di una
) è qualcosa di molto simile a un
.
DES
è uno tra i cifrari a blocco più importanti, in quanto a utilizzo ma soprattutto da un punto di vista
storico.
In questo cifrario le chiavi sono lunghe
parole
bit, i messaggi sono lunghi
bit e l'output è lungo
bit in altre
può essere visto come:
è strutturato come una rete di Feistel a 16 round, in cui una particolare Mengler function
prende in
input porzioni diverse della chiave a seconda del round. Come sappiamo la mengler function prende in input
32 bit, ossia la metà della lunghezza dell'input ed in particolare:
Per ogni
(round) esiste un sottoinsieme
al calcolo di
dei bit della chiave che contribuiranno
al round ;
Chiamiamo per convenzione tale "mengler Function"
dove è il round, quindi come sempre la
funzione mengler varia in base alla porzione di chiave;
La funzione
ha, a sua volta, una struttura, simile a quella di una
Schema della Mengler function di DES
Fase iniziale
.
La struttura è molto simile a una SPN, c'è sempre la fase di mixing in cui però i 32 bit di input non vengono
messi direttamente in xor con la chiave, ma vengono prima espansi per essere 48 bit "intermedi" e a quel
punto mettiamo questi in xor con una porzione di 48 bit della chiave.
Questi 48 bit intermedi, messi in xor con la chiave, vengono ottenuti replicando alcuni bit tra i 32 bit di input!
Fase s-box
I 48 bit intermedi che si ricavano dal mixing vengono suddivisi in 8 gruppi da 6 bit e dati in pasto alle 8 SBOX. Però, i valori risultati delle S-BOX vengono mappati in 32 bit, ciò significa che le S-BOX sono funzioni
, ossia che da 6 bit di input ne producono solamente 4! Per questo motivo queste S-BOX non sono
invertibili per pura ragione di cardinalità (la funzione non può essere biettiva!).
Fase finale
Sui 32 bit risultanti dalla fase di s-box viene applicata una permutazione.
S-BOX DI DES
Le otto
sono il cuore di
funzione
e vanno certamente definite. In particolare, ciascuna di essere è una
e, come detto, non c'è alcuna speranza che tali funzioni siano invertibili.
C'è però una proprietà interessante garantita dalle s-box: ciascuna possibile configurazione
(4
bit ) è l'immagine di esattamente quattro configurazioni in input. Questo implica che se calcoliamo la controimmagine di qualunque
lungo 4 bit, troviamo esattamente 4 configurazioni di 6 bit. In altre parole se
prendiamo la distribuzione uniforme sulle stringhe di 6 bit, una qualunque delle 4 configurazioni di 4 bit è
equiprobabile.
Altre proprietà di S-BOX:
Le
sono anche progettate per garantire l'effetto valanga, visto che sono le uniche
responsabili. In altre parole, una variazione di un bit dell'output porta sempre, almeno potenzialmente,
una variazione di almeno due bit nell'output.
Inoltre, chi progettò
si preoccupò di rendere le
resistenti alla crittografia differenziale
(che all'epoca non era ancora nota.
N.B. Qualche complottista pensa che le s-box di
abbiano delle backdoor.
Cos'è la crittografia differenziale?
La crittografia differenziale è una tecnica crittanalitica che è a differenza di quelle normali, che si basano sui
valori dei messaggi, si basa sulle differenze dei messaggi! In pratica è possibile attraverso lo studio del
cifrario a blocco su messaggi simili, capire qualcosa della chiave anche se non la determiniamo in maniera
precisa (certi valori delle chiavi sono più probabili di altre).
Sicurezza di DES
Se si considerano versione di
in cui il numero di round è ridotto (fino a 3) attacchi crittanalitici molto
semplici sono possibili (utile per capire le fragilità).
Caso DES 1 round
Se si fa un solo round di
, allora siamo nel caso della mengler function e data una coppia
riusciamo a risalire all'input e all'output di
, e da queste a pochissimi valori per ciascuna porzione della
chiave. Quindi restringiamo lo spazio delle chiavi possibili, ovviamente con una certa probabilità. Come è
possibile questa cosa?
Dall'output
che conosciamo risaliamo al valore dei 32 bit intermedi perché la permutazione che è
nota;
Ma ora ci sono le s-box che non sono invertibili, ma sappiamo che per ciascun valore dell'output di
ciascuna s-box ci sono solo 4 possibili valori per l'input, quindi i possibili valori dei 48 bit intermedi non
sono
ma bensì
.
Ma se conosciamo l'input e conosciamo questi 48 bit intermedi, con lo xor, possiamo ricavare 32 possibili
sotto-chiavi, che sono pochissime!
Se poi abbiamo più coppie
possiamo fare l'intersezione tra le sotto-chiavi trovate in questo modo e
convergere all'unica chiave possibile in maniera molto rapidamente.
Caso DES 2 round
Il modo in cui sono costruite le reti di Feistel rendono molto semplice attaccare
di round è pari a 2. Date le proprietà delle reti di Feistel e dato che la
anche quando il numero
è la mengler function, possiamo fare la
stessa cosa che abbiamo fatto nel primo attacco.
Caso DES 16 round
Se finalmente consideriamo il classico
che sfrutta 16 round, l'attacco più performante da un punto di
vista concreto rimane quello brute-force. Nonostante quest'attacco di key-recovery costi un qualcosa
nell'ordine di
è alla portata dei moderni sistemi HPC (high-performance computing).
Ma questa fragilità è data non dalla struttura di DES, ma dalla lunghezza della chiave!
Conclusione attacchi DES
Le tecniche di crittanalisi lineare (applicazione principi algebra lineare alla crittanalisi) e crittanalisi
differenziale sono entrambe applicabili, ma sono sperimentalmente meno efficienti di quelle brute-force.
Ad esempio la crittanalisi lineare prende un numero di operazioni pari a
che è sicuramente minore del
brute-force sulle chiavi ma il punto è che ogni operazioni richiesta dalla crittanalisi lineare occupa tantissimo
tempo (non è un'operazione elementare) e richiede anche molto spazio di lavoro.
Il problema di
ha risentito della sua età...ma è possibile farlo ringiovanire!
Allunghiamo la chiave di DES per renderlo sicuro
Double Encryption
La double encryption di Des lavora su coppie di chiavi lunghe
bit, e non fa altro che applicare
seconda volta con la seconda chiave, sul risultato dell'applicazione di
la prima chiave:
una
sul messaggio la prima volta non
Questo cifrario, che applica lo stesso cifrario più volte con la stessa lunghezza delle chiavi, è debole ad
attacchi meet-in-the-middle, che hanno complessità nell'ordine di
.
Si supponga di avere a disposizione un certo numero di coppie di testo in chiari, testo cifrato
, dove ognuno di questi è lungo 64 bit. Quello che si può fare è un qualcosa del tipo:
Per ciascuna coppia si applica brute force sulla prima porzione di DES e poi al contrario sulla seconda!
In pratica, determiniamo:
Per ciascun messaggio e per ciascuna chiave, quali siano i possibili crittogrammi che stanno in mezzo.
Sono molti, ma sono nell'ordine di
;
Dall'altra parte, per ciascun crittogramma e per ciascuna chiave, generiamo il messaggio che sta in
mezzo (costo sempre nell'ordine di
).
A questo punto basta fare matching tra i due! Per questo si chiama "meet-in-the-middle".
Queste operazioni consentono di assottigliare l'insieme delle chiavi possibili tra le
In pratica, questo attacco è un classico attacco DES applicato due volte!
in modo drastico.
Vorremo in un cifrario del genere, vorremmo arrivare ad attacchi bruteforce che costino
, ma in
questo modo il costo è molto inferiore.
Domanda interessante
Perché non si estende semplicemente la chiave del
piuttosto che applicare
più volte? Perché
anche solo estendere la chiave e farla diventare a 112 bit, implicherebbe la costruzione di nuove 8
,
e il procedimento è molto costoso (ricordiamo che le s-box in DES sono tutte diverse, quello che cambia in
funzione della chiave è solo la mengler function).
Triple Encryption (triple Des per gli esseri umani)
Si potrebbe passare analogamente alla triple encryption, nella quale si può procedere in due modi diversi:
Applicare DES tre volte con tre chiavi diverse:
Applicare DES tre volte con solo due chiavi diverse:
A discapito delle apparenze, in entrambi i casi la complessità dell'attacco bruteforce è
dato che la chiave è lunga
bit mi aspetto che la complessità sia proprio
. Nel primo caso,
eppure non è così perché
parzialmente possiamo farci l'attacco meet-in-the-middle!
TRIPLE-DES
Ultima variante di DES, in cui
viene applicato tre volte esattamente come per la tripla invocazione. E'
uno standard ma ha un grosso svantaggio: calcolare il DES è molto costoso in termini di tempo, figuriamoci
calcolarlo tre volte!
AES
Nasce per essere il sostituto di
della chiave di
. E' una
bit, oppure
con lunghezza del messaggio pari a
In ogni round, lo stato (che viene modificato) è visto come una matrice
al messaggio (
bit e lunghezza
.
di byte che inizialmente è pari
bit).
Ogni round prevede l'applicazione di quattro trasformazioni:
AddRoundKey: lo stato viene messo in xor con una sotto-chiave lunga 128 bit (sotto-chiave perchè
potrebbe essere anche più lunga di 128);
SubBytes: ogni byte della matrice viene sostituito con il byte ottenuto applicando una S-BOX fissa (c'è
un'unica S-BOX);
ShiftRows: ciascuna riga della matrice viene shiftata verso sinistra di un numero di posizioni variabile.
L'importante è che lo shift di ciascuna riga sia diversa;
MixColumns: a ciascuna colonna della matrice viene applicata una trasformazione lineare (nel senso
dell'algebra booleana) invertibile;
Anche qui l'attacco concreto più performante resta quello brute force che ha complessità
(o 192 o 256 a
seconda della lunghezza della chiave). Questi numeri sono proibitivi!
Costruire funzioni Hash concrete
Da un punto di vista concreto, le funzioni hash possono essere viste come funzioni della forma
. Sono quindi funzioni che concretamente sono parametrizzare sulla lunghezza
dell'output, che imponiamo essere e che agiscono su stringhe binarie di lunghezza illimitata.
Quasi sempre, tali funzioni sono costruite a partire da una funzione di compressione
e applicando a quest'ultima una trasformazione simile a quella di Merkle-Damgard. Quasi tutte le funzioni
hash seguono questo modello.
Da notare che
dovrebbe essere pari a per applicare la trasformazione di Merkle-Damgard, ma comunque
si può allungare
nel caso in cui sia più piccola per poi applicare la trasformazione.
Ma se la trasformazione di Merkle-Damgard è già nota su cosa abbiamo libertà di agire? Il nostro grado di
libertà sta nella definizione della funzione di compressione
Bisogna quindi ora chiedersi: "Come costruire
.
? Esistono dei modelli standard per costruire funzioni di
compressione?". La risposta è affermativa, un primo modo per costruire questa
è la costruzione di Davies-
Meyer.
Costruzione di Davies-Meyer
La primitiva per la costruzione di una funzione hash, come sappiamo è il cifrario a blocco che viene preso
come assunzione.
Definizione
Dato un cifrario a blocco
compressione, a partire da
, un modo naturale per costruire una funzione di
, è quello di definire
Sostanzialmente quello che succede è che la chiave
come segue:
è lunga
il messaggio
è lungo , quindi si passa alla
funzione di compressione la stringa concatenata formata da messaggio e chiave, e questa non fa altro che
richiamare il cifrario a blocco (cioè la funzione pseudocasuale) sul messaggio avendo come chiave
e
mettere il risultato in XOR con il messaggio stesso.
Cosa possiamo dire sulla sicurezza di questo modo di costruire
funzioni di compressione?
Intuitivamente trovare una collisione non è banale, dovremmo trovare due coppia
ed
che diano lo
stesso risultato! Questa cosa non è molto facile, perché dobbiamo trovare sulla stessa chiave una coppia
che su
dia lo stesso risultato, ma sappiamo che
è pseudocasuale! Magari potremmo tentare di
cambiare chiave tenendo la coppia di messaggi fissa, ma peggioreremmo solo la nostra situazione.
Risultato formale sulla sicurezza
Stiamo sfruttando il teorema del compleanno! In particolare, nessun attaccante può fare meglio di un
birthday attack.
Assumere che
sia modellata come un cifrario ideale è una assunzione ancora più forte rispetto che
sia
una permutazione (o funzione) fortemente pseudocasuale.
Questa assunzione ci chiede non solo che
sia indistinguibile da una permutazione pseudocasuale, ma che
la stessa cosa valga quando cambio chiave, quindi non vale solo sui messaggi ma anche sulla chiave,
poiché in questa funzione passo la chiave come parte del messaggio. L'attaccante vede la chiave, anzi, la può
decidere lui, per questo ho bisogno di qualcosa di più forte!
Alcune funzioni hash concrete
Vediamo ora esempi di funzioni hash concrete basate sul modello Devis-Meyer e altre che invece non lo sono.
MD5
In
ha un output lungo 128 bit, ma è non sicura, trovare una collisone richiede pochi minuti. Non solo è
insicura a livello di bruteforce, quindi per lunghezza della chiave, ma anche a livello strutturale!
SHA1 e SHA2
In SHA1 l'output è lungo 160 bit, ma esistono attacchi che richiedono meno di
invocazioni della funzione.
Sono attacchi di tipo statistico.
In SHA2, l'output può essere lungo
particolare
oppure
bit e non soffre quindi degli attacchi di cui soffre
. In
è utilizzato in alcune blockchain.
Tutte queste funzioni sono ottenute tramite la costruzione di Devis-Meyer a partire dai cifrari a blocco
appositamente definiti (per funzioni hash).
SHA3
Similmente a quello che successe con AES e DES, la NIST selezionò una nuova funzione hash nel 2012, che
prese il nome
.
supporta output di lunghezza pari a
e
bit ma è costruita attorno a principi radicalmente
diversi dalle costruzioni precedentemente incontrate. Ma da un certo punto di vista è anche più vulnerabile,
proprio perché è costruito attorno a nuovi principi (essendo nuovi non abbiamo garanzie sulla sicurezza).
6- Costruire oggetti pseudocasuali e funzioni
hash in astratto
Il titolo di questo capitolo è forviante, infatti per le funzioni hash non è possibile dare delle costruzioni in
astratto di fatto, infatti non è possibili a partire da delle assunzioni primitive per costruire delle funzioni hash.
Tuttavia riusciremo a costruire dei generatori pseudocasuali!
Come anticipato, gli oggetti pseudocasuali e le funzioni hash si possono costruire:
In concreto, come abbiamo fatto nel capitolo precedente, trovando alcune limitazioni (non c'è alcuna
garanzia sulla sicurezza futura);
In astratto, come faremo in questo capitolo, ma limitatamente agli oggetti pseudocasuali, perché come
detto non c'è modo di costruire funzioni hash in astratto.
Nell'approccio astratto, quello che faremo sarà dimostrare che gli oggetti pseudocasuali si possono costruire
a partire da altri oggetti la cui esistenza, sebbene non certa, è ritenuta molto probabile.
Infatti i teoremi definiti in questo capitolo saranno tutti nella forma "data la sicurezza di qualcosa, allora
costruisco un'altra cosa che è sicura". Effettuare costruzioni di questo genere non è difficile ma sono molto
difficili da dimostrare sicure date le ipotesi (infatti non lo facciamo yeahhh)!
Note su questo capitolo
Vedremo che la Crittografia, la pseudocasualità e in parte la complessità computazionale si occupano della
non esistenza di certi algoritmi polytime con certe caratteristiche (pensiamo all'avversario)!
Tutto il lavoro che abbiamo fatto nella prima parte del corso, e che continueremo a fare ora, consiste nel far
discendere tale non esistenza da ipotesi via via più deboli. Ossia partiremo dalla non esistenza di algoritmi
che forzano i cifrari alla non esistenza di algoritmi efficienti che riescano a risolvere problemi difficili (e
utilizziamo questo problema difficile come ipotesi per costruire il cifrario).
Come detto in precedenza, queste costruzioni sono sicure a livello teorico e ci danno molte garanzie di
sicurezza....ma vanno bene solo dal punto di vista teorico! Le costruzioni che forniremo oggi con
l'approccio astratto sono, infatti, estremamente inefficienti se implementate, molto meno efficienti rispetto
a quelli visti nell'approccio concreto (si pensi al cifrario di flusso trivium e si pensi ad un cifrario di flusso che
per tirare fuori dei bit debba calcolare, ad esempio, la fattorizzazione per un certo intero).
La situazione, per sommi capi
Fino ad ora, abbiamo dimostrare l'esistenza di cifrari, basandoci sull'esistenza di generatori pseudocasuali,
funzioni pseudocasuali e funzioni hash (la freccia che va da Mac a Mac è relativa all'hash-and-mac che sfrutta
un mac e una funzione hash per generare un nuovo mac sicuro).
Quello che faremo in questo capitolo sarà dimostrare quello che c'è a sinistra, ossia quegli oggetti che
possiamo utilizzare per dimostrare l'esistenza di generatori pseudocasuali, funzioni pseudocasuali e
funzioni hash (anche se sappiamo che per queste ultime non c'è speranza in quanto possono essere
implementate solo in maniera concreta)!
Cosa mettiamo in questa question box? Le funzioni one-way.
Funzioni one-way
Informalmente, una funzione one-way (a senso unico) è una funzione che è facile da calcolare ma difficile
da invertire. Come formalizzare quest'idea? Procederemo con un'esperimento:
L'esperimento procede come segue:
Si genera una stringa binaria
Si applica la funzione
lunga
a questa stringa generando una nuova stringa ;
Si passa all'avversario la stringa
una stringa
(parametro di sicurezza);
e il parametro di sicurezza. A questo punto l'avversario deve generare
tale per cui se gli applico la funzione
questa deve restituire y. L'avversario vince
unicamente in quel caso!
Da notare che
non debba essere per forza , potrebbe essere semplicemente una collisione per la
funzione !
Per difficile da invertire, infatti, intendiamo che ci sia un avversario che cerchi di invertire la funzione
(cioè di risalire all'input o comunque a un oggetto
tale per cui applicato alla funzione restituisca sempre lo
stesso output, avendo a disposizione il parametro di sicurezza e l'output della funzione one-way) abbia
probabilità di successo trascurabile.
Definizione formale
La probabilità di successo dall'avversari trascurabile è giustificata dal fatto che l'avversario non ha accesso ad
un oracolo, e quindi l'unica cosa che "dovrebbe poter fare" è tirare caso!
Permutazioni one-way
Ci interessano delle particolari funzioni one-way dette permutazione one-way che sono funzioni che
preservano la lunghezza e con l'interessante proprietà che
determina univocamente
tale che
.
Quindi le permutazioni one-way garantiscono sia l'invertibilità (sono funzioni biettive) che la
preservazione della lunghezza. E' ovvio che debbano garantire la preservazione della lunghezza perché
altrimenti non potremmo garantire l'invertibilità (pensiamo alle funzioni hash come contro-esempio).
Esempi di presunte funzioni one-way
Presunte perché come abbiamo detto non si è certi dell'esistenza.
Moltiplicazione tra numeri naturali
Consideriamo
definita ponendo
: date due stringhe che interpretiamo come
numeri naturali, restituiamo il loro prodotto.
Tuttavia ci potrebbe essere qualcosa che non ci piace: le funzioni one-way sono definite su un'unica stringa e
non su due! Sappiamo però che due stringhe binarie possono essere codificate in una sola stringa quindi
tutto ok.
Se non mettiamo altri vincoli su
e
la funzione
è facilmente invertibile (facendo uso della
fattorizzazione che è un problema difficile). Ma bisogna stare attenti, perché in realtà, così definita, questa
funzione non è one-way!
Infatti, se non mettiamo nessun vincolo di non trivialità sulle stringhe da restituire quando invertiamo,
potremmo sempre restituire una coppia
pari a (1,
Mettiamo allora un vincolo e imponiamo che
) e l'avversario vincerebbe sempre!
non siano triviali ma che abbiano la stessa
lunghezza! Anche in questo caso la funzione non è one-way, infatti qual'è la probabilità che una di queste due
stringhe sia pari? E' molto alta (circa
) , ma a quel punto, fattorizzando, otterremmo un numero piccolo e
uno molto grande, ma quello piccolo non sarà triviale!
Bisogna quindi stare attenti alle lunghezze, alla non trivialità, ai casi dei numeri pari etc...
Subset-Sum Problem
Consideriamo la funzione
definita come
interpretata come un sottoinsieme di
Questa funzione prende in input
, dove
stringhe ciascuna di lunghezza pari ad
interpretata come un sotto-insieme (anch'essa è una stringa lunga
questo somma, risalire a
è
e poi un ulteriore stringa
bit), che rappresenta o no la
presenza o meno di un numero nel sotto-insieme. Quindi l'input totale è lungo
La funzione restituisce le prime
e
.
stringe e poi la somma su alcune stringhe lunghe
bit.
segnalate da . Da
è difficile! Si può fare bruteforce provando tutti i possibili sotto-insiemi di
, e trovare quello buono, ma questa roba prende tempo esponenziale.
Per invertire la funzione subset-sum problem consiste nel risolvere il problema dello zaino.
Dove stiamo andando?
Vogliamo collegare la funzioni one-way alla pseudocasualità e per fare questo sono necessari gli oggetti
vengono definiti come Predicati hard-core!
Predicati hard-core
Dalle funzioni one-way stiamo andando verso la pseudocasualità, tuttavia, per loro natura, le funzione oneway non sono sufficienti, infatti queste parlano di invertibilità e non della indistinguibilità tra una stringa
casuale e una pseudocasuale.
La chiave di volta è osservare che una funzione one-way
è tale per cui da
non possiamo risalire a
nella sua interezza! Ciò non implica che la stessa cosa valga per parti di , per esempio un singolo bit!
Però, possiamo costruire una funzione
uguale ma chiama
one-way, che divide l'input in due parti e restituisce la prima parte
sulla seconda parte, dove
è una funzione one-way:
.
E' facile dimostrare che se
Infatti se da
è one-way perché se riusciamo ad invertire
sono riuscito ad ottenere una stringa
trovato il modo di invertire
Ma questa
tale per cui
(un qualunque avversario per
, questo implica che ho
lo è anche per ).
rivela una parte importante della stringa in input, cioè
poniamo però una domanda: ma se volessimo utilizzare
riusciremmo ad invertire anche .
(non
perché viene "oscurata" da ). Ci
solo per ricavare un bit, utilizzando magari gli ultimi
bit presi in considerazione da ? Non va lo stesso bene perché
, come detto, non garantisce nulla sul fatto
che l'output non dica qualcosa di alcune parti dell'input!
Definizione predicato hard-core
Per ovviare al fatto che
non garantisca nulla sul fatto che l'output non dica qualcosa di alcune parti
dell'input, utilizziamo i predicati hard-core.
Il predicato hardcore è un predicato e non una funzione! O meglio, è una funzione il cui il dominio sono i bit
non stringhe di bit. Questo predicato deve essere calcolato in tempo polinomiale, ma ci dice qualcosa di
importante: l'avversario
che è sempre
a partire da
non riesce a costruire
se non con
probabilità molto bassa.
Quindi
partire da
è una qualche proprietà di x che è facilmente calcolabile ma difficilmente ricostruibile a
. L'avversario quindi può dire qualcosa di
ma non riesce a ricavare la proprietà in
La probabilità di vittoria dell'avversario non deve essere superiore a
.
(perché l'avversario deve indovinare un
bit, quindi può tirare a caso) più un qualcosa che è trascurabile!
N.B. Vedremo che non esiste un predicato hard-core per ogni funzione one-way, ma possiamo costruirne una
nuova, basandoci su quella presa in considerazione, che invece lo ha!
come predicato hard-core universale?
Per come abbiamo definito i predicati hard-core, potrebbe sembrare che
sia predicato hard-core per ogni funzione
per forza tirare a caso
definita come
(infatti sembra che l'avversario debba
poiché lo xor dell'input potrebbe non essere nell'output!).
Tuttavia è possibile costruire una funzione , one-way per la quale questo non è vero! Infatti, data
la funzione
definita ponendo
è anch'essa one-way, ma certamente
per essa, perché il suo valore è facilmente ricostruibile dall'output. Infatti ora,
quindi basta prendere l'ultimo bit!
Hard-core non triviali per funzioni non one-way
one-way,
non è hard-core
è nell'output di
e
E' possibile costruire predicati hard-core triviali per alcune funzioni non one-way! Ad esempio la funzione
definita ponendo:
: Sulla stringa vuota, la funzione restituisce la stringa vuota
e
per ogni
e
: su una stringa non vuota butto via il primo bit.
Sicuramente questa funzione non è one-way, perché a partire da
riesco a ricostruire
, con probabilità
perché devo ricostruire .
Essendo quindi che
hard-core, perché
non dipende dal primo bit di , posso trasformare questo primo bit nel predicato
lo butta via ed è quindi impossibili ricostruire questo bit se non tirando a caso!
L'avversario non può, quindi, far altro che tirare a caso, ma dato che tira a caso la probabilità di vittoria è di
e quindi siamo apposto.
Teorema di Goldreich-Levin
E' uno dei risultati più importanti della teoria della funzioni one-way, con cadute cruciali in crittografia. L'unico
prezzo da pagare è quello di dover costruire una nuova funzione a partire da una esistente, tuttavia ci va bene
pagare questo scotto!
Come si costruisce questa fantomatica funzione
applico la
? Si costruisce da
solo alla prima parte dell'input che è , e definisco
ponendo
come
, ossia
, quindi il
predicato hard-core è definito come lo xor di alcuni i bit di x moltiplicati per alcuni bit di r. Questo alcuni
è definito da , ossia si moltiplicano quei bit in cui
vale 1! E' una sorta di xor selettivo.
Questa definizione è molto semplice, ma la dimostrazione è molto complessa!
Dalle permutazione one-way ai generatori pseudocasuali
Finalmente costruiamo un generatore pseudocasuale a partire da una funzione one-way.
In pratica
funzione
restituisce i primi
bit dell'output usando la funzione one-way e l'ultimo bit utilizzando la
.
Come sappiamo i primi
bit dell'output di G sono pseudocasuali a causa della proprietà di , mentre l'
esimo bit lo è a causa della proprietà di
risalire all'
impossibile).
. Se non fosse così, e dai primi
-esimo bit dell'output, vorrebbe dire che
bit dell'output (
-
) riuscissimo a
non è una stringa pseudocasuale (cosa
In pratica, il predicato hard-core, viene utilizzato per definire un ulteriore bit nell'output, altrimenti il nostro
generatore
non sarebbe interessante, in quanto non espanderebbe la stringa in input!
Abbiamo quindi inserito qualcosa nella nostra question box iniziale!
Tuttavia questo fattore di espansione è pari a
per stringhe lunghe
(cosa non buona, siamo
praticamente in one-time pad), dobbiamo risolvere il problema consentendo un fattore di espansione
arbitrario!
Fattore di espansione arbitrario per generatori
pseudocasuali
L'idea è di applicare tante volte
, fino a che non raggiungiamo la lunghezza desiderata (rimanendo sempre in
tempi polinomiali).
Dai generatori pseudocasuali alle funzioni pseudocasuali
La costruzione è molto interessante, come sappiamo nelle funzioni pseudocasuali ho due input (chiave e
input della funzione) e un output.
Questa costruzioni prende una chiave
stringa viene divisa in due parti lunghe
in input a
e la seconda sempre a
lunga , ci applica
e continuo così, per
dell'albero corrisponde al valore di
il quale produce una stringa lunga
(la prima etichettata con
, questa
la seconda con ) e una parte viene data
volte ottenendo
foglie dell'albero e ogni foglia
applicato ad una qualche stringa.
Costruire questo albero tuttavia ha costo esponenziale perché il numero dei nodi di questo albero è
esponenziale! Ma fortunatamente per il calcolo di
su
basta un unico ramo di questo albero, conoscendo
anche la chiave ovviamente! Riesco a scegliere il ramo giusto facendomi guidare da
ovviamente!
Chiudiamo il cerchio
Siamo quindi arrivati ad aggiungere qualcosa al nostro schema:
e dalle etichette
Conclusioni
La parte sinistra del quadro ha una valenza solo teorica, infatti, come abbiamo visto si parte da altre ipotesi
per costruire concretamente i cifrari e i mac.
C'è tuttavia chi lavora per cercare di migliorare l'efficienza delle costruzioni fatte a partire da questo approccio
astratto, ma non siamo ancora arrivati a un punto di svolta.
Algebra, Teoria dei numeri e Relative
assunzioni
Vedremo cose giustificabili dal punto di vista teorico con il capitolo sulle costruzioni in astratto, eravamo
rimasti qui:
abbiamo nozioni che hanno valenza solo teorica e altre che hanno, invece, una valenza pratica (costruzione
concreta di cifrari sicuri contro attacchi passivi).
In questo grafo "aciclico" c'è una sorgente che non abbiamo esplorato e sono le permutazioni one-way!
Faremo delle assunzioni matematiche che ci permetteranno di costruire funzioni one-way e quindi
permutazioni one-way. Siamo ancora nel contesto della chiave privata, mentre vedremo che nel
contesto a chiave pubblica c'è uno shortcut che va dalle assunzioni direttamente ai cifrari (Es. non useremo la
pseudocasualità come qualcosa che sta in mezzo tra le assunzioni e i cifrari).
Costruire funzioni one-way
Abbiamo dato solo qualche esempio di funzione “presunta” one-way, senza discuterne più in dettaglio la
natura.
Le funzioni one-way più interessanti sono senza dubbio quelle di natura "matematica", in particolare quelle
che vengono dalla teoria dei numeri e dall'algebra!
Faremo un ripasso veloce delle necessarie nozioni di algebra e teoria dei numeri, man mano che tali
nozioni ci serviranno.
Le assunzioni che vedremo troveranno applicazione diretta nella crittografia a chiave pubblica, di cui
parleremo successivamente.
Divisori, numeri primi, etc...
L'insieme
è l'insieme dei numeri interi, mentre
Dati due elementi
Quando
e
, scriviamo
è positivo, diciamo che
è l'insieme dei numeri naturali.
(a divide b) quando esiste
è
.
di , mentre quando
(ossia non è nè
nè ) è
detto fattore di .
Numeri primi e resto della divisione
Un numero
Dati
con
dove
è detto
se non ha fattori, in caso contrario si chiama composto.
indichiamo con
il resto della divisione di
per
.
Lemma numeri primi tra loro
Numeri primi e Fattorizzazione
Dato un numero naturale
, ci chiediamo se sia difficile determinare due interi
tali che
(fattorizzazione).
Esistono algoritmi che prendono tempo
La ricerca bruteforce dei divisori di
, che sono esponenziali nella lunghezza di
costa proprio
(non serve cercare tra tutti i numeri fino ad
:
, ci
basta fermarci prima);
Per ognuno di questi numeri, bisogna fare una divisione col resto per capire se effettivamente questo è
un divisore di
, e questa operazione costa
. Questo costo è giustifica to dal fatto che tutte le
operazioni aritmetiche di base sono implementabili con logaritmi.
Ma allora il tutto è polinomiale? No, perché la lunghezza dell'input non è
, ma il
in quanto
codificato in binario! Quindi l'operazione di divisione è polinomiale nell'input logaritmico ma l'operazione
di bruteforce precedente non è lineare nell'input (la radice di un logaritmo cresce molto più velocemente
di un poli logaritmo nel logaritmo ).
Questo è un problema che non ha nulla a che fare con la crittografia, ma trova utilità per essere usato come
assunzione (assumiamo il problema della fattorizzazione come difficile). Definiamo quindi, il seguente
esperimento:
Costruiamo
come una coppia di qualunque di naturali che abbiano la stessa lunghezza pari a
il parametro di sicurezza;
Calcoliamo il prodotto e lo inviamo all'avversario;
che è
Se l'avversario riesce a risalire ai fattori del numero allora ha vinto altrimenti no. Già da qua si vedere che
abbiamo sbagliato qualcosa nella definizione (non abbiamo dato vincoli sulla forma di
all'avversario)
ma ne riparleremo.
Per ora, la nostra assunzione potrebbe stare nel fatto che per ogni avversario
che sia
esiste
tale che:
Problema nell'assunzione
L'assunzione che abbiamo appena dato però, semplicemente non vale! Infatti, con probabilità pari a
numero
sarà pari, perchè basta che uno tra
e
lo sia perchè
, il
sesso lo sia...fattorizzare un numero
pari è semplicissimo!
Quindi c'è sicuramente un problema nella generazione di
alcun vincolo sulla lunghezza delle stringhe
, sono troppo triviali, in più non abbiamo dato
generate dall'avversario (potrebbe restituire anche
semplicemente il prodotto tra 1 ed N ad esempio)!
Innanzitutto occorre che
non sia (in media) banalmente fattorizzabile:
Un'idea interessante, è quella di modificare
(rappresentabili in
in modo che
e
siano sempre e solo
bit).
Ma come facciamo a generare i numeri primi in modo casuale (e in maniera efficiente polytime)?
Che probabilità avrà un certo numero primo
di essere generato? Ovviamente non vogliamo generare
sempre lo stesso numero primo!
Generare numeri primi in modo efficiente
Un modo plausibili per generare numeri primi rappresentabili in
Generiamo una stringa binaria casuale lunga
Poi ci mettiamo un
esattamente
bit è andare per tentativi:
;
in testa. Facciamo così perché, in questo modo, siamo sicuri che la stringa sia lunga
cifre binarie (se ci avessi messo 0 ad esempio sarebbe rimasta lunga
A quel punto controlliamo se
);
è primo, se non lo è si ricomincia.
Il numero di tentativi possibili per generare un numero primo è pari a .
Dobbiamo quindi valorizzare il parametro in modo che le cose funzionino:
è un polinomio in , per ragioni di efficienza (vogliamo rimanere polytime);
Vorremmo inoltre, che per tale valore di , la probabilità di ottenere
Come testare la primalità di un numero? Vorremmo poter testare se
sia trascurabile in ;
è primo in tempo polinomiale!
Come valorizzare t (teoria dei numeri)
Quello che segue è un teorema sulla numerosità dei numeri primi in
Quindi, il numero di primi rappresentabili in
fondamentalmente esponenziale, il fattore
bit è almeno pari a
bit:
. Questo oggetto è una
al denominatore scassa un po' la minchia per ovvi motivi,
tuttavia un fattore polinomiale al denominatore scassa ma solo fino a un certo punto (è una suocera
trascurabile ).
Possiamo quindi determinare, ad ogni iterata la probabilità che
sia effettivamente primo, che sarà pari a:
Questo è giustificato dal fatto che i numeri rappresentabili in esattamente
come abbiamo definito l'esperimento) sono esattamente
Semplificato, il tutto è pari a
, quindi al crescere di
bit a cui però tolgo il primo (per
.
la probabilità di ottenere un primo in una iterata
diminuisce, ma non troppo velocemente, infatti questa probabilità non è affatto trascurabile!
Di conseguenza, se
la probabilità di ottenere
sarà al più pari a:
= probabilità di ottenere un numero non primo, la moltiplichiamo per le iterate e otteniamo la
probabilità di non ottenere un numero primo nell'esperimento.
Se
, che possiamo scrivere con
elevato a , otteniamo un oggetto noto e possiamo usare una
formula per determinare che la probabilità di tirare fuori un numero non primo è pari a
(molto bassa).
Dobbiamo ora capire come testare la primalità di un numero!
Come testare la primalità di un numero in tempo
polinomiale
Esistono algoritmi deterministici per il test della primalità che prendono tempo polinomiale, come
l'algoritmo
, tuttavia il grado del polinomio è molto alto (tipo 11) e non può essere considerato da un
punto di vista pratico.
C'è però il test di Miller-Rabin che è invece probabilistico ed è
:
Se l'input è un numero primo, il test di Miller-Rabin restituisce
Se l'input
con probabilità ;
invece è composto, il testi di Miller-Rabin restituisce
dove
con probabilità
,
;
Quindi c'è qualche possibilità che l'algoritmo restituisca una risposta sbagliata, ma è molto bassa
(trascurabile);
L'assunzione, opportunamente formalizzata
L’assunzione che stiamo cercando di definire sarà parametrizzata su un algoritmo, chiamato
che, su input
L'esperimento
, produce in output una tripla
e
siano primi lunghi
bits.
opportunamente modificato è il seguente:
Possiamo ora dire che la
ogni algoritmo
dove
che sia
è un problema difficile rispetto a
esiste una funzione trascurabile
se e solo se per
tale che:
Quest'assunzione è sufficiente a derivare una funzione one-way, ma non a dimostrare la sicurezza di
schemi a chiave pubblica.
Ovviamente bisogna dare per scontato che
e
altrimenti potremmo restituire la coppia
siano numeri primi nell'esperimento quelli restituiti da
,
.
Teoria dei gruppi (Per derivare assunzione RSA)
Un gruppo è una struttura algebrica
associativa, con identità
(insieme,operazione) dove
e in cui ogni
gruppo è un elemento che messo di fronte a
Per esempio
abbia un inverso
è un'operazione binaria che sia
(un inverso di un elemento
di un
restituisce l'identità del gruppo).
con l'operatore somma è un gruppo, ma anche
con la somma. Contro-esempio è l'insieme
con la moltiplicazione, che non è un gruppo perché c'è un elemento che non ha un inverso che è .
Gruppi finiti ed operazioni binarie
A noi interessano, in particolare, i gruppi finiti in cui l'insieme
.
Un gruppo finito
è detto avere ordine pari a
Un gruppo si dice abeliano se
(cardinalità di
).
è un'operazione commutativa.
L'operazione binaria è spesso indicata:
Con il simbolo addizione
. In tal caso il gruppo si chiama additivo, e se
si può usare la notazione
Con il simbolo di moltiplicazione . In tal caso, il gruppo si chiama moltiplicativo e se
si può
scrivere:
Esponenziazione o elevamento a potenza
Il calcolo di
logaritmico in
o
può essere eseguito tramite un numero di operazioni polinomiale in
(poiché la lunghezza di
codificata in binario è logaritmica come sappiamo).
In particolare possiamo riscrivere un esponente
e ciascuno dei fattori, che sono al più
N.B
, ossia
identificando la sua forma binaria:
, può essere calcolato in tempo lineare in
dove
.
.
Identità e gruppo
è l'identità del gruppo, in pratica stiamo dicendo che per ogni
gruppo tale per cui ricaviamo l'identità (ovviamente
esiste l'esponenziazione di
all'ordine del
).
L'identità è un elemento di base (se vogliamo il più piccolo dell'insieme) che se messo in relazione con un
elemento restituisce proprio quell'elemento.
Ricaviamo il seguente corollario dal teorema precedente:
Stiamo dicendo in pratica, che anche se eleviamo con un qualsiasi un qualsiasi elemento
alla fine il
risultato sarà sempre tra gli elementi del gruppo poiché l'operazione di esponenziazione è col modulo
(non possiamo mai uscire dal gruppo)!
N.B Mentre
che è sempre un naturale
potrebbe anche non esserlo: L'elemento di un gruppo può anche
avere natura non numerica!
Esempi di gruppi finiti
Primo insieme
Prendiamo un segmento iniziale finito dei numeri naturali
. Questo
gruppo se la sottostante operazione è l'addizione modulo
(se non avessimo la
, ovvero la mappa che a
può essere definito
fa corrispondere
usciremmo dall'insieme e non andrebbe bene).
In questo gruppo:
L'identità è ;
L'inverso di
è
che esiste sempre;
Abbiamo l'associatività ereditata dall'operazione somma.
Secondo insieme
Consideriamo sempre
ma questa volta con l'operazione della moltiplicazione modulo
. Per rendere
questo un gruppo dobbiamo:
Eliminare lo
che non è invertibile dal gruppo;
L'identità diventa ;
Abbiamo l'associatività ereditata dall'operazione moltiplicazione.
è primo. Ciò garantisce che ogni
Prendiamo
sia invertibile modulo
, allora esiste il modo per moltiplicare
.
con un verto valore, in modo che modulo 7
restituisca 1. In questo caso il numero in questione è proprio 3:
.
Questo gruppo per i nostri scopi non va bene: vorremmo un gruppo numerico moltiplicativo nella forma
appena visto, in cui però
non possa essere numero primo...ma come garantiremmo l'invertibilità?
Terzo insieme
Se consideriamo
con
composto (che non garantisce di per sè l'invertibilità), c'è un modo per rendere
questo insieme un gruppo rispetto alla moltiplicazione modulo
: basta considerare
definito come
.
In questo nuovo insieme, abbiamo tutti gli elementi di
a 1, ossia tutti i numeri che sono primi con
Prendiamo come esempio
.
Infatti il massimo comun divisore tra
e
Con
che abbiamo il massimo comun divisore con
nell'insieme!
è
e quindi non lo mettiamo, stessa cosa per
piccoli la cardinalità di questo insieme è piccola ma con
grandi, la cardinalità cresce e anche
abbastanza velocemente.
La cosa interessante è che l'inverso di
è
stesso e stessa cosa per , infatti:
.
N.B. Tutti i gruppi visti sono ovviamente abelliani.
.
pari
Esempio gruppo non numerico
L'insieme delle permutazioni
tale che
.
è
Cosa ci garantisce che esista un elemento identità? L'identità è la permutazione stessa.
Cosa ci garantisce che esista l'inverso di una permutazione? Il fatto che la permutazione è una funzione
biettiva e quindi invertibile!
La cardinalità dell'insieme è molto grande al crescere di
:
Sulla cardinalità di
La funzione che ad ogni naturale
con
associa la cardinalità
è detta
ed è indicata
:
Questa funzione non è triviale, certo avrà come lower bound
e come upper bound
in senso stretto (
), ma possiamo dire qualcosa in più?
Se
è un numero primo , allora
ovvero
Se
perché ogni
compreso tra
e
è primo con ,
;
è il prodotto di due primi
oppure quando
, allora
, precisamente quando
(p è divisore di a)
(q è divisore di a). Quindi:
Cerchiamo di capire il tutto:
Possiamo eliminare tutti quegli elementi per cui
e
sono divisori che sono rispettivamente
e
. Potremmo chiederci se ci sono degli elementi in comuni tra quelli che abbiamo scartato, ma la
cosa è impossibile poiché sia
che
sono primi (l'unico in comune sarebbe
ma questo non fa parte
dell'insieme come sappiamo);
Questi sono gli unici elementi da togliere e infatti sottraiamo a
proprio queste cifre. Per
trasformazioni matematiche molto semplici otteniamo che la cardinalità di questo insieme è proprio
.
Gli elementi che restano fuori non sono molti
.
Classi resto ed elevamento a Potenza
Questo teorema parla di funzioni
costruite a partire dall'esponenziazione. Facciamo una cosa non
standard: indicizziamo la funzione con l'esponente, quindi non stiamo proprio esponenziando, piuttosto
stiamo calcolando dei monomi se vogliamo (polinomio ad un fattore). Infatti
l'argomento,
( è
fa parte della funzione).
Di questa funzione ci chiediamo se è una permutazione o no, ossia se al crescere di
riusciamo ad ottenere
tutti gli elementi del codominio. L'idea è che se è vero che il massimo comun divisore tra la cardinalità
dell'insieme
ed
è proprio pari a 1, allora
è una permutazione ed è quindi biettiva.
Perché ci interessano le permutazioni? Perché sono quello che usiamo nelle nostre assunzioni (sarà la cosa
difficile da invertire in
).
Postilla del teorema: se
è l'inverso di , ossia
allora
è l'inversa di
.
Osservazioni teorema
Osserviamo come dati
ed , il valore di
sia efficientemente calcolabile a partire da
(basta
applicare l'algoritmo di esponenziazione);
Osserviamo anche che dati
ed
, l'inverso
di
modulo
sia efficientemente calcolabile.
Questa cosa vale poiché l'inversione modulo di un qualunque intero è un problema trattabile (in
particolare passeremo per l'esponenziazione).
Infine, osserviamo come dato
prodotto di due primi
il valore di
calcolabile. Lo sarebbe se si potesse (efficientemente) fattorizzare
non sia invece facilmente
(basterebbe calcolare
).
C'è una difficoltà nella scelta di , perché deve soddisfare questa condizione, il dominio di
naturale maggiore di
è un qualunque
per principio ma non ci interessano troppo grandi, perché ad esempio se
è più
grande della cardinalità del gruppo ritorniamo dentro con il modulo quindi tanto vale prenderlo uguale o più
piccolo.
Assunzione RSA
Nella cosiddetta Assunzione
quando
quello che facciamo è predicare la difficoltà di invertire
è il prodotto di due primi
e
e
:
.
A differenza dell'assunzione sulla fattorizzazione, però, il problema che stiamo assumendo difficile diventa
facile (c'è una sorta di backdoor) se:
oltre a
si conoscono anche
e ;
Oppure se si conosce un inverso
L'assunzione
Un naturale
di
in modulo
(poiché potremmo ricavare ) ;
è parametrizzata su di una routine
che sia prodotto di due primi
Un naturale
tale che
Un naturale
tale che
e
che, dato un input
con
ritorna:
;
;
.
Esperimento
L'esperimento
è parametrizzato su un avversario e sulla routine
:
Nell'esperimento:
Generiamo la tripla
dove
ed
Si prende un elemento casuale dall'insieme
passiamo
e
due esponenti con le proprietà viste prima;
e lo si passa all'avversario assieme ad
ed
(ma non
per ovvie ragioni) chiedendogli di costruire un ;
L'avversario vince quando
Diciamo che il problema
!
è difficile rispetto a
sse per ogni avversario
che sia
esiste
tale che:
Come abbiamo già avuto modo di argomentare, se
e quindi a calcolare , invertendo di conseguenza
riuscisse a fattorizzare
, riuscirebbe a calcolare
.
Stiamo dicendo tra le righe che quando un avversario riesce a fattorizzare
vince contro l'assunzione
(che quindi è un'assunzione più forte).
Come Costruire
Vorremmo che
?
sia efficiente calcolabile ma come fare per costruirlo? Potremmo passare per
e procedere come segue:
è un algoritmo che genera una tripla
Calcoliamo
che ora si chiama
dove
Tra tutti gli elementi del gruppo ne scegliamo uno, chiamato
A questo punto calcoliamo l'inversa di
sono primi ed
e riusciamo a farlo poiché conosciamo
;
;
tale che
;
(sappiamo che non è difficile) modulo
;
Tutte queste operazioni sono assumibili essere eseguite in tempo polinomiale, supponendo che
sia polinomiale.
Quello che potrebbe sembrare non polinomiale è l'operazione di scelta di . In particolare campionare
l'insieme
trovando quel giusto valore tale che venga rispettato
sembrare polinomiale...tuttavia non è affatto così! Infatti, gli elementi di
potrebbe non
per cui non vale
non sono molti! In particolare, basta prendere i primi valori (quelli vicini a 1) sono quelli per
cui vale la proprietà di interesse. Spesso questo
non viene nemmeno scelto probabilissimamente (come
nell'algoritmo), si potrebbe prendere in maniera fissa un
molto piccolo.
Come postilla abbiamo che Il campionamento ha una probabilità di successo pari a quella di generare dei
numeri primi (che sappiamo avere una buona probabilità di successo).
Abbiamo già argomentato che l'assunzione RSA implica l'assunzione sulla fattorizzazione che è vero poiché se
riesce a fattorizzare allora riusciamo a forzare RSA.
Gruppi ciclici
Consideriamo un gruppo moltiplicativo finito
Ho creato l'insieme di tutte le potenze di
, un suo elemento
elemento di
. A prima vista questo insieme sembra infinito, ma
essendo che questo insieme è sotto-insieme di
, se
in questo insieme
stesso che è
abbiamo l'identità
,
e costruiamo:
è FINITO anche questo insieme lo sarà. In particolare,
e poi tutte le potenze non triviali.
Ripetizione degli elementi di
Per un teorema visto nelle precedenti sezioni, sappiamo che nella successione di
arriveremo a
tale per cui
, per cui possiamo scrivere che
che le cose si ripeteranno poiché
Ma
prima o poi
, se ben ricordiamo è l'ordine del gruppo! (d'altronde questo gruppo
tuttavia niente ci impedisce di pensare che in
Ad esempio, ci potrebbe essere un
molto più piccolo del gruppo (infatti
. Ma allora vuol dire
e così via...
arriva esattamente a
),
ci possano essere delle ripetizioni!
tale che
e per ovvie ragioni
che però è
contiene al più elementi).
Supponendo che questo sia il più piccolo valore tale che
la cardinalità di
può essere diversa
da (ci sono altre ripetizioni)? Ovviamente no!
Supponiamo che
e supponiamo che
:
Infatti se ci fossero due elementi uguali, potremmo prendere il prodotto tra il primo e l'inverso del secondo,
che corrispondono all'identità, ma questo non è possibile perché abbiamo per ipotesi che sia il più piccolo
elemento tale per cui
.
Ordine di
L'ordine di
la cardinalità di
(ordine di un elemento, non di un gruppo!) è il più piccolo naturale tale che
.
Tutto quello che abbiamo detto finora vale per ogni gruppo finito!
, ossia
Es. nel gruppo
devo sommare
, se prendo
ben
pari a , il mio insieme
conterrà tutti gli elementi di
volte per ritornare all'identità, mentre se prendessi
somme sostanzialmente e la cardinalità di
pari a
poiché
mi basta fare due
sarebbe pari a .
Questo è per introdurre la prossima nozione.
Gruppo ciclico
Un gruppo finito
si dice ciclico se esiste un
con
. Un tale
si dice generatore di
.
Definizione gruppo ciclico
Un gruppo finito
uno elementi di
Es.
si dice ciclico se esiste
. Si dicono ciclici perché sono generati da
.
è ciclico per
Un tale
con
.
si dice generatore di
, per ovvie ragioni.
Gruppi ciclici e Ordine
(i divide m).
Non a caso in
se scegliessi
l'insieme
avrebbe ordine
Questo lemma tra le righe cosa suggerisce? Se l'ordine
ed
e non a caso 2 divide 8!
è primo, quali sono gli interi che dividono l'ordine?
stesso.
Corollario non scritto: nei gruppi finiti in cui
Se non valesse il lemma (se non divide
Esce fuori che
è primo, tutti gli elementi, diversi dall'identità, generano
), per assurdo , avremmo che
con
è uguale all'identità, ma questo è impossibile perché solo
ed
.
. Ma quindi:
dovevano essere l'identità
e in più è più piccolo di e questo vorrebbe dire che non era l'elemento più piccolo per cui vale
.
Teorema sui gruppi ciclici
Se
ha ordine primo, allora
è ciclico e ogni
con
genera
.
Questi gruppi ciclici sono quindi facile da costruire e se prendiamo gruppi con ordine primo, tutti i suoi
elementi tranne l'identità generano il gruppo. Ma a cosa ci serve tutto questo in crittografia?
ASSUNZIONE DEL LOGARITMO DISCRETO
Se
ogni
è un gruppo moltiplicativo ciclico, allora esiste una corrispondenza biunivoca "naturale" tra
si può mettere in corrispondenza un unico
un generatore del gruppo).
, ossia quello tale per cui
e
(dove
: ad
è
Dove
è un qualunque intero preso nell'insieme
rispetto a , che scriviamo
che chiamiamo logaritmo discreto di
.
Come è possibile immaginare, è molto facile passare da
ad
tramite , basta fare l'esponenziazione...ma
non è facile il contrario! Ossia non è facile passare da un elemento del gruppo
potenza con il generatore
all'esponente che messo in
mi dia proprio l'elemento che sto cercando!
Problema del logaritmo discreto
Il problema del logaritmo discreto è semplicemente il problema di calcolare
generatore
per
logaritmo
dati un gruppo ciclico
, un
e un elemento casuale . Non esiste un algoritmo semplice che consenta di calcolare il
in modo semplice ed evitando un bruteforce.
Esperimento
L’esperimento con cui formalizzeremo l’assunzione del logaritmo discreto è parametrizzato, al solito, da una
routine
che dato
Cosa vuol dire costruire
costruisce un gruppo
, di ordine
con
e un generatore
.
? Di sicuro non serve descriverlo tutto elencando tutti gli elementi (sono troppi),
viene restituito quindi in una descrizione compatta (
restituisce un algoritmo che calcola
se
vogliamo).
L'esperimento
è definito come segue:
restituisce non solo la descrizione compatta del gruppo
gruppo e
ordine del gruppo, rappresentabile in
Poi scelgo un valore casuale
di
, ma anche
generatore del
bit;
;
All'avversario passo tutto quello che ho appena generato e questo deve calcolare il logaritmo discreto di
, cioè .
L'avversario vince quando
.
Al solito, diciamo che l'assunzione del logaritmo discreto vale rispetto a
esiste
sse per ogni avversario
tale che:
Parleremo, come per
, di alcune assunzioni un po' più forti di quella del del logaritmo discreto, ossia
quella di Diffie-Helmann.
Assunzione Computazionale di Diffie-Helmann
Dato un gruppo ciclico
segue:
e un generatore
per esso, definiamo la funzione
come
La funzione
è parametrizzata su un
input due elementi del gruppo
discreti ed eleva
che è generatore di un gruppo ciclico
. Questa funzione prende in
, calcola il logaritmo discreto di entrambi, moltiplica i rispettivi logaritmi
a questo prodotto. Osserviamo come:
Questo è vero poiché
per ovvie ragioni e stessa cosa per .
Problema
Il problema
gruppo
(computational diffie-helmann) consiste nel calcolare efficientemente
e un generatore
L'assunzione
per esso (prodotti tramite
(rispetto a
, dati un
).
) vale quando il problema
è difficile (rispetto allo stesso
).
Questa cosa è fortemente legata quindi all'ipotesi che il calcolo del logaritmo discreto sia un problema
difficile, infatti ogni algoritmo efficiente per il logaritmo discreto induce un algoritmo efficiente per
. Basta infatti calcolare i logaritmi di
e , moltiplicare i risultati ottenuti ed elevare
per il prodotto.
Assunzione Decisionale di Diffie-helmann
Informalmente, il problema
,dati ovviamente
consiste nel distinguere
e .
Formalmente, diciamo che il problema
sse per ogni
dove
da un arbitrario elemento del gruppo
è difficile (o che l'assunzione
è valida rispetto a
)
esiste trascurabile tale che:
è il risultato di
e
sono casuali.
In pratica l'avversario non deve rendersi conto che il sesto parametro è parente del quarto e del quinto, se si
rende conto di questa cosa, ovviamente vince trivialmente.
Ogni algoritmo efficiente per
un'assunzione ancora più forte di
induce trivialmente un algoritmo efficiente per
! Infatti basta che
calcoli
quinto parametro e confrontare il risultato con il sesto parametro!
Siamo quindi arrivati a questo punto
Assunzioni di DH su Gruppi Specifici
dove
. Quindi
è
e sono il quarto e il
Per curiosità: le curve ellittiche sono gruppi (buoni) verso le quali le assunzioni di DH sono assunte essere
vere e in un senso forte.
Prima di tutto è da osservare come l'utilizzo di gruppi con un numero primo di elementi è preferibile, questo
perché:
Testare se un elemento è o meno un generatore è triviale (tutti gli elementi del gruppo tranne l'identità
sono generatori);
Perché è possibile dimostrare che se
La probabilità della funzione
uniforme (non ci sono valori di
su due parametri
con
casuale, sia uguale a
allora:
casuale è esattamente quella
più o meno probabili). Non si riescono a fare attacchi statistici.
Prendiamo ora in considerazione i gruppi
(dove
è un gruppo di ordine primo
dove
è un numero primo, che contengono tutti gli interi tra
e
non è primo ed è pari soprattutto):
Un algoritmo
che generi gruppi di questo tipo esiste ed è efficiente. L’assunzione del logaritmo
discreto vale per questo gruppo;
DDH non si crede difficile per questi gruppi; Esiste però un algoritmo che genera un sottogruppo di
per il quale
si crede essere difficile. Un esempio di questi sottogruppi, sono come dicevamo, sono
le curve ellittiche.
Dalla Fattorizzazione alle Funzioni One-Way
Sia data una funzione
, che utilizzi al più
bit casuali su input di lunghezza , dove
è un
polinomio.
Tentiamo di costruire un algoritmo che calcoli
abbiamo il primo grande ostacolo:
che sia one-way e quindi deterministica. Qui
è probabilistico!
Dobbiamo quindi prendere le scelte probabilistiche che
esegue come parametro e farle
diventare parametro di questa funzione.
Costruiamo l'algoritmo come segue:
L'input è una stringa ;
Calcola prima di tutto un intero
Calcola ora
tale che
come il risultato di
;
usando come bit casuali quelli in , che sono
sufficienti. Quindi ho usato il parametro della funzione per risolvere le scelte probabilistiche di
;
Restituisce
;
Osserviamo che se costruiamo questa funzione, ci sono due variabili casuali che sono identicamente
distribuite per ogni
Il risultato
di
Il risultato
di
Deriviamo quindi che:
:
dove
dove
è scelta in modo casuale;
.
La difficoltà di fattorizzare
grado da
a risalire a
anche
!
e
Invertire
Riassumendo
, diventa la difficoltà di risalire da
potrebbe ritracciare la sequenza di
permette di fattorizzare
a : se l'avversario di
per ottenere non soltanto
, proprio quello che diciamo nel teorema.
fosse in
, ma
La rivoluzione a chiave pubblica
La crittografia simmetrica e i suoi limiti
Nonostante la crittografia simmetrica (o a chiave privata) permetta di risolvere i problemi dell’autenticazioni
e della confidenzialità, essa soffra di alcune fondamentali limitazioni: la distribuzione delle chiavi, lo storage
delle chiavi e la gestione dei sistemi aperti.
Distribuzione delle chiavi
Nella crittografia a chiave privata occorre che mittente e destinatario condividano una chiave. Ma ci deve
essere una fase "iniziale" in cui questa chiave, generata in qualche modo arrivi sia al mittente che al
destinatario e non si può certo usare un canale insicuro per scambiarla, e lo scambio "manuale" delle chiavi
è realistico solo in applicazioni militari.
Quindi come viaggia questa chiave? Ci deve essere una terza parte che consenta che la chiave privata
generata viaggi in maniera segreta... ma stiamo risolvendo il problema della confidenzialità assumendo che la
chiave viaggi in maniera confidenziale (non buono)!
Storage delle chiavi
Se il numero di utenti in gioco è pari ad
utente richiede
, il numero delle chiavi necessario è
nuove chiavi e quindi il problema è al crescere di
e l'ingresso di un nuovo
(che di solito è molto grande).
Cercheremmo di ridurre il numero di chiavi grazie al meccanismo a chiave pubblica e in particolare di rendere
questa cifra costante!
Gestione dei sistemi aperti
Nei sistemi aperti, ossia quei sistemi distribuiti in cui nuovi utenti si possono connettere/disconnettere al
sistema in modo dinamico, la crittografia simmetrica non offre alcuna soluzione. Quando un nuovo utente
entra nel sistema, infatti, si creano un sacco di problemi: un utente si deve mettere in connessione con tutti gli
utenti, per cui bisogna creare una nuova chiave per ogni altro utente già esistente all'interno del
sistema...troppo costoso per la frequenza di connessione/disconnessione di un utente al sistema.
Una soluzione parziale: il KDC
Un modo abbastanza semplice per alleviare i primi due problemi (che è ovviamente multi-party) è quello di
dotare il sistema di un key distribution center (
), in cui:
Tutti gli utenti condividono una chiave segreta con il
Ogniqualvolta un utente
;
vuole comunicare con un utente
,
invia al
uno speciale messaggio
in cui richiede il rilascio di una speciale session-key;
Il
invia poi sia ad
che a
una nuova chiave, che al termine della sessione di comunicazione
viene cancellata. Quindi questa chiave ha una valenza temporale!
Questo è il caso di un sistema distribuito, in cui c'è però un'entità centralizzata! Ovviamente il
occupa della comunicazione, ossia i messaggi che
si scambiando passano per un altro canale e
verranno crittati utilizzando la chiave di sessione fornite dal
consegna dal
I
stesso (poiché il
non si
è un
...ma i messaggi non verranno mai presi in
)!
di questo approccio sono i seguenti:
Ogni utente ha bisogno di conservare una sola chiave (ovvero quella che condivide con il
), più le
session-keys;
Quando un nuovo utente entra in gioco, basta scomodare il
Gli
e non tutti gli altri utenti.
di questo approccio sono i seguenti:
Forzare il
significa forzare l'intero sistema. Se una parte malfidata riesce a fingersi
è game
over;
Se il
è un point-of-failure (non esiste ridondanza), allora se questo cade, l'intero sistema cade (per
ovvie ragioni).
In particolare se il
si occupa solo della distribuzioni delle chiavi allora una comunicazione già
iniziata potrà andare a buon fine (anche se nessun nuovo utente potrà comunicare), mentre se si occupa
anche della comunicazione allora anche quella fallirà!
Distribuzione delle chiavi nel KDC
Come avviene la distribuzione delle chiavi di sessione? Esistono molti protocolli per la distribuzione della
chiave tramite
.
Uno di questi è un protocollo dovuto a Needham e Schroeder (detto anche protocollo
fa richiesta al
Il
di creare una nuova chiave di sessione per comunicare con
, genera una chiave di sessione
quella condivisa tra
chiave
ed
e la invia prima ad
):
;
, ma criptandola con la chiave
che è
e gli invia anche il crittogramma che corrisponde alla criptazione della
ma stavolta con la chiave
(quella condivisa tra
ed
, in modo tale che
non possa
modificarla);
Infine
Il protocollo
codifica
Tuttavia
invia la chiave crittata con
a
.
è comunque vulnerabile ad attacchi sul lato dell'autenticazione, se si usa uno schema di
-sicuro.
sta alla base del protocollo Kerberos (che è un ottimo protocollo di distribuzioni delle chiavi).
Rivoluzione a chiave pubblica
I
sono quindi una soluzione ma non la SOLUZIONE al problema! Risolvono dei problemi ma hanno il
problema della sicurezza del one point failure per la gestione delle nuova comunicazioni (anche se è
possibile lavorare con vecchie sessioni).
La rivoluzione a chiave pubblica ha luogo a partire dal 1976, quando Whitfield Diffie e Martin Hellman
pubblicarono un articolo, “New Directions in Cryptography” (prima del quale era universalmente accettato
che condizione necessaria alla crittografia fosse la chiave condivisa).
Diffie Hellman, osservarono che è assolutamente plausibile uno scenario asimmetrico, i cui ci siano
ma
chiavi:
Una chiave di cifratura,usata dal mittente;
Una chiave di decrittazione, usata dal destinatario.
Sorprendentemente, la sicurezza dello schema di codifica deve valere anche contro avversari che conoscano
la chiave di cifratura, che è anche detta chiave pubblica.
In questo modo, non c'è più bisogno di un canale sicuro per la distribuzioni delle chiavi poiché il mittente
rende pubblica la chiave di cifratura (mentre quella di decrittazione rimane al proprietario)!
Comunicazione in chiave pubblica
Cosa succede in comunicazione:
vuole comunicare con
e possiede la sua chiave di cifratura pubblica;
cripta il messaggio usando questa chiave di cifratura ed invia il messaggio sul canale;
Solo
può leggere il messaggio, in quanto solo lui possiede la chiave di decrittazione!
Volendo si può risolvere anche il problema dell'autenticazione: in pratica
con la chiave di cifratura pubblica di
prima di criptare il messaggio
lo cripta con la sua chiave di decrittazione. In questo modo solo
sarà in grado di leggere il messaggio, poiché possiede sia la sua chiave di decrittazione che la chiave di
cifratura pubblica di
! Ed inoltre è sicuro che il messaggio è stato scritto da
per ovvi motivi.
Soluzioni alle limitazioni della crittografia simmetrica
Le tre limitazioni della crittografia simmetrica sono risolti come segue:
1. Le distribuzioni delle chiavi può avvenire su canali pubblici, ma autenticati. Se questi canali non
fossero autenticati, il ricevente non potrebbe avere la certezza che la chiave pubblica di
effettivamente generata da
sia stata
.
2. Ogni parte coinvolta nella comunicazione deve mantenere segreta una sola chiave (la sua chiave di
decrittazione, tanto le altre le prende da qualche posto pubblico);
3. I sistemi aperti sono gestiti in maniera soddisfacente: ogni nuovo utente crea una chiave pubblica e la
rende disponibile agli altri utenti (senza scambiare nuove
chiavi, la complessità in questo caso è
costante!).
Primitive a chiave pubblica
Diffie ed Hellman proposero tre primitive a chiave pubblica:
La crittografia a chiave pubblica, di cui abbiamo parlato ora;
La firma digitale, ovvero l'equivalente asimmetrico dei
;
Un protocollo interattivo per lo scambio di chiavi, tramite il quale due parti che non condividono
nessuna informazione segreta possono generare una chiave segreta in modo sicuro.
Protocollo per lo scambio delle chiavi
Il primo dei problemi per la crittografia simmetrica è gestire la fase iniziale quella , in cui due utenti
e
devono da zero, arrivare a condividere una chiave segreta che poi utilizzeranno per lo scambiarsi messaggi
in maniera segreta. Per risolvere il problema utilizzeremo un
Un
.
è un insieme di regole
che prescrive in che modo due parti
e
si debbano scambiare un certo numero di messaggi:
Al termine del protocollo
ha costruito una chiave
Il protocollo è corretto se
, mentre
ha costruito
;
.
Questo protocollo è banalmente insicuro, poiché questa chiave viaggia in chiaro nel canale.
Sicurezza di un protocollo per lo scambio di chiavi
La sicurezza di un protocollo per lo scambio delle chiavi è formulata tramite un esperimento:
Nella prima istruzione c'è
che è il protocollo di scambio di chiavi che faccio eseguire sul parametro di
sicurezza e ottengo come risultato due informazioni:
La chiave generata
corretto) da
e da
in comune (che deve essere identica poiché consideriamo il protocollo
;
Il transcript dell'interazione tra
e
e
che possiamo pensare come una sequenza di messaggio che
si scambiano. Sicuramente il transcript non contiene lo stato interno di
e
e
sperabilmente il transcript non dovrebbe contenere .
Poi passiamo all'avversario o la chiave
casualmente (con probabilità
generata dal protocollo oppure una chiave generata
), ma non solo...gli passiamo anche il transcript!
L'avversario non deve far alto che capire se la chiave è una chiave del protocollo oppure no!
L'avversario
emetterà come al solito un bit.
Come al solito, consideriamo
sicuro sse per ogni
esiste
tale che:
Se l'avversario indovina sempre siamo messi veramente male. C'è però da dire una cosa: l'esperimento
modella bene il caso di avversari passivi (
) ma a volte l'avversario potrebbe essere attivo in un
protocollo ma in questo esperimento non lo prevediamo (l'avversario vede solo quel che passa).
Il protocolli
è e deve essere probabilistico, perché avendo la
simulare l'esecuzione di
e indovinare sempre.
Il protocollo Diffie e Hellman
e il transcript, l'avversario potrebbe
E' costruito su principi simili a quelli che hanno portato i due ricercatori a formulare le assunzioni che
prendono da loro il nome. Il protocollo è definito come segue:
1.
genera un gruppo ciclico usando
e costruisce una istanza problema del logaritmo
discreto costruendo un esponente buono
(compreso in
abbiamo appena generato). Poi calcoliamo
2. A questo punto
3.
e inviamo a
è l'ordine del gruppo che
la descrizione del gruppo e questo
ha a disposizione tutti gli elementi della quadrupla e fa la stessa cosa di
esponente buono
privata che è
, dove
allo stesso modo di
, calcola
ed invia
ad
;
: genera un
e genera la sua chiave
.
, dopo aver ricevuto
genera la sua chiave privata che corrisponde a
.
La chiave generata è la stessa? Ma certo e lo dimostriamo con:
Elevando alla
mai scambiare
e alla
ed
otteniamo quindi lo stesso risultato, ma il punto è che siamo riusciti a fare tutto senza
!
Il fatto che questo valore sia difficile da ricostruire è da dimostrare ma lo faremo col prossimo teorema.
Sulla sicurezza del Protocollo Diffie Hellman
Osserviamo che l'assunzione
è stata formulata dopo l'introduzione del protocollo di cui sopra e proprio
per capire quale fosse una proprietà sufficiente per la relativa sicurezza. Cosa ci abbiamo guadagnato?
Abbiamo enucleato una proprietà, che possiamo poi mettere in relazione con il logaritmo discreto.
Oltre ad attacchi passivi, ne esistono altri che
non cattura:
Attacchi di impersonificazione;
Attacchi Man-in-the-Middle, nei confronti dei quali Diffie-Hellman è noto essere insicuro.
Dimostrazione sicurezza del Protocollo Diffie Hellman
Costruiremo la dimostrazione sfruttando unicamente le probabilità: dimostreremo che l'avversario rispetto a
è lo stesso di
.
Passo 1
Usiamo la probabilità e distinguiamo due casi: uno nel caso in cui passiamo all'avversario la chiave generata
dal protocollo o casuale:
Passo 2
Sappiamo che quando
l'avversario
l'avversario
riceve in input la chiave
e il transcript mentre quando
riceve in input una chiave casuale (che corrisponde a generare un elemento del gruppo) e il
transcript . Per cui possiamo riscrivere tutto quello che abbiamo scritto finora in:
Questo è vero poiché l'avversario vince quando
solo se emette !
Passo 3
Il tutto, per trasformazioni matematiche semplici, è pari a:
Poiché, per ipotesi
sappiamo che
una funzione trascurabile che viene proprio da
è
!
Schemi di codifica a chiave pubblica
I cifrari in un quadro asimmetrico
Nella crittografia asimmetrica, chiunque voglia riceve messaggi genera non una chiave ma una coppia di
chiavi (
) dove:
è una chiave pubblica, che è usata dal mittente nella codifica dei messaggi e deve raggiungere il
numero maggiore possibile di utenti (tramite canali autenticati anche se non privati);
deve rimanere segreta.
Il quadro diventa quindi il seguente:
l'avversario
però ha accesso solo in lettura poiché stiamo parlando di confidenzialità.
Chiave simmetrica vs asimmetrica
Nella crittografia asimmetrica:
Solo una parte della chiave è tenuta segreta, mentre l'altra è resa pubblica;
(Porzioni di) chiavi diverse vengono usate nelle fasi di codifica e decodifica.
I vantaggi della chiave asimmetrica sono:
Non c'è più la necessità di distribuire le chiavi su canali privati;
Ogni utente deve gestire la segretezza di una sola chiave.
Gli svantaggi della chiave asimmetrica sono:
Gli schemi a chiave asimmetrica tendono ad essere ordini di grandezza meno performanti degli schemi
a chiave simmetrica;
Occorre distribuire le chiavi pubbliche su canali autenticati, in assenza dei quali un semplicissimo
attacco è possibile. Infatti se il ricevente della chiave pubblica non avesse delle certezze sul proprietario
della stessa, inizierebbe a cifrare i messaggi con questa chiave....ma poi chi sarà a leggerli?
Schemi di codifica a chiave pubblica
La definizione di schema di codifica
deve essere opportunamente modificata:
prende in input una stringa nella forma
che
possa essere inferito proprio da
L'algoritmo
crittogramma;
e
e produce due chiavi
tali che
.
prende in input un messaggio
e una chiave pubblica
e produce in output un
e tali
L'algoritmo
può essere probabilistico, prende in input un crittogramma
e una chiave segreta
e
produce in output :
Un messaggio;
Oppure uno speciale simbolo
(con una probabilità trascurabile) che sta per errore (il
crittogramma non è prodotto in maniera buona). Questo errore deriva dal fatto che il crittogramma
che stiamo decifrando è un crittogramma che per quanto sia prodotto tramite
non sia in una
buona forma.
Assumiamo lo schema corretto in senso probabilistico: deve esistere una funzione trascurabile tale che per
ogni coppia
prodotta da
e per ogni :
Quindi esiste la possibilità che la decodifica del messaggio generi un qualcosa diverso dal messaggio ma è
trascurabile.
Spesso,
è definita solo per i messaggi di lunghezza pari a , oppure su tutto lo spazio
(per
stringhe arbitrarie). Quindi lo schema di codifica potrebbe essere sia a lunghezza fissa che variabile!
Come definiamo la sicurezza per questo nuovo tipo di schemi?
Sicurezza di uno Schema di Codifica a Chiave Pubblica
La nozione di esperimento va opportunamente modificata:
Ovviamente l'avversario conosce la chiave pubblica e genera due messaggi di stessa lunghezza a questo
punto ne crittiamo uno dei due (ovviamente con
, la slide è sbagliata) e l'avversario deve indovinare quale
abbiamo crittato.
Osservazioni sulla definizione
La definizione di sicurezza che abbiamo appena dato è impercettibilmente diversa da quella vista in un
contesto simmetrico:
ha accesso, ovviamente, anche a
.
Tale leggera differenza ha conseguenze importanti:
Il fatto che
abbia accesso a
implica che
possa crittare qualunque messaggio, anche senza
l’accesso all'oracolo (perché lo ha già);
Dati
e
, è sempre possibile ricostruire
avendo a disposizione tempo arbitrario.
Questo è vero perché può provare a codificare tante volte messaggi diversi e vedere per quale
messaggio corrisponde quel crittogramma.
Quindi, la definizione di sicurezza contro attacchi passivi e contro attacchi attivi collassano! Infatti:
Inoltre:
Questo secondo teorema è vero perché la sicurezza perfetta vale per tutti gli avversari, mentre qua non lo
possiamo dire perché se ci sono avversari con tempo illimitato riusciamo a risalire al messaggio (mentre in
one time pad no).
Insicurezza delle codifiche deterministiche
Sappiamo che ogni avversario passivo, avendo a disposizione
è di fatto anche attivo. Di conseguenza,
tante proprietà che abbiamo visto valere nel caso simmetrico e per attacchi
Se per assurdo
, valgono anche qui.
fosse deterministico, allora un avversario che vince con probabilità 1 l'esperimento, è
quello che produce due messaggi diversi qualunque, li da in pasto allo schema (che li codifica) ma l'avversario
ha a disposizione la chiave pubblica quindi li ricodifica e trova subito quale è stato crittato.
Storicamente, si osserva come moltissimi schemi di codifica a chiave pubblica concreti siano tali per cui
deterministica. Tutto ciò ha avuto (ed ha) conseguenze nefaste.
Sulle codifiche multiple
Siccome siamo vicini al caso
l'esperimento, che definiamo ora
una coppia di messaggi
, le codifiche multiple non creano nessun problema. Modifichiamo
per codifiche multiple, in cui in cui l’avversario produca non
ma una coppia di tuple di messaggi
=
;
=
;
dove:
è
.
L'avversario restituisce due tuple di messaggi che hanno la stessa lunghezza e la lunghezza dei due messaggi
corrispondenti nelle tuple (stesso indice) hanno stessa lunghezza. All'avversario viene passato uno dei due
vettori di messaggi, in cui però li abbiamo criptati tutti e questo deve capire quale dei due vettori di messaggi
abbiamo crittato. Come al solito l'avversario restituisce un bit.
Al solito, uno schema di codifica a chiave pubblica
si dice sicuro rispetto a codifiche multiple sse per ogni
esiste con:
Stiamo quindi dicendo che se
è uno schema di codifica pubblico sicuro contro attacchi passivi per codifiche
singole lo è anche per codifiche multiple.
La dimostrazione è intuitiva: se ho a disposizione un avversario che vince l'esperimento per codifiche
multiple, come faccio a far diventare questo avversario per codifiche singole? Questo avversario produce delle
tuple, quindi basta "gestirsi in casa" tutti gli elementi della tupla di messaggi tranne uno, che passerò
all'ambiente.
Nota ulteriore sui messaggi a lunghezza variabile
I messaggi di lunghezza variabile non sono un problema: estendere uno schema di codifica a chiave pubblica
che lavori per messaggi di lunghezza fissa a uno che lavori con messaggi di lunghezza variabile non è un
problema (basta dividere per segmenti il messaggio e applicare la criptazione a ciascun segmento).
Dato un
cifrario a chiave pubblica (dove lo spazio dei messaggi è
costruire un cifrario
(dove lo spazio dei messaggio è
dove
), possiamo
cifrario a chiave pubblica per messaggi di lunghezza variabile
), definendo
. Ovviamente anche
Ovviamente abbiamo che se
come:
deve essere modificato nel modo ovvio.
è sicuro contro attacchi passivi allora anche
lo è.
Conclusioni
Avere uno schema di codifica sicuro contro attacchi passivi e che lavori per messaggi di lunghezza fissa
garantisce sia la sicurezza per codifiche multiple che per messaggi di lunghezza variabile!
La codifica ibrida
Questa costruzione dà per scontato che uno schema a chiave pubblica esista già.
La codifica ibrida nasce dal fatto che gli schemi a chiave pubblica sono meno performanti di quelli a chiave
privata (quelli a chiave privata fanno operazioni più semplici e veloci, a volte realizzate direttamente in
hardware).
Questa codifica cosiddetta ibrida vuole prendere i vantaggi della codifica a chiave privata (performance) e
quelli della codifica a chiave pubblica (sistemi aperti, numero chiavi e store delle stesse).
Dati uno schema a chiave pubblica
e uno schema a chiave privata
, possiamo costruire
In pratica, si codifica una chiave
codifica la chiave
in cui la codifica abbia più o meno l'aspetto seguente:
(stringa casuale) e viene passata ad
con la chiave pubblica
(schema chiave pubblica) che
e fa diventare il risultato parte del crittogramma finale (così solo
il ricevente potrà leggerla).
La stessa chiave casuale
diventa la chiave di
(schema chiave privata), che cripterà il messaggio
.
Definizione formale
Nel definire formalmente la codifica ibrida, faremo l'assunzione che
e
(schema a chiave pubblica) includa
restituisca una stringa casuale in
tra lo spazio dei messaggi (riesca almeno a criptare
messaggi lunghi ).
Formalmente
è definito nel modo seguente a partire da
e
:
Come vediamo, le chiavi dello schema ibrido sono le chiavi dello schema pubblico;
Allora, in pratica ha sbagliato a mettere tutti gli
In
si restituisce
In
la chiave
della chiave
quindi tento di metterli bene io:
);
viene generata con
e quindi viene fatto da
, poi il crittogramma
. Infine,
rappresenta la criptazione
che è il crittogramma del messaggio viene
fatto da
In
la chiave
viene calcolata decifrando con la chiave di cifratura pubblica il crittogramma
. A questo punto è possibile ricavare il messaggio
semplicemente applicando
.
, poiché decifrando
ho ottenuto la chiave ,
Ma dov'è l'efficienza di questo schema?
dello schema pubblico lo chiamo comunque, però lo chiamo su
qualcosa di piccolo che è la chiave e non sul messaggio (che invece può essere molto lungo).
Tempi di codifica
Supponiamo che la codifica della chiave richieda tempo
e che la codifica del messaggio richieda tempo
per ogni bit.
Il tempo medio impiegato da
per ogni bit sarà quindi, per messaggi lunghi , pari a
.
Dividiamo tutto per perché stiamo calcolando il tempo medio.
Quando diventa grande, vediamo che il tempo tende a :
Quindi il tempo medio per la codifica di messaggi tende al tempo che impiega
dello schema a chiave
privata che sappiamo essere efficiente.
Lunghezza del crittogramma
Un discorso molto simile a quello fatto per il tempo di codifica può essere fatto per la lunghezza dei
crittogrammi. Al crescere di
, la quantità
resta fissa (schema a chiave pubblica), mentre esistono schemi
di codifica a chiave privata tali per cui
Al crescere di
, la lunghezza di
è quindi lineare.
Lo schema di codifica RSA
La codifica a chiave ibrida è una costruzione che presuppone l'esistenza di due costruzioni, ma dobbiamo
definirle! Gli schemi a chiave privata li abbiamo già visti e costruiti, dobbiamo quindi costruire uno schema a
chiave pubblica.
Textbook RSA
Presentiamo un primo schema detto
In
generiamo la solita tripla
pubblica e
:
dove
è il prodotto
(due primi).
la chiave privata (ed hanno le proprietà esposte nell'assunzione
dato da due coppie
. E' sicuro far viaggiare il prodotto di
e
che è
diventa la chiave
), infatti il risultato è
!.
Nell'
, il ruolo che svolgeva la
è dato da
Nel
nell'assunzione
, lo gioca il messaggio
ed ora il crittogramma
;
, dobbiamo invertire la
un qualcosa alla
ma sappiamo già come fare
che precedentemente è stato elevato alla
. Poiché
è l'inversa di
! Elevare
equivale a trovare l'identità che è il
messaggio.
Stiamo dicendo tra le righe che il messaggio deve essere un elemento di
La correttezza dello schema discende dal fatto che
per cui
è l'inversa di
.
crei unicamente coppie nella forma
.
Problemi di Textbook RSA
Purtroppo
è insicuro rispetto alla nostra definizione. Per rendersene conto, basta
osservare che
è deterministico: poiché l'avversario ha a disposizione
e quindi qualcosa del
messaggio lo capisce (andando per tentativi) e poi c'è l'attacco in cui l'avversario produce due messaggi
distinti per poi calcolare la codifica di questi due e riconoscere subito quale dei due è stato crittato.
Vale però la nozione di sicurezza molto debole che viene dall'assunzione RSA: data
non è possibile determinare il messaggio
l'assunzione
chiave pubblica e
nella sua interezza, almeno quando vale
.
Da un punto di vista teorico, occorrerebbe garantire che
Tuttavia anche quando
la codifica e la decodifica funzionano!
Inoltre, si può anche dimostrare che
probabilità che un generico elemento di
è la radice cubica di
... ma sarebbe troppo costoso per gli utenti.
, vista come funzione di
sia effettivamente in
è nella forma
. Ossia la
è molto alta!
(errore slide problemi.)
Attacchi Textbook RSA
La letteratura ci offre tantissimi esempi di attacchi contro
Se, come spesso succede, si sceglie
molto piccolo (ad esempio 3), allora
:
come un valore fisso (non si usa nemmeno la randomness) e
è la radice cubica di
, la quale può essere
facilmente calcolata.
La complessità dell’attacco brute force può essere ridotta da
l'attacco brute force esponenziale perché
In particolare, questa versione di
a
. Questo fa rimanere comunque
è esponenziale in .
è molto suscettibile agli attacchi side-channel sono quegli
attacchi che (nel nostro modello di crittografia non catturiamo) e consistono nel fatto che gli avversari
riescano a carpire qualcosa del messaggio sul canale non analizzando il comportamento di messaggiocrittogramma ma piuttosto osservando quanto tempo/ energia lo schema impiega per crittare il
messaggio.
Padded RSA: rendere RSA sicuro secondo la nostra definizione
Otteniamo
dove il padding è ovviamente probabilistico:
E' tutto uguale a prima ma aggiungiamo nell'imposizione più significativa del messaggio dei bit
casuali.
In
non si codifica, quindi, il messaggio originale che viene preposto con una stringa
che è lunga
ossia una quantità sufficiente a riempire un eventuale spazio mancante al posto dei bit
più significativi. Ovviamente
dovrà prendere solo i bit meno significativi del messaggio proprio
perché nei più più significativi c'è qualcosa di casuale.
Abbiamo reso l'algoritmo deterministico!
Il tutto vale se è una funzione tale che
ed
riporta i bit meno significativi.
Come detto, questo nuovo schema è probabilistico, ma vorremmo che la quantità di randomness sia alta!
Purtroppo non è così: infatti i bit che abbiamo a disposizione sono al massimo
lunghezza esprimibile con
poichè
è la
che se ben ricordiamo è il prodotto di due primi esprimibili in esattamente
bit.
Occorre quindi scegliere
sufficientemente piccolo, meno che lineare!
Il problema è che la sicurezza, quando
nulla. L'unica soluzione per rendere
è meno che lineare è una sfida aperta,: non si riesce a dimostrare
sicuro per la nostra definizione è prendere
, quindi
il numero di bit per il padding probabilistico è logaritmico nel parametro di sicurezza.
E' un disastro (troppo poco padding) però potremmo estendere
a messaggi di lunghezza
variabile e riapplicare questo padding molte vote (e risolvere il problema).
Se però, lasciamo tutto secondo questa definizione dobbiamo fare solo messaggi cortissimi e per questi
messaggi vale che:
Schema di codifica di Elgamal
Oltre ad
, esiste un altro schema di codifica sicuro alle base delle assunzioni di cui abbiamo parlato
qualche lezione fa. In particolare, ne esiste uno, dovuto a Elgamal, che si può dimostrare sicuro a partire
dall’Assunzione
.
L'osservazione da cui partire è il fatto che fissati due elementi
di un gruppo finito (non solo ciclico),
la probabilità che un elemento casuale
è pari a
Quindi ogni elemento del gruppo
sia tale per cui
.
ha la stessa probabilità di essere un elemento per cui vale
,
quindi un qualunque elemento del gruppo ha la stessa probabilità di essere utilizzato come chiave (come in
one-time pad).
Osserviamo che la struttura di gruppo (in particolare gli inversi) ci permette di scrivere ciò che abbiamo detto
come :
Ribadiamo che
è scelto in modo casuale nel gruppo, e lo vediamo in questo passaggio (nella seconda
probabilità siam sicuro che questo
sia un valore casuale).
Definizione formale Elgamal
Chiamo
(generatore gruppo ciclico), ottengo la solita tripla gruppo, ordine, generatore del
gruppo. Poi si definisce l'esponente
come un elemento di
che diventa la chiave privata
mentre
diventa la chiave pubblica.
che ha a disposizione la chiave pubblica e come sappiamo la criptazione deve essere probabilistico.
Quello che facciamo è definire
come elementi casuale di
e poi restituisco la tripla :
che non dipende dal messaggio ed è il generatore elevato a ;
che dipende ovviamente dal messaggio.
In
mi arriva
ricordiamo
dove
e lo elevo alla
e
è la chiave pubblica che mi è arrivata da
ce l'ho perché sono in fase di decodifica e se ben
era stato fissato come la chiave privata quindi ottento
ma allora per ottenere
basta effettuare
. Ma sappiamo che
.
La correttezza dello schema è facile da dimostrare:
Il prossimo teorema ci dice esattamente quello che ci aspettiamo.
Dimostrazione sulle codifiche multiple schemi a chiave
pubblica
Dimostriamo il seguente teorema:
;
,
Assumiamo che queste codifiche multiple siano due! Assumiamo quindi
(guarda definizione).
Vogliamo analizzare la probabilità seguente:
La cifra
sta lì perché si. Sappiamo come funziona l'esperimento, sappiamo quindi che quando
all'avversario passo il primo vettore di messaggi criptato mentre se
io
gli passo il secondo. Per cui posso
scrivere la probabilità precedente come:
Il fatto che ci sia prima un
ed
quando
è dovuto al fatto che l'avversario vince se restituisce
quando
.
Ora trasformiamo il primo
vedere questo
e poi un
in un
(non riscrivo tutta la roba di sopra tanto è identica). Ora, possiamo
come una somma di due eventi complementari (in probabilità la somma di due eventi
complementare fa proprio uno) e questi due eventi complementari sono i seguenti:
Se guardiamo bene, sto guardando gli eventi che corrispondono al comportamento dell'avversario quando si
vede arrivare due messaggi con stessi indici ma di due vettori di messaggi diversi! (evento molto strano)
Ovviamente l'avversario dovrà restituire 0 oppure 1 come al solito. Quindi possiamo scrivere tutta la prima
equazione come;
L'idea è di raggruppare questi 4 elementi in coppie non standard!
Accoppiamo quindi la seconda probabilità con la terza e la prima con la quarta e otteniamo:
In entrambi i casi, si trova nel primo lato sempre la codifica di
Lemma 1
, mentre nel secondo lato un'altra cosa.
Questo è vero, perché se ci fosse un avversario di fare di meglio riusciremmo a costruire un avversario per lo
schema di partenza
che era sicuro rispetto a
in maniera facile. Infatti, l'avversario riceve due
vettori di messaggi criptati dove il primo è lo stesso, ma se
è sicuro per codifiche multiple questo non deve
variare le possibilità dell'avversario di vincere!
Lo stesso concetto vale per il lemma 2.
Lemma 2
Fine dimostrazione
Per i due lemmi appena mostrati e per le varie uguaglianza fatte possiamo dire che:
Ma allora abbiamo dimostrato il nostro teorema!
Crittografia formale
Finora abbiamo parlato di crittografia computazionale dove abbiamo:
I nostri assiomi sono le primitive e i protocolli crittografici;
Gli avversari, che sono modellati tramite algoritmi PPT e le probabilità di successo degli stessi sono
positive ma trascurabili!
Questo è stato il modello di riferimento, finora, in una moltitudine di scenari e in particolari quelli nei quali la
comunicazione avvenga seguendo protocolli relativamente semplici. Questi scenari sono ad esempio: cifrari a
chiave pubblica e privata, MAC, firma digitale, etc...
Tuttavia, il modello computazione è per sua natura sensibile alla quantità di risorse utilizzate e alla
probabilità che certi eventi si verifichino e questo ci porta a parlare dei limite dall'approccio
computazionale.
Limiti della crittografia computazionale
Il primo limite è pratico: appena si comincia a lavorare con sistemi e problemi più complessi di quelli visti
(confidenzialità, cifrari, etc...), ragionare in modo probabilistico e ragionare sulla complessità delle entità
in gioco diventa difficile.
Il secondo limite consiste nel fatto che gestire sistemi simili a quelli aperti, quando il numeri di parti
coinvolte cresce è difficile! E' difficile capire anche solo come declinare il concetto di efficienza, centrale
nell'approccio computazionale.
Mano mano che ci si sposta dalle semplici primitive con cui siamo abituati a lavorare a protocolli più
complessi, la crittografia computazionale mostra tutti i suoi limiti. Esempi di casi complessi sono i protocolli
per il voto elettronico, cripto valute, commercio elettronico etc...
Spesso i protocolli che vorremmo dimostrare sicuri hanno le seguenti caratteristiche:
Molteplici parti coinvolte;
Molteplici round di interazione;
Le primitive crittografiche (codifica, autenticazione) vengono utilizzate come subroutine.
Dovremo capire come superare questi limiti, grazie all'approccio formale.
Esempio: il protocollo di Needham - Schroeder
Lo abbiamo già visto, era il protocollo distribuzioni e generazione di chiavi di sessione private, con
questa volta lo decliniamo come un protocollo interattivo.
, ma
vuole comunicare con
e chiede ad
identità, ed un nonce
di creare una chiave di sessione. Nel farlo gli invia la sua
che è un valore, tale per cui una qualunque entità che non sia
non
riuscirebbe a generare (stringa casuale);
quando riceve la richiesta di
, genera una nuova chiave di sessione
e risponde ad
con un
messaggio molto grande:
Contiene sicuramente la nuova chiave di sessione;
Il nonce
(questo convince
che il messaggio ricevuto è quello che si aspetta), l'identità di
un crittogramma che contiene la chiave di sessione e l'identità di
condivisa tra
A questo punto
e
in modo tale che solo
e
crittato con la chiave segreta
possa leggerlo;
prende il contenuto che gli spessa e poi invia il contenuto dedicato a
legge il crittogramma e lo decifra e a questo punto genera un suo nonce
proprio a lui;
crittato con la nuova
chiave di sessione. Questo per accertarsi che la comunicazione sia veramente con
e non con
qualcun'altro;
Quando
riceve il messaggio lo decritta ed effettua un'operazione fissa sul nonce
per poi rinviare il
risultato criptato con la chiave di sessione condivisa.
Attacchi contro il protocollo di Needham - Schroeder
Vediamo ora due attacchi che, dal punto di vista della crittografia computazionale, sono difficili da catturare.
Attacco 1
Se un attaccante avesse a disposizione una vecchia chiave di sessione
messaggio 3. Il messaggio 3 è quello in cui
Per
, potrebbe fare il replay del
invia la parte relativa alla chiave di sessione a
.
questo corrisponderebbe all'inizio del protocollo, e quindi risponderebbe generando un nonce
l'attaccante potrebbe decodificare il messaggio e produrre
impersonificare
. Ma a quel punto l'attaccante potrebbe
! Infatti da quel momento in poi, l'attaccante può comunicare con
.
Questo tipo di attacchi, in cui ci sono concetti come l'impersonificazione, man in the middle, replay
diventano difficili da rappresentare nel modello computazionale!
, ma
Attacco 2
C'è un attacco ancora più convincente: in assenza dell'identità
nel messaggio 2, un attaccante
potrebbe
intercettare il messaggio 1, modificare la seconda componente facendola diventare C.
Da quel punto in poi l'attaccante potrebbe impersonificare
senza che
se ne accorga.
Conclusioni
Ciò che rende possibile questi attacchi non è una fragilità nelle primitive crittografiche utilizzate, ma un
errore di natura logica nella costruzione del protocollo stesso (ossia della maniera con cui queste
primitive vengono utilizzate!).
Modellizzare il protocollo nel senso della crittografia computazionale sarebbe non solo difficile (poiché siamo
davanti a protocolli interattivi, molti agenti etc..) ma sarebbe un vero e proprio overkill, poiché le fragilità del
protocollo hanno poco a che fare con le probabilità o con i vari discorsi sugli attaccanti PPT.
La Crittografia formale
La crittografia cambia le carte in tavola, cambiamo completamente il nostro modello.
Indipendentemente dal modello computazionale, a partire dagli anni Ottanta si sviluppa un modello
alternativo a quello computazionale, che in un certo senso risolve alcune. Tale modello è detto modello
formale o modello di Dolev-Yao, in onore di coloro che introdussero il modello in un articolo del 1983.
Confronto tra il modello computazionale e formale:
Questo modello è un modello ancora più astratto della crittografia computazionale e le differenze sono le
seguenti:
I messaggi non sono più stringhe binarie ma espressioni che possiamo definire su una grammatica
libera;
L'avversario era un algoritmo efficiente, mentre ora è un processo arbitrario tra i tanti processi attivi in
un certo protocollo. L'avversario ha a disposizione quante risorse vuole, potrebbe non essere calcolabile
in linea di principio, ma ce ne fottiamo, astraiamo ancor di più.
Un attacco non è più un evento di probabilità non trascurabile ma un evento possibile!
Crittografia formale : Le espressioni
Le espressioni del modello formale non vanno confuse con le stringhe di bit del modello computazionale,
con le quali descrivevamo messaggi, crittogrammi e chiavi, ma vanno pensate come alberi di derivazione di
una grammatica e non come la relativa codifica binaria!
La peculiarità rispetto all’approccio computazionale è che si suppone che conoscere un’espressione nella
sua interezza non necessariamente implichi conoscere le sue sotto-espressioni. Se prendiamo ad esempio
Needham-Schroeder,
potrebbe conosce l'intera espressione che
conosce una sua parte (in particolare
Se un'espressione è proprio
gli invia nel passaggio 2, ma non
).
:
Un avversario potrebbe benissimo conoscere l’espressione;
Ma se non conosce
, l’espressione gli appare come un’entità imperscrutabile. Infatti lui vede un
crittogramma, ma non sa nulla di esso proprio perché non possiede la chiave con cui è stato crittato!
In tal caso non c’è alcuna possibilità che l’avversario riesca a ricostruire, ad esempio,
. Questa è la
principale differenza con il modello computazionale.
Stiamo dicendo tra le righe che se l'avversario non possiede le chiavi o non riesce a ricavarle in alcun
modo da altri messaggi, non riesce a leggere le sotto-espressioni codificate con quelle chiavi, in altre parola
buttiamo via gli attacchi bruteforce! (vedremo che in questo approccio non è possibile enumerare le
chiavi).
Assumiamo quindi, che le primitive crittografiche siano perfette! Quando usiamo la codifica simmetrica,
supponiamo che questa sia impossibile da forzare!
Ovviamente, concretamente l'espressione diventa una stringa binaria, ma ribadiamo che noi, in questo
modello formale, la vediamo come alberi di derivazione dalla grammatica che conosciamo:
Dove le parentesi tonde, rappresenta una produzione tupla o coppia!
Il modello formale?
In realtà, di modelli formali, a differenza di quello computazionale, ne esistono molti:
Per esempio, si può parametrizzare su quali siano le primitive crittografiche a disposizione (a volte c'è
bisogno di utilizzare uno stream chiper o un block chipher o di un hash function...);
La sottostante teoria può cambiare drasticamente (diventando più o meno interessante) quando le
espressioni cambiano, per esempio aggiungendo o togliendo vincoli sulla formazione delle espressioni.
Ad esempio, assumiamo, che nelle espressioni, che creiamo con gli alberi di derivazione, una situazione
di aciclicità nel senso dell'albero di derivazione creato! Un esempio concreto è una chiave che è messa
all'interno di un messaggio crittato con la chiave stessa! Un vincolo del genere, nel modello
computazionale, non si era mai visto. E' sicuramente un vincolo particolare, ma vedremo che un risultato
fondamentale dipende proprio da questo!
In ogni caso, la cosa importante è che al variare delle assunzioni o dei vincoli fatti in questo senso,
otteniamo teorie diverse;
Ciò che però è comune a tutti i modelli formali (rispetto a quello computazionale) è la loro semplicità:
La sicurezza dei protocolli si può dimostrare senza assunzioni. Vero perché assumiamo che la
sicurezza delle primitive valga, una volta per tutte! Mentre nel caso computazionale dovevamo stare
attenti a definire da cosa dipendeva la sicurezza di una primitiva dando le opportune ipotesi;
Dati un protocollo, decidere se esista un avversario è un problema affrontabile anche con tecniche
automatiche. E' decidibile
Di conseguenza, il modello formale sta alla base di tecniche di model-checking, dimostrazione semiautomatica, programmazione logica.
Tutto ciò ha permesso il progetto di tool concreti (come ProVerif, di cui parleremo). Infatti, il fatto che il
problema di decidere se esista un avversario sia decidibile è dato dal fatto che esistono dei tool,
corretti ma non completi, a cui diamo la descrizione della proprietà di sicurezza che vogliamo
dimostrare e la descrizione del protocollo e questi in automatico concludono se la proprietà vale o non
vale (e nel caso troviamo un attacco) . Purtroppo non sono completi, infatti a volte questi rispondono
"non lo so", perché il modello in esame potrebbe essere troppo complesso o la proprietà troppo forte da
dimostrare per un certo modello.
Oltre a Proverif c'è anche easy-cript, che è un tool di dimostrazione assistita, che ci aiuta a dimostrare le
nostre proprietà di sicurezza (ma non ci leva il peso di scrivere una dimostrazione, ci guida semplicemente nel
procedimento).
Un semplice modello formale
Questo modello è semplice perché ci mettiamo una sola primitiva crittografica: un cifrario asimmetrico.
Per prima cosa, cerchiamo di creare le nostre espressioni e per farlo, partiamo dall'insieme
booleani e da un insieme
di simboli di chiave. Gli elementi di
dei
non devono essere confusi con le
stringhe binarie: sono simboli atomici, senza una struttura interna.
Possiamo pensare alle chiavi come i nomi delle variabili del lambda calcolo, ossia semplici simboli senza
una struttura interna poiché non ci interessa.
Espressioni
Le espressioni (e quindi messaggi) di questo semplice modello non sono nient’altro che le espressioni
prodotte tramite la seguente grammatica:
dove:
;
;
indica una coppia di espressioni;
indica un crittogramma generato dalla cifratura dell'espressione
Osserviamo come non ci sia ambiguità nelle espressioni. Ad esempio,
con la chiave
e
.
sono sempre diverse.
Perché non si può fare bruteforce?
Detto questo perché non si può fare bruteforce? Perché l'avversario non ha modo di percorrerlo, una chiave
dopo l'altra. Ovviamente nell'approccio computazionale, ci sarà un certo spazio delle chiavi possibile dettato
dal parametro di sicurezza , ma qui nemmeno c'è! Ma questo è dovuto al modello, qui ci occupiamo solo di
attacchi logici.
Inoltre, ogni chiave ha una struttura atomica e quindi indivisibile!
Implicazione tra Espressioni
Un concetto centrale nel modello formale è l’implicazione tra espressioni, che è fondamentale per definire
come l'avversario riesca (o non riesca) a ricavare parti dell'espressione partendo da altre parti
dell'espressioni!
Informalmente, un’espressione
(
) quando conoscere
Nell'esempio iniziale
, infatti se l'avversario conosce la chiave, allora può ricostruire tutto
il messaggio ma se ad esempio conoscesse solo
permette di ricostruire
.
allora non potrebbe fare nulla!
Descriviamo formalmente la cosa tramite il seguente sistema formale composto da regole (che sono
judgement):
I primi due sono assiomi,
e
a differenza delle chiavi sono noti a tutti, sono espressioni atomiche che
a differenza di qualunque altra chiave l'avversario conosce sempre. Questo spiega sempre perché i
booleani sono diverse dalle chiavi;
Se riesco a ricavare da
un messaggio
che posso ricavare la coppia
ricavare
o
e dallo stesso messaggio
un messaggio
. Dualmente se riesco a ricavare la coppia
allora vuol dire
vuol dire che posso
;
Ci sono poi le ultime due regole che riguardano i crittogrammi:
Se vogliamo ricostruire un crittogramma, devo essere in grado a partire da un messaggio
ricostruire
e
e a quel punto posso ricostruire
di
.
Se invece, voglio ottenere un messaggio in chiaro a partire da un crittogramma, l'unico modo è
conoscere la chiave!
C'è una differenza sostanziale nel modo in cui trattiamo i crittogrammi e le coppie: i crittogrammi nono
sono altro che coppie speciali in cui per conoscere la prima componete devo conoscere la seconda!
Esempio
Nel primo esempio il messaggio
segue dal messaggio iniziale perché lo abbiamo e viaggia in chiaro!
Nel secondo esempio, invece, facciamo seguire il messaggio
, che è la chiave di cifratura del blocco
Tuttavia l'avversario non potrebbe risalire a
che riusciamo sempre a ricavare poiché
.
.
Il modello, informalmente
Nel modello formale, il sistema diventa un insieme di processi che interagiscono scambiandosi
messaggi (prendiamo come esempio
di Needham-Schroeder).
Si possono, ad esempio, usare formalismi come le algebre di processi, in cui i processi vengono visti come
espressioni algebriche che riusciamo a trattare in modo opportuna grazie alla matematica, o come le
strutture di kripke, ottime per formalizzare questi sistemi, soprattutto se composte in maniera parallela.
L'avversario non è nient'altro che uno dei possibili processi!
L'avversario interagisce con il protocollo, in ogni momento, interferendo nella comunicazione in maniera
passiva e attiva:
Leggendo i messaggi scambiati (incrementando la propria base di dati);
Inviando uno qualunque tra i messaggi "implicati" dai messaggi che ha precedentemente letto. Poiché i
canali non sono autenticati, il ricevente non si accorge che questo messaggio è inviato dall'avversario.
In pratica l'avversario sfrutta la propria base di dati, applicando la relazione di implicazione, per
generare messaggi ed inviare dei messaggi "implicati" (tipico esempio di attacco replay) per interagire
con le altre parti.
Implicazione e avversario
La relazione di implicazione è un buon modo di modellizzare le capacità dell’avversario nel modello
formale:
Se
è un insieme di espressioni, allora
è ciò che l’avversario riesce a calcolare a
partire da .
Ad ogni passo, l’avversario potrà calcolare qualunque espressione tra quelle in tale insieme ed utilizzarla
per costruire un attacco.
Equivalenze tra espressioni
Allo scopo di confrontare il modello formale e quello computazionale, vale la pena parlare di equivalenze
tra espressioni.
Due espressioni si considerano equivalenti se sono indistinguibili di fronte ad un avversario il cui compito
sia quello di “separarle”. Quindi non devono essere per forza identiche, bastano che siano indistinguibili per
l'avversario!
Esempio 1:
In questo caso, i due messaggi sono distinguibili per l'avversario, infatti nel primo messaggio ho
con la
quale posso spacchettare la sotto-espressione mentre nel secondo caso no! Da qui capiamo che il conteso è
importantissimo e infatti l'avversario riesce a distinguere proprio grazie ad esso!
Esempio 2:
Questi messaggi sono indistinguibili per l'avversario, infatti, non conoscendo alcuna chiave lui vede due
messaggi in cui c'è uno 0 e un crittogramma!
Dobbiamo ora capire come formalizzare questa nozione ed in particolare dobbiamo capire cosa
l'avversario veda in un'espressione!
Patterns
Il modo in cui un avversario “vede” un espressione è catturato dalla nozione di pattern. Un pattern è ciò che
superficialmente l'avversario vede di un'espressione, senza ragionare, quando non consideri la presenza delle
chiavi.
Ad esempio, l'espressione formata dalla coppia (0 ,crittogramma), dal punto di vista dell'avversario contiene
un buco nero, cioè questo crittogramma di cui non sa nulla!
La grammatica dei pattern è, quindi, la stessa che usiamo per le espressioni ma c'è un box, che indica che
l'avversario non ha idea di ciò che sta vedendo, cioè una parte non intellegibile.
Bisogna anche dire che il pattern cui corrisponde un’espressione dipende però dall’insieme di chiavi
l’avversario dispone :
, di cui
Se l'avversario ha una chiave la vede in tutta la sua interezza ovviamente, a prescindere da ;
Se l'avversario vede un booleano la stessa cosa del precedente;
Stessa cosa per le coppie, però guardo ricorsivamente la prima componente e la seconda. La coppia ha
una struttura conosciuta, posso sempre andare a vedere dentro tutte e due le componenti, ipotizzando
di vedere la coppia in maniera chiara;
Cosa diversa è per i crittogrammi:
Se
è una chiave conosciuta che appartiene all'insieme , allora l'avversario vede il messaggio e lo
controlla ricorsivamente;
Altrimenti black box, se non ha la chiave, l'avversario non capisce nulla di quel crittogramma!
Esempio 1
Supponiamo di avere
. Allora:
Esempio 2
Con lo stesso
di prima, supponiamo di avere
infatti, avendo a disposizione
. Allora:
riuscirei quindi anche ricostruire
.
Definizione formale di pattern
Infine il modo in cui un avversario vede un'espressione
è catturato da :
Sto semplicemente dicendo che l'avversario può utilizzare, durante l'interpretazione di
non solo le chiavi
che ha a disposizione, ma anche tutte le chiavi che riesce a ricostruire a partire dal messaggio stesso!
Esempio 1
Supponiamo che
supponendo che
,e
allora
non facesse parte della sua conoscenza!
Esempio 2
Ma se gli passiamo un altro messaggio, ad esempio :
l'avversario potrebbe risalire completamente al messaggio
, infatti:
Piccola precisazione
Ricordiamo che ovviamente l'avversario può salvare le chiavi che scopre su un messaggio per poi riutilizzarle
su altri, pensiamo all'esempio di aver visto prima
e poi
...cosa vedrebbe l'avversario su
? Tutto!
Una definizione di equivalenza
Due espressioni
e
sono equivalenti, e scriveremo
, sse:
ovvero se le due espressioni sono indistinguibili agli occhi di ogni avversario.
Indebolimento dell'equivalenza
C'è però un problema: le chiavi vanno pensate come nomi! Infatti, due messaggi
ed
per la
nostra definizione non sarebbero equivalenti, ma dovrebbero! Dobbiamo fare lo stesso ragionamento che
facevamo con
nel lambda calcolo e lavorare con delle sostituzioni!
L'equivalenza va quindi indebolita ponendo:
dove
è una biiezione su
. Nel concreto lavoriamo con le sostituzioni!
Primo esempio
mentre sono equivalenti se effettuo la sostituzione
! Lavoreremo quindi
supponendo che questa sostituzione venga fatta sempre.
Altri esempi di equivalenza e non equivalenza
Mettere in relazione il modelli formale e quello
computazionale
Perché questa nozione di equivalenza è così interessante? Perché ci permettere di trovare una relazione
tra il modello formale e quello computazionale!
Una domanda naturale è la seguente: in che senso il modello formale è un’astrazione del modello
computazionale?
Bisogna prima di tutto capire a cosa corrisponde un’espressione
nel modello computazionale, dato uno
schema di codifica Π = (Gen,Enc,Dec):
Corrisponderà ad una famiglia di distribuzioni, parametrizzata su un parametro di sicurezza . Quindi
una mappa che ad ogni espressione binaria associ una certa probabilità.
Prima di tutto, ad ogni chiave K ∈ K^* che occorre in
Gen(1^n ). La generica chiave
faccio corrispondere la chiave ottenuta tramite
di un'espressione, è quindi il Gen calcolata on-the-fly ,che quindi ha una
probabilità uniforme di essere scelta! Gen ha bisogno del parametro di sicurezza e quindi ecco spiegato il
perché di ;
Poi, a
e
faccio corrispondere una stringa binaria che codifichi tale valore booleano. La distribuzione è
ovvia;
Le coppie
in
saranno codificate opportunamente come stringhe binarie;
Un crittogramma \{N\}_K che occorre in
, dovremo interpretare
, interpretare
, costruendo una
certa distribuzione, per poi calcolare Enc sulle interpretazioni!
La famiglia di distribuzioni dei vari elementi cui corrisponde
è indicata con [[M]]_\Pi.
Quindi l'interpretazione di un'espressione nel modello computazionale è una famiglia di distribuzioni, una
volta scelto uno schema di codifica che ci lascia interpretare crittogrammi.
Equivalenza tra distribuzioni
Quando due tali famiglie di distribuzioni sono equivalenti da un punto di vista computazionale? Quando
l'avversario ha una probabilità trascurabile di distinguerle!
Questa definizione l'abbiamo esplicitamente usata quando parlavamo di generatori pseudocasuali, quando
dicevamo che la distribuzione in output era indistinguibile applicando semplicemente la distribuzione
uniforme. Stessa cosa di quando parlavamo della sicurezza di uno schema di codifica.
Ipotesi sull'aciclicità
Come detto, un’espressione
occorre in
si dice aciclica sse per ogni sotto espressione \{N\}_K di
, la chiave
non
.
Teorema di Abadi & Rogaway
Se due espressioni sono equivalenti in senso formale la loro semantica (le famiglie di distribuzioni) è
indistinguibile, nel senso computazionale!
Lo scotto che si paga per questo risultato è che le espressioni devono essere acicliche e che dobbiamo avere
un cifrario sicuro nel senso CPA!
Conclusioni sulla crittografia formale
Cosa significa tutto questo? Se diamo prove di sicurezza nel modello formale, che è semplice, possiamo dare
delle prove valide anche nel modello computazionale! E' quindi il protocollo, se dimostrato sicuro per il
modello formale, è sicuro anche per quello computazionale!
Ovviamente questo importate risultato di correttezza vale quando le primitive utilizzate sono sicure!
Scarica