Questa è una buona domanda.
Dobbiamo prima dare una precisione: molte funzioni unidirezionali, in particolare la funzione hash come comunemente usata in crittografia, accettano input da uno spazio che è molto più grande di lo spazio dei valori di output. Ad esempio, SHA-256 è definito per ingressi che sono stringhe fino a 18446744073709551615 bit; ci sono 2 18446744073709551616 -1 possibili ingressi, ma poiché l'uscita è sempre una sequenza di 256 bit, ci sono solo 2 256 possibili uscite per SHA-256. Necessariamente, alcuni input distinti producono lo stesso output. Pertanto, per un dato output di SHA-256, non è possibile recuperare in modo univoco l'input che è stato utilizzato, ma, forse, potrebbe essere possibile calcolare un input che restituisce il valore di output dato. Preimage resistance riguarda questo: la difficoltà di trovare un input corrispondente per un output (indipendentemente da come tale output è stato ottenuto in primo luogo).
Quindi parliamo di una funzione che tutti possono calcolare su qualsiasi input (utilizzando un programma pubblicamente noto, nessun valore segreto coinvolto - non stiamo parlando di crittografia).
Cosa dicono gli accademici
Non è chiaro se le funzioni unidirezionali possano effettivamente esistere. In questo momento, abbiamo molte funzioni che nessuno sa come invertire; ma questo non significa che siano impossibili da invertire, in senso matematico. Nota, tuttavia, che non è dimostrato che le funzioni unidirezionali non possano esistere, quindi la speranza rimane. Alcune persone sospettano che se le funzioni unidirezionali possano esistere o meno potrebbe essere una di queste fastidiose asserzioni matematiche che non possono essere né provate né confutate (il teorema di Gödel dimostra che tali cose devono esistere). Ma non c'è nemmeno una prova di questo.
Pertanto, non c'è nessuna prova che una data funzione hash sia davvero resistente alle preimmagini.
Ci sono alcune funzioni che possono essere collegate a noti problemi difficili. Ad esempio, se n è il prodotto di due grandi numeri primi, la funzione x ⟼ x2 mod n è difficile da invertire: essere in grado di calcolare radici quadrate modulo un numero intero non primo n (in generale) equivale a essere in grado di fattorizzare n , e questo problema è noto per essere difficile. Non dimostrato di essere duro, intendiamoci; solo che i matematici hanno cercato di fattorizzare in modo efficiente grandi numeri interi per (almeno) gli ultimi 2500 anni, e sebbene siano stati compiuti alcuni progressi, nessuna di queste persone intelligenti ha trovato un algoritmo davvero killer per questo. Il record mondiale per la fattorizzazione di un "modulo RSA" (un prodotto di due grandi numeri primi scelti a caso di lunghezze simili) è un intero a 768 bit.
Alcune funzioni hash basate su tali sono stati proposti "problemi difficili"; vedi ad esempio MASH-1 e MASH-2 (sul problema RSA) e ECOH (con curve ellittiche). Esistono solo poche di queste funzioni, perché:
-
Trasformare un "problema difficile" in una funzione hash sicura non è facile; ci sono molti problemi complicati. Ad esempio, mentre l'estrazione di radici quadrate modulo un n non primo è di solito difficile, ci sono valori per i quali l'estrazione della radice quadrata è facile.
-
Le prestazioni di tali funzioni hash tendono ad essere, diciamo, non ottimali. Come essere 100 volte più lento di uno SHA-1 più comunemente usato.
Il modo più "standard" di costruire una funzione hash è riunire i crittografi e farli rosicchiare alcuni progetti proposti; le funzioni che sopravvivono a tentativi crittoanalitici per alcuni anni sono quindi considerate "probabilmente robuste". Il concorso SHA-3 è un tale sforzo; il vincitore dovrebbe essere annunciato entro la fine dell'anno. Dei 51 candidati (quelli che hanno superato la fase amministrativa), 14 sono stati selezionati per il "round 2" e questi 14 sono stati esaminati relativamente da vicino da molti crittografi, e nessuno di loro ha trovato qualcosa che valesse la pena di dire sulle funzioni. L'elenco è stato ridotto a 5 e sarà ulteriormente ridotto a 1 "presto", ma non per motivi di sicurezza (la maggior parte dei dati effettivi riguardava le prestazioni, non la resistenza).
Cosa rende difficile invertire MD5
Dato che non sappiamo come provare che una funzione è difficile da invertire, il meglio che possiamo fare è darla una prova su una funzione specifica, in modo da avere una "intuizione" di come la funzione raggiunge la sua apparente resistenza.
Scelgo MD5, che è ben noto. Sì, MD5 è "rotto", ma è per le collisioni, non per le preimmagini. Esiste un noto attacco preimage che è, almeno in teoria, più veloce del modo generico (il "modo generico" è "fortuna", vale a dire provare gli input finché non viene trovata una corrispondenza trovato, per un costo medio di 2128 valutazioni poiché MD5 ha un'uscita a 128 bit; l ' attacco Sasaki-Aoki ha un costo 2 123.4 , che è più basso, ma ancora troppo alto per essere effettivamente provato, quindi il risultato è ancora teorico). Ma MD5 è relativamente semplice e ha resistito agli attacchi per un bel po 'di tempo, quindi è un esempio interessante.
MD5 consiste in una serie di valutazioni di una "funzione di compressione" su blocchi di dati. Il messaggio di input viene prima riempito, in modo che la sua lunghezza diventi un multiplo di 512 bit. Viene quindi suddiviso in blocchi da 512 bit. Uno stato di esecuzione a 128 bit (contenuto in quattro variabili a 32 bit chiamate A , B , C e D ) viene inizializzato su un valore convenzionale, quindi elaborato con la funzione di compressione . La funzione di compressione prende lo stato di esecuzione e un blocco di messaggi a 512 bit e li mescola in un nuovo valore per lo stato di esecuzione. Quando tutti i blocchi di messaggi sono stati così elaborati, il valore finale dello stato di esecuzione è l'output hash.
Quindi concentriamoci sulla funzione di compressione. Funziona in questo modo:
- Input: lo stato di esecuzione ( A B C D ) e un blocco di messaggi M . Il blocco del messaggio è di 512 bit; lo abbiamo diviso in 16 parole a 32 bit M0 , M1 , M 2 , ... M15.
- Risultato: il nuovo valore dello stato di esecuzione.
-
Elaborazione:
- Salva lo stato corrente in alcune variabili: A → A ', B → B' , C → C ' e D → D'
- Fai 64 round che assomigliano a questo:
- Calcola T = B + ((A + f i (B, C, D) + M k + X i ) <<< s i sub>) . Si legge così: calcoliamo una data funzione fi (una semplice funzione bit per bit, che dipende dal numero tondo i ) su B , C e D . Aggiungi a ciò il valore di A , una parola del messaggio Mk e una costante X i (le aggiunte vengono fatte modulo 232 ). Ruota il risultato a sinistra di alcuni bit (la quantità di spostamento dipende anche dal round). Infine, aggiungi B : il risultato è T.
- Ruota le parole di stato: D → A , C → D , B → C , T → B .
- Aggiungi i valori di stato salvati alle variabili di stato correnti: A + A '→ A , B + B' → B , C + C '→ C , D + D '→ D .
Il punto importante è che ci sono 64 round, ma solo 16 parole del messaggio. Ciò significa che ogni parola del messaggio entra in elaborazione quattro volte . Lo scrivo in grassetto perché è il punto centrale; la resistenza alle preimmagini deriva da quella caratteristica. La parola del messaggio utilizzata in ogni round è descritta nella specifica MD5 (RFC 1321); la specifica descrive anche le funzioni fi , la rotazione conta si e le costanti a 32 bit X i .
Ora supponi di voler "invertire" MD5; si parte dall'output e si aumenta lentamente la funzione di compressione. Per prima cosa, devi decidere l'output del round 64. In effetti, l'output della funzione di compressione è la somma dell'output del round 64 e lo stato salvato (il A 'B' C Valori "D" ). Non hai nessuno dei due, quindi devi scegliere. La tua speranza è che sarai in grado di trovare valori per le parole del messaggio che ti consentiranno di ottenere per l'input del primo round dei valori coerenti con la tua decisione arbitraria su A ' e sui suoi fratelli.
Vediamo come vanno le cose quando cammini indietro nella funzione di compressione. Hai l ' output di un round (le variabili A , B , C e D dopo il round) e vuoi ricalcolare l ' input di quel round. Conosci già i valori precedenti di B , C e D , ma per A e M k hai molta scelta: ogni valore a 32 bit è possibile per A e ognuno ha un corrispondente M k sub > . All'inizio sei contento di questo; chi rinnegherebbe tale libertà? Basta scegliere un Mk casuale e questo produce il corrispondente A con poche operazioni (provalo!).
Ma dopo aver invertito in questo modo 16 round (i round da 49 a 64, poiché stai lavorando all'indietro), la libertà scompare. Hai "scelto" i valori di tutte le parole del messaggio. Quando si tenta di invertire il round 48, si desidera ricalcolare il valore di A appena prima di quel round; secondo la specifica MD5, la parola del messaggio M2 viene utilizzata nel round 48 e hai già scelto il valore di M 2 (quando si inverte il round 63). Quindi c'è solo una scelta per A . Quindi cosa, diresti. Una scelta è sufficiente per continuare la camminata all'indietro. Quindi continui.
Ora sei all'inizio della funzione di compressione. Ricorda che, inizialmente, hai fatto una scelta arbitraria dei valori di A 'B' C 'D' : questo ti ha permesso di calcolare l'output del round 64 e iniziare il cammino all'indietro. Ora hai ottenuto l'input del round 1, che dovrebbe essere identico a A 'B' C 'D' ... e non corrisponde. È abbastanza normale: hai scelto arbitrariamente A 'B' C 'D' e hai anche scelto arbitrariamente le parole del messaggio Mk , quindi ci si può aspettare che non funzioni per la maggior parte del tempo. Quindi provi a riparare il calcolo, alterando retrospettivamente la tua scelta iniziale di A 'B' C 'D' , o una o più scelte casuali per M k . Ma ogni modifica su qualsiasi Mk implica modifiche altrove, perché ogni Mk viene utilizzata quattro volte. Quindi hai bisogno di altre modifiche per cancellare le altre, e così via ...
A quel punto inizi a capire il problema dell'inversione di MD5: ogni volta che tocchi un solo bit, si innesca un terribile molte modifiche in tutto l'algoritmo, che è necessario annullare toccando altri bit, e ci sono troppe interazioni. Fondamentalmente, giochi con 2128 palline contemporaneamente, e questo è troppo per tenerne traccia di tutte.
Se ogni blocco di messaggio era lungo 2048 bit, suddiviso in 64 parole e ogni parola di messaggio veniva utilizzata solo una volta in MD5, è possibile invertirla facilmente. Fai come sopra: selezione arbitraria di LA 'B' C 'D' , selezione arbitraria delle parole del messaggio per i round 64-5; e per i primi quattro round, considera solo il valore che desideri ottenere per l'input del round (il valore che corrisponde alla tua scelta arbitraria di A ', B' , C ' o D' ) e trova la parola del messaggio corrispondente. Facile come una torta. Ma MD5 non elabora i dati per blocchi da 2048 bit, ma per blocchi da 512 bit e ogni parola di messaggio viene utilizzata quattro volte.
Alcuni colpi di scena aggiuntivi
La struttura della funzione di compressione di MD5 è in realtà una generalizzazione di un cifrario Feistel. In un cifrario Feistel, i dati sono divisi in due metà e, per ogni round, ne alteriamo una metà aggiungendola / xorandola a un valore intermedio che viene calcolato dall'altra metà e dalla chiave; e poi scambiamo le due metà. Estendi questo schema a una divisione in quattro parti e ottieni la stessa struttura dei round MD5 - con una rotazione di 90º: MD5 sembra la crittografia dello stato corrente usando il blocco dei messaggi come chiave (e c'è l'aggiunta extra dell'output del round 64 con lo stato salvato, che allontana MD5 da un cifrario ruotato).
Quindi forse possiamo costruire funzioni hash fuori blocco cifre? In effetti possiamo: questo è ciò di cui parla Whirlpool. Una funzione hash costruita su un cifrario a blocchi ruotato (il blocco del messaggio è la chiave); il codice a blocchi di Whirlpool è "W", un derivato di Rijndael, meglio conosciuto come AES. Ma W ha blocchi più grandi (512 bit invece di 128 bit) e una pianificazione delle chiavi riforgiata.
Quando si crea una funzione hash da un cifrario a blocchi ruotato, gli attacchi preimage alla funzione hash sono in qualche modo equivalenti agli attacchi di ricostruzione chiave sul cifrario a blocchi; quindi c'è qualche speranza che se il cifrario a blocchi è sicuro, lo è anche la funzione hash. Anche in questo caso, ci sono dettagli irriverenti. Inoltre, per una tale struttura, le collisioni sulla funzione hash sono come gli attacchi con chiave correlata al cifrario a blocchi; gli attacchi con chiave correlata sono generalmente considerati non fatali e spesso ignorati (ad esempio, non facevano parte dei criteri di valutazione per la competizione AES, e Rijndael è reputato un po 'instabile sotto questo aspetto, motivo per cui W ha una chiave nuova di zecca pianificazione).
Alcuni progetti più recenti sono costruiti su un cifrario a blocchi che non viene ruotato, in modo che la sicurezza della funzione hash possa essere derivata più direttamente dalla sicurezza del cifrario a blocchi; si veda ad esempio il candidato SHA-3 Skein, definito su un cifrario a blocchi chiamato Threefish.
Al contrario, si potrebbe provare a creare un cifrario a blocchi da una funzione hash. Vedi ad esempio SHACAL, che è SHA-1 "impostato in posizione verticale". E, al momento giusto, SHACAL ha alcune debolezze chiave correlate che sono abbastanza simili alle debolezze note di SHA-1 per quanto riguarda le collisioni (non è stata calcolata alcuna collisione effettiva, ma abbiamo un metodo che dovrebbe essere quasi un milione di volte più veloce del algoritmo generico di ricerca delle collisioni).
Pertanto, contrariamente a quanto ho detto nell'introduzione di questo post, abbiamo sempre parlato di crittografia . C'è ancora molto da scoprire e studiare sui collegamenti tra le funzioni hash e la crittografia simmetrica.
TL; DR: non c'è TL; DR per questo messaggio . Leggi tutto o vattene.