Bene, potresti cercarlo su Wikipedia ... Ma poiché vuoi una spiegazione, farò del mio meglio qui:
Funzioni hash
Forniscono una mappatura tra un input di lunghezza arbitraria e un output (di solito) a lunghezza fissa (o di lunghezza inferiore). Può essere qualsiasi cosa, da un semplice crc32, a una funzione di hash crittografica completa come MD5 o SHA1 / 2/256/512. Il punto è che c'è una mappatura a senso unico in corso. È sempre un mapping molti: 1 (nel senso che ci saranno sempre collisioni) poiché ogni funzione produce un output più piccolo di quello che è in grado di immettere (Se si alimenta ogni possibile file 1mb in MD5, si otterranno tonnellate di collisioni).
Il motivo per cui sono difficili (o impossibili nella pratica) da invertire è il modo in cui lavorano internamente. La maggior parte delle funzioni hash crittografiche ripetono più volte l'input impostato per produrre l'output. Quindi se osserviamo ogni blocco di input a lunghezza fissa (che dipende dall'algoritmo), la funzione hash chiamerà quello stato corrente. Quindi ripeterà lo stato e lo cambierà in uno nuovo e lo utilizzerà come feedback in sé (MD5 lo fa 64 volte per ogni blocco di dati a 512 bit). Quindi in qualche modo combina gli stati risultanti di tutte queste iterazioni insieme per formare l'hash risultante.
Ora, se vuoi decodificare l'hash, devi prima capire come dividere l'hash dato nei suoi stati iterati (1 possibilità per input più piccoli delle dimensioni di un blocco di dati, molti per input più grandi). Quindi dovrai invertire l'iterazione per ogni stato. Ora, per spiegare il motivo per cui questo è molto difficile, Immaginate di provare a dedurre a
e b
dalla seguente formula: 10 = a + b
. Ci sono 10 combinazioni positive di a
e b
che possono funzionare. Ora ripetilo più volte:tmp = a + b; a = b; b = tmp
. Per 64 iterazioni, avresti più di 10 ^ 64 possibilità da provare. E questa è solo una semplice aggiunta in cui un certo stato viene preservato dall'iterazione all'iterazione. Le funzioni hash reali eseguono molto più di 1 operazione (MD5 esegue circa 15 operazioni su 4 variabili di stato). E poiché la successiva iterazione dipende dallo stato della precedente e la precedente viene distrutta nella creazione dello stato corrente, è quasi impossibile determinare lo stato di input che ha portato a un determinato stato di output (per ogni iterazione non meno). Combina che, con il gran numero di possibilità coinvolte, e la decodifica anche un MD5 richiederà una quantità quasi infinita (ma non infinita) di risorse. Così tante risorse che
Funzioni di crittografia
Forniscono una mappatura 1: 1 tra input e output di lunghezza arbitraria. E sono sempre reversibili. La cosa importante da notare è che è reversibile usando un metodo. Ed è sempre 1: 1 per una determinata chiave. Ora, ci sono più input: coppie di chiavi che potrebbero generare lo stesso output (in realtà ce ne sono, a seconda della funzione di crittografia). I buoni dati crittografati sono indistinguibili dal rumore casuale. Questo è diverso da un buon output hash che ha sempre un formato coerente.
Casi d'uso
Utilizzare una funzione hash quando si desidera confrontare un valore ma non è possibile memorizzare la rappresentazione semplice (per un numero qualsiasi di motivi). Le password dovrebbero adattarsi molto bene a questo caso d'uso poiché non si desidera memorizzarle come testo normale per motivi di sicurezza (e non dovrebbero). E se volessi controllare un file system per i file musicali piratati? Non sarebbe pratico memorizzare 3 MB per file musicale. Quindi, invece, prendi l'hash del file e memorizzalo (md5 memorizzerebbe 16 byte invece di 3mb). In questo modo, devi solo eseguire l'hashing di ogni file e confrontarlo con il database memorizzato degli hash (questo non funziona altrettanto bene in pratica a causa della ricodifica, della modifica delle intestazioni dei file, ecc., Ma è un esempio di caso d'uso).
Utilizzare una funzione hash quando si verifica la validità dei dati di input. Ecco per cosa sono progettati. Se hai 2 input e vuoi verificare se sono uguali, esegui entrambi tramite una funzione hash. La probabilità di una collisione è astronomicamente bassa per input di piccole dimensioni (presupponendo una buona funzione hash). Ecco perché è consigliato per le password. Per password fino a 32 caratteri, md5 ha 4 volte lo spazio di output. SHA1 ha 6 volte lo spazio di uscita (circa). SHA512 ha circa 16 volte lo spazio di uscita. In realtà non importa ciò che la password era , ti importa se è la stessa di quella che è stata memorizzata. Ecco perché dovresti usare gli hash per le password.
Utilizzare la crittografia ogni volta che è necessario ripristinare i dati di input. Nota la parola bisogno . Se stai memorizzando i numeri delle carte di credito, devi recuperarli a un certo punto, ma non vuoi memorizzarli come testo semplice. Quindi, invece, archivia la versione crittografata e mantieni la chiave il più sicura possibile.
Le funzioni hash sono ottime anche per la firma dei dati. Ad esempio, se si utilizza HMAC, si firma un pezzo di dati prendendo un hash dei dati concatenati con un valore noto ma non trasmesso (un valore segreto). Quindi, si invia il testo normale e l'hash HMAC. Quindi, il destinatario esegue semplicemente l'hashing dei dati inviati con il valore noto e verifica se corrisponde all'HMAC trasmesso. Se è lo stesso, sai che non è stato manomesso da una parte senza il valore segreto. Questo è comunemente usato nei sistemi di cookie sicuri dai framework HTTP, così come nella trasmissione di messaggi di dati su HTTP dove si desidera avere una certa integrità nei dati.
Una nota sugli hash per le password:
Una caratteristica chiave delle funzioni crittografiche dell'hash è che dovrebbero essere molto veloci da creare e molto difficili / lenti da invertire (al punto che è praticamente impossibile). Ciò pone un problema con le password. Se immagazzini sha512(password)
, non stai facendo nulla per proteggerti da tavoli arcobaleno o attacchi di forza bruta. Ricorda, la funzione hash è stata progettata per la velocità. Quindi è banale per un utente malintenzionato eseguire un dizionario attraverso la funzione hash e testare ogni risultato.
L'aggiunta di un sale aiuta le cose poiché aggiunge un po 'di dati sconosciuti all'hash. Quindi, invece di trovare qualcosa che corrisponda md5(foo)
, hanno bisogno di trovare qualcosa che quando viene aggiunto al sale noto produce md5(foo.salt)
(che è molto più difficile da fare). Ma non risolve ancora il problema della velocità poiché se conoscono il sale è solo una questione di eseguire il dizionario.
Quindi, ci sono modi per affrontarlo. Un metodo popolare è chiamato rafforzamento delle chiavi (o allungamento delle chiavi). Fondamentalmente, ripeti più volte un hash (di solito migliaia). Questo fa due cose. Innanzitutto, rallenta significativamente il runtime dell'algoritmo di hashing. In secondo luogo, se implementato correttamente (passando l'input e saltando indietro su ogni iterazione) aumenta effettivamente l'entropia (spazio disponibile) per l'output, riducendo le possibilità di collisioni. Un'implementazione banale è:
var hash = password + salt;
for (var i = 0; i < 5000; i++) {
hash = sha512(hash + password + salt);
}
Esistono altre implementazioni più standard come PBKDF2 , BCrypt . Ma questa tecnica è utilizzata da parecchi sistemi relativi alla sicurezza (come PGP, WPA, Apache e OpenSSL).
La linea di fondo, hash(password)
non è abbastanza buona. hash(password + salt)
è meglio, ma non è ancora abbastanza buono ... Usa un meccanismo di hash esteso per produrre gli hash delle password ...
Un'altra nota sullo stretching banale
Non alimentare in nessun caso l'output di un hash direttamente nella funzione hash :
hash = sha512(password + salt);
for (i = 0; i < 1000; i++) {
hash = sha512(hash); // <-- Do NOT do this!
}
La ragione di ciò ha a che fare con le collisioni. Ricorda che tutte le funzioni hash hanno delle collisioni perché il possibile spazio di output (il numero di possibili output) è inferiore allo spazio di input. Per capire perché, guardiamo cosa succede. Per prefigurare questo, supponiamo che ci sia una probabilità dello collisione dello 0,001% sha1()
(è molto più basso nella realtà, ma a scopo dimostrativo).
hash1 = sha1(password + salt);
Ora hash1
ha una probabilità di collisione dello 0,001%. Ma quando facciamo il prossimo hash2 = sha1(hash1);
, tutte le collisioni hash1
diventano automaticamente collisioni dihash2
. Quindi ora abbiamo il tasso di hash1 allo 0,001% e la seconda sha1()
chiamata si aggiunge a quella. Quindi ora hash2
ha una probabilità di collisione dello 0,002%. Sono due volte più possibilità! Ogni iterazione aggiungerà un'altra 0.001%
possibilità di collisione al risultato. Quindi, con 1000 iterazioni, la possibilità di collisione è passata da un banale 0,001% all'1%. Ora, il degrado è lineare e le probabilità reali sono molto più piccole, ma l'effetto è lo stesso (una stima della probabilità di una singola collisione md5
è di circa 1 / (2 128 ) o 1 / (3x10 38). Anche se sembra piccolo, grazie all'attacco del compleanno non è proprio così piccolo come sembra).
Invece, aggiungendo nuovamente salt e password ogni volta, si reintroducono nuovamente i dati nella funzione hash. Quindi qualsiasi collisione di un round particolare non è più collisione del round successivo. Così:
hash = sha512(password + salt);
for (i = 0; i < 1000; i++) {
hash = sha512(hash + password + salt);
}
Ha le stesse possibilità di collisione della sha512
funzione nativa . Qual è quello che vuoi. Usa quello invece.