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!