One Time Pads
From UIC
One Time Pads
Contents |
| Infos | |
|---|---|
| Author: | quequero |
| Email: | |
| Website: | http://quequero.org |
| Date: | 21/04/2007 (dd/mm/yyyy) |
| Level: |
|
| Language: | Italian |
| Comments: | Non sono pigro, ho solo un'alta energia di attivazione... |
Introduzione
Nel corso di questo articolo illustrerò il funzionamento degli OTP e principalmente quali sono i pericoli associati al riutilizzo del keystream. Questo secondo punto sarà il cardine dell'articolo poiché nella letteratura tradizionale si parla sempre di non-riutilizzo, ma mai se ne mostrano i rischi concreti.
Infine, per rendere il gioco più interessante, ho creato una piccola sfida con cui potrete cimentarvi dopo aver letto questo paper, ed il premio sono sicuro che vi piacerà.
Tools & Files
- OTP - OTP Encrypter/Decrypted
- OTP Bruteforcer - Programma per il bruteforcing degli OTP
- Messaggi Cifrati - I messaggi che decifreremo nel corso del paper
- Wordlist - Una wordlist con tutte le parole italiane
- Final Challenge - Il gioco a premi, risolvetelo e come ricompensa otterrete...
- Una calcolatrice capace di calcolare lo Xor
One Time Pads
OTP è l'acronimo che identifica i One Time Pads (o blocchi mono-uso se preferite l'italiano), si tratta essenzialmente di un cifrario di Vernam, il cui funzionamento è semplice: si genera una chiave casuale lunga quanto il testo da cifrare, e poi si xora questa chiave con il testo. Il ricevente, grazie alle proprietà dello Xor, non dovrà far altro che xorare la stessa chiave col blocco cifrato per riottenere il testo. Il procedimento è assolutamente identico a quello adottato nella cifratura con RC4, ovvero:
- Definiamo come K un byte della chiave.
- Definiamo come P un byte del testo da cifrare (plaintext).
- Definiamo come C un byte del test cifrato (ciphertext).
La cifratura avviene in questo modo:
- P ^ K = C
E l'operazione inversa sarà ovviamente:
- C ^ K = P
Quindi nulla di nuovo, stiamo semplicemente applicando la proprietà commutativa dello xor. Per quanto semplice possa sembrare, sappiate che questo genere di cifrari è l'unico che è stato matematicamente dimostrato1 come sicuro, per la precisione fu il grande Shannon a pubblicarne nel 1948 la dimostrazione nel suo A mathematical theory of communication. Si, anche RSA è sicuro, il problema è che con un computer potente è sempre possibile fattorizzare la chiave, e non è detto che in futuro non si scopra un algoritmo in grado di scomporre un numero in fattori primi. Nel caso degli OTP il discorso è diverso, non importa infatti la potenza del computer perché il testo cifrato non espone assolutamente nulla del testo in chiaro, e poi ad ogni keystream corrisponde un plain-text differente. L'ulteriore vantaggio è che non sarà possibile distinguere una trasmissione di dati random dalla trasmissione di un flusso cifrato con un OTP... A differenza di quanto abbiamo visto con RC4.
Keystream Reuse
Abbiamo detto che la chiave per la cifratura deve essere necessariamente casuale, se non fosse casuale... Significherebbe che si tratta di una sequenza predicibile, oppure biased in modo identificabile, per cui sarebbe semplice risalire al keystream, e da lì al contenuto in chiaro (parleremo di questo nella crittanalisi della funzione random()). La generazione di una chiave random non è un problema banale, qualunque sistema software difficilmente sarà in grado di produrre una distribuzione univoca ed uniforme, se non supportata da un apposito impianto di generazione hardware. Ma anche l'impianto di generazione hardware potrebbe non essere affidabile, fino a pochi anni fa, ed a volte anche oggi, si consigliava di utilizzare come sorgente di rumore bianco l'ingresso di una presa microfonica, senza il microfono inserito. Si tratta di una scelta pessima perché quel genere di rumore è semplice da modellare e prevedere, per cui la qualità della chiave ottenuta con questi metodi è bassissima. Attualmente possiamo generare una chiave realmente casuale soltanto in due modi:
- Utilizzando una sorgente radioattiva, e misurando accuratamente il tempo tra due eventi di decadimento (tramite un contatore Geiger ad esempio).
- Sparando un fotone su uno specchio semi-trasparente e vedendo se viene riflettuto o se attraversa lo specchio.
Il secondo metodo è estremamente accurato, ed esistono in commercio svariate schede PCI (che costano però abbastanza, dai 600$ in su) per generare numeri random ad alta velocità, tuttavia non utilizzano la tecnologia a singolo fotone, ma ne sparano un fascio e fanno la media sui sensori... Non è il massimo, ma la qualità dello stream ottenuta è comunque buona. Tutte le altre sorgenti di rumore, inclusi i generatori presenti sulle schede madri (che sono a tutti gli effetti dei sensori termici), sono in varia misura sempre abbastanza predicibili, ed in crittografia anche una predicibilità minima può compromettere seriamente qualunque algoritmo.
Detto questo, e supponendo di avere uno stream effettivamente casuale (potete usare /dev/random se non disponete di un generatore hardware), vediamo cosa succede se cifriamo due differenti messaggi con la stessa chiave:
- C1 = K ^ P1
- C2 = K ^ P2
Spostiamo K al primo membro da entrambe le equazioni:
- K = C1 ^ P1
- K = C2 ^ P2
E quindi proviamo a ricavarlo:
- C1 ^ P1 = C2 ^ P2
Guardate cosa succede:
- C1 ^ C2 = P1 ^ P2
Interessante! Se effettuiamo lo xor dei due testi cifrati, otteniamo lo xor dei due testi in chiaro. Ecco, qui è dove si fermano tutti quelli che parlano di OTP, ma vi dovreste chiedere: anche avendo lo xor dei due testi, cosa ci facciamo?
NSA vs KGB
Durante la guerra fredda le truppe Russe (probabilmente memori del fallimento di Enigma) scambiavano messaggi in questo modo: il messaggio veniva cifrato con un cifrario a sostituzione ed il risultato veniva di nuovo cifrato con un OTP. Veniva fornito agli operatori un libro contenente la soluzione del cifrario a sostituzione, ed un libro contenente una serie di caratteri casuali (in verità non erano il massimo della casualità, visto che venivano generati da una serie di operatori che pigiavano alla rinfusa su delle macchine da scrivere) che utilizzavano come chiave in maniera incrementale. La stragrande maggioranza di questi messaggi non venne mai decifrata dalla NSA (o meglio dal team del progetto Venona), nonostante la distribuzione dei byte della chiave non fosse realmente uniforme, tuttavia in alcuni casi ci riuscirono, ma come? Principalmente a causa dell'incuria degli operatori che reagivano in maniera errata alle richieste di ritrasmissione o che, semplicemente, riutilizzavano più volte la stessa chiave. C'e' da dire che le tecniche per risalire ad ogni singolo plain-text sono tutt'ora classificate, per cui non sappiamo cosa venisse effettivamente utilizzato per la crittanalisi dei messaggi. Ma poco importa, studieremo un nostro metodo e vedremo che ne esistono di altamente efficienti.
Crittanalisi
Abbiamo visto che xorare due messaggi cifrati con la stessa chiave porta, come risultato, ad avere i due plain-text cifrati tra loro, ma lo stesso principio vale se abbiamo n messaggi cifrati con la stessa chiave, quindi:
- C1 ^ C2... ^ Cn = P1 ^ P2... ^ Pn
Ed è abbastanza immediato capire che più sono i messaggi a nostra disposizione, più è semplice la fase di crittanalisi. Supponendo infatti di avere un messaggio cifrato, avremo una probabilità di 1/256 che pescando un carattere casuale e xorandolo col messaggio, otterremo il byte originale. Ma abbiamo la stessa probabilità anche sul secondo, terzo ed n-esimo carattere, quindi si tratta di una pesca decisamente complessa. La situazione non sembra cambiare molto nel caso si abbiano due messaggi cifrati con la stessa chiave, la probabilità che pescando un byte casuale, otterremo il byte originale, è sempre la stessa, ma con un bel vantaggio: se il byte pescato è quello giusto, otterremo non uno ma ben due byte di plain-text. Analogamente per n messaggi cifrati otterremo n byte di plain-text per ogni byte di chiave pescato bene. Ecco quindi che si apre uno spiraglio.
A questo punto possiamo fare una supposizione: il messaggio in chiaro sarà pur scritto in una lingua, quindi possiamo applicare al messaggio P1 ^ P2 ^ Pn le stesse procedure di analisi che applicheremmo ad un testo normale. Questa assunzione è abbastanza verosimile, perché se spiamo un avversario sappiamo anche il "tipo" di messaggi che lui invierà, ma vedremo a breve che gli stessi principi si applicano a qualunque distribuzione di dati non uniforme.
Decifriamo un Messaggio
Qui sotto troviamo due messaggi cifrati con la stessa chiave, sono gli stessi che trovate nel file: Messaggi Cifrati, chiaramente i byte che vedete qui sotto sono il corrispondente esadecimale di ogni carattere, visto che la maggior parte non sono stampabili:
BFB688095BF3EC5ADB907C97AE2B25666619859EDD6B79235322BA39
9EB77C82A8F7722EA0ABF0085F31B0F4805DF3C99489D7DC8FBBADE3
CD42B595ADBC29AB0CB9EF82850C4A8E5789FCABA2C3292B097F1B8C
7F31833EFB2007C72C70E4ECF02A69AAC9ED5F412C00D0611D74D86B
19224EDB582BCCBD924E694537F3DEC08DFDAD98D9627B0B8FCEC126
95275F7508F47F8EEF2666B6E0D6CDB292CA30094ACD15A3AE6C54C9
125BFABE32B9F8F8AA7F44C4DC1205F71380574700F400DC37D9BE98
768A9287
<b>Messaggio B:</b>
BAF7DB1241E4E15ED7C36198E8363E7D68019889942A70234E6BBE24
9DB17C86E5F53B6FA5E5E4025E2CBEEE9A5FF8808B8399C3DA2AE2E8
D858B792BAFD29B04DB8EB83831C4EC05288BFA2ABC32927037F178D
2B388427EA3D1FCB246BA5E4F82664A084E858052915C5241D70D875
0F2B41C2142DCCA8DD59740473F7C6CCD9E0BB92D97F710CC88DE93E
DC16572B49F365CBA239772BE0DBC2F69ECF3A014AC802B4E06818D4
0C14AEA537A2E2FCA83A59D9C04611EF13CE5A4600EA0C9A34C2B994
7F8D9208
La prima cosa che faremo sarà quella di xorare tra loro i due messaggi, in modo da ottenere, come risultato, lo xor dei due plain-text, da ora in poi il file risultante verrà chiamato: file finale. Per far questo useremo OTP, un piccolo programma che ho scritto appositamente per questo paper, compilatelo se siete su Linux (ci sono le istruzioni all'interno), oppure utilizzate l'exe se siete su Windows:
ed otterrete questo:
0541531B1A170D040C531D0F461D1B1B0E181D17494109001D49041D
030600044D024941054E140A011D0E1A1A020B491F0A4E1F55914F0B
151A02071741001B410104010610044E050143090900000C0A000C01
54090719111D180C081B4108080C0D0A4D050744051515450004001E
16090F194C0600154F171D414404180C541D160A001D0A0747432818
4931085E41071A454D1F119D000D0F440C050A08000517174E044C1D
1E4F541B051B1A0402451D1D1C541418004E0D01001E0C46031B070C
0907008F
A questo punto una piccola digressione è necessaria...
Un po' di Statistica
Se eseguiamo un'analisi statistica su un testo italiano, possiamo vedere che alcune lettere appaiono con frequenza maggiore di altre, e questo vale anche per alcuni digrammi/trigrammi. Quella che vedete qui sotto è una tabella con le frequenze relative di tutte le lettere dell'alfabeto, se i punteggi differiscono di qualche decimo da quelli che trovate sul vostro libro di statistica non fateci caso, ho ricalcolato le frequenze utilizzando una serie di testi stilisticamente differenti (trattati storici, romanzi, testi di legge etc...). Non mi sono basato, come spesso si fa, sull'analisi di un singolo romanzo/opera, perché per mille motivi possono esserci dei bias dovuti allo stile dell'autore.
| Lettera | Frequenza (%) | Lettera | Frequenza (%) |
|---|---|---|---|
| I | 11.5% | M | 2.3% |
| E | 11.5% | G | 2.1% |
| A | 11.1% | V | 1.4% |
| O | 8.8% | Z | 1.1% |
| N | 7.2% | F | 1.0% |
| L | 7.1% | B | 1.0% |
| T | 6.8% | H | 0.6% |
| R | 6.6% | Q | 0.2% |
| S | 4.6% | Y | 0.0% |
| D | 3.9% | K | 0.0% |
| C | 3.8% | W | 0.0% |
| U | 2.7% | X | 0.0% |
| P | 2.7% | J | 0.0% |
Le prime 4 lettere sono delle vocali e si distribuiscono in maniera uniforme tra loro, quindi non possiamo identificare una lettera predominante nella lingua italiana, come invece accade, ad esempio, per l'inglese con la lettera E. È da notare l'assenza delle lettere Y, K, W, X e J che sono presenti esclusivamente nei nomi di persone/località per lo più straniere. Passiamo ora all'analisi dei digrammi:
| Digramma | Frequenza (%) | Digramma | Frequenza (%) |
|---|---|---|---|
| ON | 2.2% | AL | 1.4% |
| RE | 1.7% | RA | 1.4% |
| ER | 1.7% | EN | 1.4% |
| DI | 1.7% | EL | 1.4% |
| CO | 1.6% | TA | 1.4% |
| DE | 1.6% | TI | 1.4% |
| AN | 1.5% | TO | 1.4% |
| RI | 1.5% | TE | 1.3% |
| LA | 1.5% | NE | 1.3% |
| IN | 1.5% | NT | 1.3% |
In questo caso il digramma ON spicca decisamente sugli altri, mentre tutte le altre coppie appaiono con frequenze molto simili tra loro. Passiamo ai trigrammi:
| Trigramma | Frequenza (%) | Trigramma | Frequenza (%) |
|---|---|---|---|
| DEL | 1.0% | PER | 0.5% |
| ENT | 0.9% | ALL | 0.5% |
| ELL | 0.9% | ICA | 0.4% |
| ION | 0.9% | MEN | 0.4% |
| CON | 0.8% | STA | 0.4% |
| LLA | 0.7% | TRA | 0.4% |
| ONE | 0.7% | TTO | 0.4% |
| ZIO | 0.6% | ATT | 0.3% |
| CHE | 0.6% | NTI | 0.3% |
| NTE | 0.6% | LLE | 0.3% |
E quindi passiamo ai tetragrammi, visto che ci saranno comunque molto utili:
| Tetragramma | Frequenza (%) | Tetragramma | Frequenza (%) |
|---|---|---|---|
| DELL | 0.8% | IONI | 0.2% |
| ZION | 0.7% | ELLE | 0.2% |
| IONE | 0.7% | PRES | 0.2% |
| ELLA | 0.5% | ENTO | 0.2% |
| MENT | 0.5% | TRAT | 0.1% |
| AZIO | 0.4% | NELL | 0.1% |
| ENTE | 0.4% | PART | 0.1% |
| ENTI | 0.2% | IFIC | 0.1% |
| ALLA | 0.2% | ISTI | 0.1% |
| AMEN | 0.2% | CONT | 0.1% |
Il Grande Assente
Ma forse dovremmo notare l'assenza di qualcuno, ovvero lui: . Non lo vedete?
Giusto è invisibile, forse se faccio così ci riuscite, lui: ' '. Ora dovreste vederlo, si trova tra due apici... Bravi! È proprio lui, lo spazio! Si perché il suo ruolo è fondamentale, in tutte le lingue con alfabeto latino è infatti il carattere più presente, molto più presente delle vocali, appare all'incirca con una frequenza doppia rispetto alla lettera più usata. Per cui forse è il caso di introdurre le relative tabelle dove è incluso anche lui, ci tornerà ugualmente utile. Salteremo quella delle singole lettere perché abbiamo già detto qual'e' la sua frequenza, e partiremo dai digrammi, lo spazio è rappresentato da un punto:
| Digramma | Frequenza (%) | Digramma | Frequenza (%) |
|---|---|---|---|
| E. | 3.1% | ON | 1.5% |
| A. | 2.9% | .C | 1.3% |
| I. | 2.4% | .A | 1.2% |
| O. | 2.3% | .S | 1.2% |
| .D | 2.1% | RE | 1.2% |
Procediamo con i trigrammi:
| Trigramma | Frequenza (%) | Trigramma | Frequenza (%) |
|---|---|---|---|
| .DI | 0.8% | TO. | 0.6% |
| LA. | 0.8% | DEL | 0.6% |
| .DE | 0.8% | E.D | 0.5% |
| .CO | 0.7% | ENT | 0.5% |
| DI. | 0.7% | ELL | 0.5% |
I tetragrammi utili sono invece di meno:
| Tetragramma | Frequenza (%) |
|---|---|
| .DI. | 0.7% |
| .DEL | 0.6% |
| LLA. | 0.4% |
| .LA. | 0.4% |
| .CON | 0.4% |
| DELL | 0.3% |
All'attacco!
Digressione terminata, è tempo di tornare a noi! Date le statistiche appena riportate, possiamo fare delle assunzioni abbastanza importanti sulla natura dei messaggi che stiamo per esaminare. Per prima cosa sappiamo che lo spazio è il carattere predominante, per cui ogni volta che nel file finale troveremo uno 0, potremo assumere che si tratti, con maggior probabilità rispetto agli altri casi, di uno spazio in quella posizione in entrambi i plain-text. La seconda assunzione è anch'essa interessante: sappiamo che le lettere maiuscole possono assumere valori esadecimali che vanno da 0x41 (la A) a 0x5A (la Z), e sappiamo che lo spazio equivale a 0x20, xoriamo questi due valori con lo spazio:
- 0x41 ^ 0x20 = 0x61 (corrisponde al carattere: a)
- 0x5A ^ 0x20 = 0x7A (corrisponde al carattere: z)
Adesso eseguiamo lo xor tra la Z e la A:
- 0x5A ^ 0x41 = 0x1B
Il primo teorema degli spazi
Per la linearità dello xor possiamo quindi affermare che:
Teorema 1: una lettera maiuscola xorata con uno spazio, non può essere uguale a nessuna coppia di lettere maiuscole xorate tra loro.
Percio' se siamo in grado di identificare la posizione di una lettera maiuscola in un plain-text, siamo anche in grado di trovare lo spazio corrispondente nell'altro plain-text, senza contare che se il messaggio è inviato tutto in caratteri maiuscoli, allora abbiamo appena trovato un modo di isolare tutti gli spazi. Basterà cercare tutti i byte maggiori o uguali a 0x61 (a) e tutti i byte minori o uguale a 0x7A (z).
Il secondo teorema degli spazi
A ben guardare forse possiamo estendere il nostro teorema anche ai caratteri minuscoli, facciamo una dimostrazione:
- 0x61 ^ 0x20 = 0x41 (corrisponde al carattere: A)
- 0x7A ^ 0x20 = 0x5A (corrisponde al carattere: Z)
Adesso eseguiamo lo xor tra la z e la a:
- 0x7A ^ 0x61 = 0x1B
Passiamo quindi alla definizione del secondo teorema:
Teorema 2: una lettera minuscola xorata con uno spazio, non può essere uguale a nessuna coppia di lettere minuscole xorate tra loro.
Ecco quindi che siamo in grado di identificare gli spazi anche in un messaggio inviato in caratteri minuscoli. Semplificando vuol dire che quando nel file finale troveremo un carattere maiuscolo, potremo dire con certezza che si tratta di un carattere minuscolo in un plain-text, e di uno spazio nell'altro plain-text. Niente male! Certo, non abbiamo considerato la distribuzione della punteggiatura, ma quella la estrapoleremo dal contesto.
Fase 1
Prendiamo i primi 4 byte del file finale (ma possiamo iniziare da dovunque), troviamo soltanto 2 caratteri stampabili e si tratta di lettere maiuscole, quindi per il secondo teorema degli spazi, possiamo affermare che ognuno di questi caratteri è uno spazio:
.AS.
non possiamo fare assunzioni sul primo carattere, cioe' non possiamo sapere se è minuscolo o meno, perché i due messaggi cifrati li ho presi casualmente da una serie di messaggi che avevo creato. Quindi neanche io so di cosa si tratti, ed in particolare non tutti i messaggi iniziavano con un carattere maiuscolo. L'unica assunzione che per il momento possiamo fare, è che il secondo e terzo byte difficilmente saranno due spazi consecutivi nello stesso messaggio, ma probabilmente si tratta di uno spazio nel file A e di uno spazio nel file B, in sostanza i messaggi si possono presentare in queste varianti:
1. x_xx
2. xx_x
<b>Possibili configurazioni per il file B</b>
1. xx_x
2. x_xx
Partiamo testando il caso 1. per il file A, e logicamente il caso 1. per il file B, perché non possiamo avere due volte la stessa configurazione, visto che spazio ^ spazio = 0, e noi non abbiamo zeri. Utilizzando la tabella della frequenza vediamo qual'e' il carattere singolo più frequente nella nostra lingua: la I. Assumiamo che sia maiuscola visto che si tratta della prima lettera, e scopriamo cosa viene fuori dal file A:
- 0x05 ^ 0x49 = 0x4C (L)
Abbiamo quindi ottenuto il primo byte del file A ed il primo del file B... Sempre che siano giusti ovviamente. Iniziamo a sostituirli e vediamo cosa viene fuori:
File B: Lx_x
Ma a questo punto possiamo verificare la nostra ipotesi in maniera semplice, dal momento che certamente il secondo byte del file A è uno spazio... Allora possiamo ricavare anche il secondo byte del file B:
- 0x20 ^ 0x41 = 0x61 (0x41 è il secondo byte del file finale)
Quindi abbiamo:
File B: La_x
Che in italiano potrebbe anche suonare bene. Non possiamo sapere a priori se realmente i primi due caratteri siano maiuscoli o minuscoli, ma questo non ci interessa perché non ci crea problemi se entrambi hanno lo stesso case. Avremmo invece avuto problemi nel caso in cui il primo carattere di A fosse stato maiuscolo e il primo di B minuscolo, o vice versa. Ma adesso possiamo ricavare, con lo stesso procedimento, anche il terzo byte del file A:
- 0x53 ^ 0x20 = 0x73 (s)
Risultato:
File B: La_x
Fase 2
Ora proviamo a spostarci un po' avanti nel file, e vediamo se riusciamo a scoprire la fine della parola dopo "I ", o la fine della parola dopo "La ", potrebbe tornarci davvero utile. Spostiamoci al nono byte, ovvero dove troviamo un secondo probabile spazio. Le configurazioni possibili sono le seguenti:
File A: I_sxxxxxxx
File B: La_xxxxxx_
<b>Caso 2:</b>
File A: I_sxxxxxx_
File B: La_xxxxxxx
Proviamo ad attaccare la prima parola del file B usando la tabella dei trigrammi senza gli spazi, vediamo infatti che il trigramma più comune che inizia con la S è STA, la s già la conosciamo, per cui proviamo a xorare le lettere TA sul file B facendo finta che la parola in A sia "sta...".
File B: La_o{.....
Niente, possiamo eliminare il trigramma STA perché la soluzione sembra poco plausibile. Proviamo con un altro approccio, nella lingua italiana possiamo fare l'assunzione sensata che ogni parola, più lunga di 3 lettere, finisce quasi sempre con una vocale. Prendiamo nuovamente la tabella delle frequenze, prendiamo il Caso 2 e supponiamo che l'ultima lettera della seconda parola sia una i, poi proveremo la e etc...
File B: La_xxxxxexxx_
Giunti a questo punto abbiamo bisogno di un computer per velocizzare la nostra ricerca, cercheremo ora di ricavare la seconda parola del file A, e qualche byte successivo, partendo dalla seconda parola del file B con una sorta di bruteforce. Utilizzeremo OTP Bruteforcer una piccola utility che ho scritto per questo paper, il programma prende in input un file cifrato ed una lista di parole, xora ogni parola con il file cifrato, e se il risultato è scritto in caratteri stampabili, lo mostra a schermo. Potrebbe essere una buona idea quella di installare MinGW se siete su windows, in modo da avere a portata di mano utility indispensabili come less e grep. Scompattiamo quindi la wordlist italiana e creiamo un piccolo file, contenente soltanto i byte del file finale che vanno dal quarto al tredicesimo:
Chiamiamolo cipher.bin, quindi da riga di comando richiamiamo il bruteforcer:
Ci penserà il programma ad utilizzare soltanto le parole della giusta lunghezza. Guardiamo ora l'output perché troveremo una serie di stringhe, quasi tutte inutili ad eccezione di una:
<b>"istemi in" richiesta</b>
"istemi ij" richieste
Sappiamo che la prima lettera è la s perché l'avevamo scoperta già prima, quindi gli unici risultati utili sono: "sistemi in" per il file A e "richiesta" per il file B. Il che ci porta in questa situazione:
File B: La_richiesta_
Lo spazietto dopo la seconda parola nel file B è abbastanza certo, per cui scopriamo un ulteriore byte:
File B: La_richiesta_
Ma ora ci troviamo di fronte ad una piccola ambiguità, i byte 20 e 21 sono entrambi degli spazi, tuttavia non conosciamo la lunghezza esatta né della parola del file A né della parola del file B... Come facciamo? Faremo un paio di tentativi ancora con l'OTP Bruteforcer, tanto sappiamo che il primo spazio appartiene al primo o al secondo file, per cui la parola che cerchiamo avrà al massimo due forme:
infxxxxxxxxx
Ma la radice inf- ci dice tanto, quindi potremo escludere molti casi. Terremo cifrata anche la radice, tra poco vedremo perché, ora creiamo nuovamente il nostro cipher.bin:
E bruteforziamo come prima, ma questa volta grepperemo tutte le parole che iniziano per inf- dal momento che siamo certi della presenza di questa particella... Ok non viene fuori niente, forse lo spazio che abbiamo incontrato è parte del secondo file e non del primo, quindi aggiungiamolo al nostro file cipher.bin
E ripetiamo il bruteforce:
"ta rivolxa " informatevi
"ta rivoltt " informatici
<b>"ta rivolta " informativi</b>
Cool! l'abbiamo trovato! A questo punto possiamo ampliare ancora di più i nostri plain-text, e poi il testo ottenuto per il file A è un byte più lungo rispetto a quello ottenuto per il file B, per cui possiamo ricavare un nuovo byte per B dal byte supplementare in A:
File B: La_richiesta_rivolta_a
Adesso le cose si complicano un pochino, se guardiamo ai byte successivi vediamo infatti che c'è un byte a 0x00, questo vuol dire che nel plain-text sono presenti due byte uguali, ma quali? Statisticamente si tratterà o di uno spazio o di una vocale... Ma se siamo sfortunati potrebbe trattarsi anche di un segno di punteggiatura. Supporre che si tratti di uno spazio potrebbe non essere errato perché nel file B la frase suggerisce poche alternative, la parola composta dalla a potrebbe essere: "a ", "ai ", "al ", "alle " etc... Quindi usiamo la statistica e supponiamo che quel byte a zero sia effettivamente lo xor di due spazi tra loro, ora dobbiamo scoprire da cosa è seguita la particella "a", potrebbe essere: "ai " oppure "al ", ma visto che la i è più comune della l, proviamo con questa e vediamo cosa otteniamo:
File B: La_richiesta_rivolta_ai_
Direi di no, proviamo con "al":
File B: La_richiesta_rivolta_al_
Ok funziona, ora dobbiamo scoprire un byte sconosciuto e quindi dobbiamo trovare in quale dei due plain-text si trova lo spazio che segue la lettera. Senza dubbio deve appartenere al primo file perché qualcosa nella forma: "La richiesta rivolta ai a/e/i/o/u xxxxx" ha poco senso, sostituendo lo spazio otteniamo:
File B: La_richiesta_rivolta_al_xi
supponiamo di nuovo che il byte incognito nel file A sia una i, anche perché è difficile che si tratti di altro, otteniamo:
File B: La_richiesta_rivolta_al_ti
Forse possiamo ottenere un bel po' di byte, "for free" grazie al bruteforcer, abbiamo davanti a noi un altro spazio sicuro, ma abbiamo anche un byte a 0x00, speriamo non si tratti di nuovo di uno spazio, altrimenti dovremo tornare indietro. Creiamo un nuovo cipher.bin e dentro mettiamo i byte cifrati fino allo spazio:
Bruteforziamo e greppiamo tutte le stringhe che iniziano per "ti" (in verità grepperemo tutte le stringhe che contengono la sillaba "ti" per evitare di utilizzare script come awk che magari non avete installati).
"i vxqcmk" tireremo
<b>"i progra" titolare</b>
"i progve" titolava
Anche questa è trovata sostituiamo ai nostri plaintext:
File B: La_richiesta_rivolta_al_titolare_
Senza neanche dirlo, completiamo l'ultima parola del primo file:
File B: La_richiesta_rivolta_al_titolare_o_a
Ora abbiamo nuovamente a che fare con uno spazio che molto probabilmente apparterrà al secondo file, sostituiamolo e scopriamo anche una nuova lettera, ma ci resta da scoprire la particella "ax" a cosa corrisponde. Non può trattarsi di "ai" perché altrimenti l'ultima parola del primo plain-text sarebbe: "ln....", quindi proviamo con "al":
File B: La_richiesta_rivolta_al_titolare_o_al_
Abbiamo di fronte una parola lunga almeno 11 caratteri nel file A, ma forse anche 14, quindi mettiamo mano al bruteforcer:
"l respohuld" informarono
"l responsaj" informatica
<b>"l responsab" informatici</b>
"l responsad" informatico
Niente male di nuovo, completiamo anche la parola del file B, verranno fuori altre parole, continuiamo a completarle fin dove possiamo e vediamo cosa ne esce:
File B: La_richiesta_rivolta_al_titolare_o_al_responsabile_pu
Adesso sorge un problema, la parola "pux" potrebbe essere completata con una o con accento grave o acuto (un errore può sempre esserci). Ma abbiamo la certezza di uno spazio, quindi sostituiamolo e sostituiamo anche la "o" con accento corretto, ovvero quello grave (byte: 0xF2):
File B: La_richiesta_rivolta_al_titolare_o_al_responsabile_può_
Abbiamo ancora una volta un'indecisione, la parola del file A potrebbe essere lunga sia 8 caratteri sia 11, perché abbiamo due spazi e non sappiamo quale dobbiamo utilizzare. Creiamo di nuovo il cipher.bin:
ma stavolta diremo al bruteforcer di mostrare tutti i caratteri alfanumerici, visto che è presente anche una lettera "speciale", ovvero la "ò":
. essere tz - configurata
<b>. essere tr - configurati</b>
. essere tt - configurato
Il punto iniziale è la "ò" che non viene mostrata, sostituiamo di nuovo e vediamo cosa è venuto fuori:
File B: La_richiesta_rivolta_al_titolare_o_al_responsabile_può_essere_tr
Ripetendo lo stesso procedimento per tutto il resto del file, ed utilizzando l'opzione "-A" del bruteforcer quando abbiamo a che fare con la punteggiatura, otteniamo finalmente tutto il messaggio:
I sistemi informativi e i programmi informatici sono configurati riducendo al minimo l'utilizzazione di
dati personali e di dati identificativi, in modo da escluderne il trattamento quando le finalità
<b>File B:</b>
La richiesta rivolta al titolare o al responsabile può essere trasmessa anche mediante lettera
raccomandata, telefax o posta elettronica. Il Garante può individuare altro idoneo sistema in riferimento
Conclusione
Abbiamo visto che il riuso del keystream presenta un pericolo reale, non solo teorico. Gli OTP vengono normalmente utilizzati lì dove la segretezza è un requisito fondamentale, come ad esempio nello scambio di informazioni tra le intelligence etc... Un utente "normale" difficilmente farà uso di OTP, tuttavia non dobbiamo dimenticare che gli stream-cipher, come ad esempio RC4, non fanno altro che generare un keystream e sono utilizzati sia a livello di networking: WEP ed SSL ne sono l'esempio principale, sia a livello applicativo. RC4 è infatti un algoritmo ampiamente utilizzato per la cifratura dei dati, ed usare due volte la stessa chiave, significa usare due volte lo stesso keystream!!! La cifratura del vecchio Word ne era un esempio, infatti cifrando con la stessa password due file .doc e xorandoli tra loro, si otteneva proprio lo xor dei due plaintext.
È sempre possibile il recovery?
È stata dimostrata2 la possibilità di recuperare qualunque genere di documento che segue un pattern non casuale. Ma questo è un concetto abbastanza intuitivo, possiamo infatti pensare che finché abbiamo a che fare con due messaggi, il cui contenuto non è preso da una distribuzione uniforme (cioe' non sono formati da dati random), possiamo predire i bias e riottenere i messaggi originali. Questo procedimento è tanto più semplice quanti più sono i messaggi cifrati con la stessa chiave. Nel caso analizzato nel corso del paper, se avessimo avuto 256 differenti messaggi cifrati con la stessa chiave, non avremmo avuto nessun byte incerto, cosa che invece è accaduta per il primo byte. Infatti non abbiamo alcun modo di sapere se le prime due lettere originali erano entrambe maiuscole, o entrambe minuscole. Da questo possiamo concludere che è sempre possibile risalire ai plain-text originali, a meno che i messaggi cifrati non siano degli stream casuali di dati. Quindi il monito è oramai ovvio: attenti, non riutilizzate la stessa chiave MAI, né con un OTP né con uno stream-cipher. In quest'ultimo caso concatenate sempre la chiave segreta ad un vettore di inizializzazione tramite una funzione di hash cyptographically-strong.
The Final Challenge
Prendete questo file, contiene due messaggi cifrati con la stessa chiave, il contenuto sono sicuro che vi interesserà parecchio. Dentro il readme.txt troverete qualche suggerimento. Chi riuscirà a crackarli, oltre ad ottenere accesso all'archivio che si trova nello zip, verrà menzionato qui sotto, ma i messaggi non saranno mai rivelati...
Special Mention
I nomi di coloro (se ce ne saranno) che sono riusciti a crackare la Final Challenge.
- Faina
- Saturn
- teDDy (che ha anche fatto un magnifico script con OpenOffice :))
Bibliografia
- C. E. Shannon. - A mathematical theory of communication.
- J. Mason, K. Watkins, J. Eisner, A. Stubblefield - A Natural Language Approach to Automated Cryptanalysis of Two-time Pads
Disclaimer
Non usate mai la stessa chiave per cifrare due messaggi, sia se usate un OTP sia se usate uno stream-cipher.