Non esistono pubblicazioni riguardo ad attacchi al DES portati con successo, ma esistono attacchi che funzionano per opportune varianti del DES (vedi crittoanalisi differenziale) e che danno un'idea della sicurezza del sistema. D'altra parte non è stata nemmeno dimostrata la non attacabilità del DES.
Un primo semplice attacco è quello di forza bruta: vediamo fin dove possiamo giungere con questa strategia. \ Questo tipo di attacco è universale, nel senso che va bene per qualsiasi sistema.
Osserviamo innanzitutto che, a dispetto delle chiavi possibili, in
media bastano
tentativi.
Se volessimo analizzare il DES con modalità operative come l'OFB, l'OCB o
altre (vedi sezione Modalità Operative), l'analisi risulterebbe enormemente complessa. Scegliamo perciò il
caso piú semplice.
Il DES codifica 64 bit con chiave a 56 bit, e produce un output di 64 bit.
Portiamo un attacco di tipo known plaintext.
Un attacco esaustivo richiederebbe di provare tutte e chiavi con:
Per far fronte alla dimensione del tempo sono state proposte macchine estremamente veloci. Queste macchine dovrebbero avere tanti processori paralleli ognuno dei quali testa una chiave. Ci si è posti il problema di determinare:
Con macchine di questo tipo il tempo è enormemente ridotto, mentre paghiamo il guadagno in termini di spazio oltre che di soldi.
Cambiamo adesso strategia di attacco. Portiamo un attacco di tipo Chosen plaintext. Dopo un certo numero di tentativi si vorrà essere capace di trovare la chiave.
Il metodo piú immediato è quello di provare (in monoprocessore) tutte le chiavi. Le risorse richieste sono:
Questa via è però impraticabile. Per far fronte ad un valore di tempo cosí grande possiamo immaginare un algoritmo che sacrifica dello spazio per guadagnare in tempo.
L'algoritmo lavorerebbe nel seguente modo. Per un dato testo in chiaro x
precomputa per ogni K tra le
chiavi possibili.
Manteniamo una tabella di coppie
ordinate per testo cifrato.
Dopo aver ottenuto y, il testo cifrato di x con chiave sconosciuta, cerchiamo y nella tabella e teniamo memoria della chiave K associata. Questa è la chiave cercata.
Se la ricerca è fatta in modo binario il tempo è logaritmico; se la tabella è mantenuta come una tabella hash il tempo di ricerca è addirittura costante.
In definitiva, pagato il tempo di pre-computazione (dell'ordine di , già troppo!),
la ricerca della chiave prende un tempo costante mentre il mantenimento della
tabella è all'ordine di
.
Anche questo algoritmo non è implementabile perché spazi di queste dimensioni sono troppo costosi oltre che tecnicamente difficili da costruire.
Se questi algoritmi gravano troppo o solo sul tempo o solo sullo spazio diventano non implementabili. Cerchiamo perciò di fare un trade off tra i due algoritmi proposti.
L'attacco che adesso analizzeremo richiede un tempo ridotto rispetto alla
ricerca esaustiva e uno spazio ridotto rispetto al
mantenimento della tabella delle coppie .
Il nuovo algoritmo richiede una funzione di riduzione
che riduce
una stringa binaria di 64 bit ad una a 56 bit
(F potrebbe semplicemente eliminare gli ultimi 8 bit).
Sia x un fissato testo in chiaro di 64 bit e X(1,0) una stringa arbitraria di 56 bit. Interpretiamo X(1,0) come una chiave per codificare x:
A questo punto y è una stringa a 64 bit, e con l'ausilio di F otteniamo X(1,1) a 56 bit:
Potremo ancora interpretare X(1,1) come una chiave per codificare x:
Ripetiamo questo procedimento per t volte ottenendo:
Abbiamo ottenuto una sequenza di stringhe di 56 bit, logicamente correlate, che possono essere interpretate sia come messaggi codificati (e poi ridotti) e sia come chiavi di cifratura.
È chiaro che se invece di scegliere la sola stringa X(1,0), scegliamo
m stringhe diverse , tutte di 56 bit, possiamo
applicare il procedimento illustrato per X(1,0) a tutte queste stringhe
ottenendo la Tabella 10. Ogni elemento di questa tabella (eccezion fatta per la prima colonna o colonna 0) è computato secondo la
seguente regola:
per e
.
0 | 1 | 2 | .... | t | ||||
X(1,0) | ![]() | X(1,1) | ![]() | X(1,2) | ![]() | .... |
![]() | X(1,t) |
X(2,0) | ![]() | X(2,1) | ![]() | X(2,2) | ![]() | .... |
![]() | X(2,t) |
X(m,0) | ![]() | X(m,1) | ![]() | X(m,2) | ![]() | .... |
![]() | X(m,t) |
A questo punto non è chiaro né come utilizzare la Tabella 10, né quali sono i valori di m e t ottimali. Rispondiamo ora alla prima domanda.
Dal testo in chiaro x otteniamo il messaggio y cifrato con la chiave sconosciuta K. Stiamo cercando di scoprire K. Cercheremo perciò y nell'ultima colonna; ma y è a 64 bit mentre gli elementi della tabella sono a 56 bit. Per questo motivo non verrà cercato y ma F(y), la riduzione di y.
Supponiamo che F(y) è presente nell'ultima colonna, e sia X(i,t) = F(y). X(i,t) è la riduzione di
, per cui X(i,t-1) è candidato a essere la chiave cercata.
Il dubbio che X(i,t-1) sia la chiave giusta è legittimo, e dovuto al fatto che X(i,t) = F(y) è
la riduzione
di y, e non y stesso. Si potrebbe cioè avere che F(y)=X(i,t) e
ma
.
Non ci resta perciò che cifrare x secondo X(i,t-1) e verificare se la cifratura è proprio y.
In caso affermativo ci fermiamo concludendo con successo. In caso negativo andiamo avanti.
Sia nel caso in cui F(y) non è presente nell'ultima colonna, sia quando F(y) c'è ma la presunta chiave si rivela sbagliata, si procede come segue.
Adesso la colonna t-1 prenderà il ruolo della colonna t, e la colonna t-2 (quella che precede t-1) prenderà il ruolo di t-1. Questa modalità puó essere ripetuta fino ad esaurire tutta la tabella (cioè fino a raggiungere la colonna 1).
Anche se ci convinciamo che questo algoritmo funziona, non è chiaro dove è la sua convenienza rispetto a quelli già illustrati. Anzi, sembrerebbe addirittura il peggiore tra quelli esposti. Ebbene, l'intero procedimento appena illustrato puó essere implementato mantenendo soltanto la colonna 0 e la colonna t. Vediamo come.
È evidente che conviene mantenere ordinata la colonna t (meglio se utilizziamo una tabella hash). Adesso si puó cercare F(y) efficientemente nell'ultima colonna. Se tale valore è presente e F(y)=X(i,t) la presunta chiave è X(i,t-1) che non ho piú. Ma dal valore X(i,0) potremo ricalcolare il valore desiderato in t-1 applicazioni ripetute del DES.
Se X(i,t-1) non è la chiave giusta procederemo cercando nella colonna t-1, che però non manteniamo in memoria. Il ricalcolo di tutta la colonna t-1 è troppo costoso, per cui converrà seguire un'altra strada.
Se F(y) è nella colonna t-1, vuol dire che è nella colonna t. Potremo perciò utilizzare
ancora la colonna
t cercando in essa non piú F(y), ma la riduzione della cifratura di x secondo la chiave F(y). Se la
ricerca
termina con successo (diciamo che X(j,t-1) è l'elemento cercato) il candidato ad essere la chiave è
X(j,t-2),
che non abbiamo piú, ma che possiamo ricalcolare da X(j,0) con l'applicazione ripetuta di DES.
Se anche sulla colonna t-1 il processo di ricerca della chiave fallisce, tentiamo con la colonna t-2 che nemmeno abbiamo. Ma se F(y) è in questa colonna vuol dire che
dove
Questo perché se F(y) si trova nella colonna t-2 allora si trova nella colonna t-1 e
si trova nella colonna t.
Quindi, ancora una volta puó essere utilizzata la sola colonna t. E questo per tutte le fasi della computazione.
In sintesi l'algoritmo (senza precomputazione) è il seguente:
In definitiva, pagato il prezzo iniziale di precomputazione per il calcolo di tutti i valori X(i,s), basterà mantenere le sole colonne 0 e t guadagnando perciò in spazio, ma pagando in termini di tempo per la produzione delle presunte chiavi e degli elementi da cercare nell'ultima colonna.
Purtroppo puó capitare che, pur trovando diversi F(y) nella Tabella 10, la chiave associata non è quella
giusta, e questo perché per ogni u nel codominio di F ci sono valori y nel dominio tale che
F(y) = u.
Possiamo a questo punto costruire pitabelle del tipo Tabella 10, ripetendo per ognuna di esse tutto l'algoritmo visto in precedenza. Tuttavia, anche utilizzando pitabelle la ricerca puó fallire.
La filosofia che sta alla base di questo algoritmo è la seguente: la tabella completa puó essere molto grande, per cui converrà memorizzare non tutti gli elementi ma solo alcuni. Daltronde questo guadagno in termini di spazio verrà pagato in termini di tempo.
Ci restano ancora da definire i valori di m, t, e il numero di tabelle, tutti
valori vitali per l'efficacia dell'algoritmo.
Senza entrare nei dettagli diciamo che l'analisi probabilistica mostra che se allora
la probabilità che la chiave si trovi
nella tabella è circa
.
Si consiglia di scegliere i seguenti valori:
Procediamo con l'analisi delle complessità con questi valori.
Lo spazio complessivo è:
Il tempo di precomputazione è:
Per l'analisi del tempo effettivo di calcolo (al netto della precomputazione) procediamo nel seguente modo: il caso pessimo consiste nello scandire tutte le tabelle senza trovare la chiave. Supponiamo che il Passo 3. dell'algoritmo fallisce sempre (test sempre negativo). La ricerca puó essere efficientemente implementata con le tabelle hash, per cui il tempo complessivo di ricerca è:
Un'analisi piú dettagliata (che non facciamo) puó dimostrare che anche se vengono eseguiti i passi da 4. a 6. (il test al passo 3. non fallisce sempre, ma fallisce sempre il test al passo 6.) il tempo di computazione cresce solo di un fattore costante.