Il "doppio hashing" è una password meno sicura rispetto al semplice hashing una volta?


293

L'hashing di una password due volte prima dell'archiviazione è più o meno sicuro rispetto al semplice hashing una volta?

Quello di cui sto parlando è fare questo:

$hashed_password = hash(hash($plaintext_password));

invece di questo:

$hashed_password = hash($plaintext_password);

Se è meno sicuro, puoi fornire una buona spiegazione (o un link a uno)?

Inoltre, la funzione hash utilizzata fa la differenza? Fa differenza se mescoli md5 e sha1 (per esempio) invece di ripetere la stessa funzione hash?

Nota 1: quando dico "doppio hashing" sto parlando di hashing di una password due volte nel tentativo di renderlo più oscurato. Non sto parlando della tecnica per risolvere le collisioni .

Nota 2: so che devo aggiungere un sale casuale per renderlo davvero sicuro. La domanda è se l'hashing due volte con lo stesso algoritmo aiuta o fa male all'hash.


2
Hash(password)e Hash(Hash(password))sono ugualmente insicuri. Entrambi mancano del concetto di sicurezza semantica . Cioè, l'output è distinguibile da casuale. Ad esempio, lo MD5("password")è 5f4dcc3b5aa765d61d8327deb882cf99. So che è l'hash MD5 di password, ed è distinguibile da casuale. Invece, dovresti usare un HMAC. È decisamente sicuro ed è un PRF.
jww

Risposte:


267

Hashing una password una volta non è sicuro

No, gli hash multipli non sono meno sicuri; sono una parte essenziale dell'uso sicuro della password.

L'iterazione dell'hash aumenta il tempo impiegato da un utente malintenzionato per provare ciascuna password nell'elenco dei candidati. Puoi facilmente aumentare il tempo necessario per attaccare una password da ore a anni.

L'iterazione semplice non è sufficiente

Il semplice concatenamento dell'output di hash nell'input non è sufficiente per la sicurezza. L'iterazione dovrebbe avvenire nel contesto di un algoritmo che preserva l'entropia della password. Fortunatamente, ci sono diversi algoritmi pubblicati che hanno avuto abbastanza controllo per dare fiducia nel loro design.

Un buon algoritmo di derivazione delle chiavi come PBKDF2 inietta la password in ogni round di hashing, mitigando le preoccupazioni sulle collisioni nell'output di hash. PBKDF2 può essere utilizzato per l'autenticazione della password così com'è. Bcrypt segue la derivazione della chiave con un passaggio di crittografia; in questo modo, se viene scoperto un modo rapido per invertire la derivazione della chiave, un utente malintenzionato deve comunque completare un attacco in chiaro.

Come rompere una password

Le password memorizzate devono essere protette da un attacco offline. Se le password non vengono salate, possono essere violate con un attacco del dizionario precalcolato (ad esempio, utilizzando una tabella arcobaleno). Altrimenti, l'attaccante deve impiegare del tempo per calcolare un hash per ogni password e vedere se corrisponde all'hash memorizzato.

Tutte le password non sono ugualmente probabili. Gli aggressori possono cercare in modo esauriente tutte le password brevi, ma sanno che le loro possibilità di successo con la forza bruta diminuiscono drasticamente con ogni personaggio aggiuntivo. Invece, usano un elenco ordinato delle password più probabili. Iniziano con "password123" e passano alle password utilizzate meno frequentemente.

Diciamo che una lista di aggressori è lunga, con 10 miliardi di candidati; supponiamo anche che un sistema desktop possa calcolare 1 milione di hash al secondo. L'attaccante può testare il suo intero elenco è inferiore a tre ore se viene utilizzata una sola iterazione. Ma se vengono utilizzate solo 2000 iterazioni, tale tempo si estende a quasi 8 mesi. Per sconfiggere un aggressore più sofisticato, uno in grado di scaricare un programma in grado di sfruttare la potenza della sua GPU, ad esempio, sono necessarie più iterazioni.

Quanto è abbastanza?

Il numero di iterazioni da utilizzare è un compromesso tra sicurezza ed esperienza dell'utente. L'hardware specializzato che può essere utilizzato dagli aggressori è economico, ma può ancora eseguire centinaia di milioni di iterazioni al secondo. Le prestazioni del sistema dell'attaccante determinano il tempo necessario per violare una password, dato un numero di iterazioni. È probabile che l'applicazione non utilizzi questo hardware specializzato. Quante iterazioni puoi eseguire senza aggravare gli utenti dipende dal tuo sistema.

Probabilmente puoi consentire agli utenti di attendere ¾ di secondo in più durante l'autenticazione. Profila la tua piattaforma di destinazione e usa tutte le iterazioni che puoi permetterti. Le piattaforme che ho testato (un utente su un dispositivo mobile o molti utenti su una piattaforma server) possono supportare comodamente PBKDF2 con tra 60.000 e 120.000 iterazioni o bcrypt con un fattore di costo di 12 o 13.

Più sfondo

Leggi PKCS # 5 per informazioni autorevoli sul ruolo del sale e delle iterazioni nell'hashing. Anche se PBKDF2 era pensato per generare chiavi di crittografia da password, funziona bene come hash unidirezionale per l'autenticazione della password. Ogni iterazione di bcrypt è più costosa di un hash SHA-2, quindi puoi usare meno iterazioni, ma l'idea è la stessa. Bcrypt fa anche un passo oltre la maggior parte delle soluzioni basate su PBKDF2 utilizzando la chiave derivata per crittografare un noto testo semplice. Il testo della cifra risultante viene memorizzato come "hash", insieme ad alcuni metadati. Tuttavia, nulla ti impedisce di fare la stessa cosa con PBKDF2.

Ecco altre risposte che ho scritto su questo argomento:


68
Fare intenzionalmente un algoritmo lento è una pratica accettata quando si tenta di prevenire attacchi da dizionario contro archivi di autenticazione compromessi. La tecnica si chiama "rafforzamento chiave" o "allungamento chiave". Vedi en.wikipedia.org/wiki/Key_stretching

17
@RoBorg: non importa quanto sia lenta la tua implementazione, ma quanto sarà lenta l'implementazione di un attaccante: se l'hash stesso è migliaia di volte più lento, ci vorrà un attaccante migliaia di volte più a lungo per forzare la password.
orip

5
Probabilmente vorresti collisioni nello spazio a 128 bit da 0 a 2 ^ 128-1. Se lo spazio di output 2 ^ 128 dell'algoritmo hash è perfetto, in teoria, hai solo un codice di sostituzione con un alfabeto di 2 ^ 128 glifi.
jmucchiello,

13
@devin - non è "la mia soluzione", è una pratica ampiamente accettata, integrata in standard di crittografia basati su password come PKCS # 5 e consigliata da esperti come Robert Morris. È estremamente scalabile, poiché la frazione di tempo impiegata per l'autenticazione degli utenti è piccola in un'applicazione legittima. Diventa difficile scalare solo quando l'applicazione sta violando le password, da cui la raccomandazione. Certamente, lo spazio di ricerca di un hash è più piccolo di quello delle possibili password, ma anche uno spazio a 128 bit è troppo grande per la ricerca a forza bruta. La minaccia da difendere è un attacco di dizionario offline.
Erickson,

6
Non mi riferivo al disagio per il singolo utente, ma piuttosto allo stress che sarebbe stato messo sul server se avessi una grande base di utenti, perché stai facendo affidamento sul carico della CPU per rallentare il numero di richieste. Significa che se aggiungi più potenza della CPU, stai riducendo le restrizioni su quegli aggressori a forza bruta. - Tuttavia, hai completamente ragione sulla scalabilità e sulla pratica ampiamente accettata. Ho sbagliato su quasi tutte le cose che ho detto nei miei commenti precedenti. Siamo spiacenti :)
DevinB,

227

Per coloro che dicono che è sicuro, sono corretti in generale . L'hash "doppio" (o l'espansione logica di ciò, iterando una funzione hash) è assolutamente sicuro se fatto bene , per una specifica preoccupazione.

Per quelli che dicono che è insicuro, sono corretti in questo caso . Il codice pubblicato nella domanda non è sicuro. Parliamo del perché:

$hashed_password1 = md5( md5( plaintext_password ) );
$hashed_password2 = md5( plaintext_password );

Ci sono due proprietà fondamentali di una funzione hash di cui siamo preoccupati:

  1. Resistenza pre-immagine - Dato un hash $h, dovrebbe essere difficile trovare un messaggio del $mgenere$h === hash($m)

  2. Resistenza pre-immagine seconda - Dato un messaggio $m1, dovrebbe essere difficile trovare un messaggio diverso $m2talehash($m1) === hash($m2)

  3. Resistenza alla collisione - Dovrebbe essere difficile trovare una coppia di messaggi ($m1, $m2)tale che hash($m1) === hash($m2)(si noti che questo è simile alla resistenza di seconda pre-immagine, ma diverso dal fatto che qui l'attaccante ha il controllo su entrambi i messaggi) ...

Per l'archiviazione delle password , tutto ciò che ci interessa davvero è la resistenza pre-immagine . Gli altri due sarebbero discutibili, perché $m1è la password dell'utente che stiamo cercando di proteggere. Quindi se l'attaccante lo possiede già, l'hash non ha nulla da proteggere ...

NEGAZIONE

Tutto ciò che segue si basa sul presupposto che ci interessa solo la resistenza pre-immagine . Le altre due proprietà fondamentali delle funzioni hash potrebbero non reggere (e in genere no) allo stesso modo. Quindi le conclusioni in questo post sono applicabili solo quando si usano le funzioni hash per la memorizzazione delle password. Non sono applicabili in generale ...

Iniziamo

Per il bene di questa discussione, inventiamo la nostra funzione hash:

function ourHash($input) {
    $result = 0;
    for ($i = 0; $i < strlen($input); $i++) {
        $result += ord($input[$i]);
    }
    return (string) ($result % 256);
}

Ora dovrebbe essere abbastanza ovvio cosa fa questa funzione hash. Riassume i valori ASCII di ciascun carattere di input e quindi prende il modulo di quel risultato con 256.

Quindi proviamolo:

var_dump(
    ourHash('abc'), // string(2) "38"
    ourHash('def'), // string(2) "47"
    ourHash('hij'), // string(2) "59"
    ourHash('klm')  // string(2) "68"
);

Ora vediamo cosa succede se lo eseguiamo alcune volte attorno a una funzione:

$tests = array(
    "abc",
    "def",
    "hij",
    "klm",
);

foreach ($tests as $test) {
    $hash = $test;
    for ($i = 0; $i < 100; $i++) {
        $hash = ourHash($hash);
    }
    echo "Hashing $test => $hash\n";
}

Che produce:

Hashing abc => 152
Hashing def => 152
Hashing hij => 155
Hashing klm => 155

Caspita. Abbiamo generato collisioni !!! Proviamo a capire perché:

Ecco l'output di hashing di una stringa di ogni output hash possibile:

Hashing 0 => 48
Hashing 1 => 49
Hashing 2 => 50
Hashing 3 => 51
Hashing 4 => 52
Hashing 5 => 53
Hashing 6 => 54
Hashing 7 => 55
Hashing 8 => 56
Hashing 9 => 57
Hashing 10 => 97
Hashing 11 => 98
Hashing 12 => 99
Hashing 13 => 100
Hashing 14 => 101
Hashing 15 => 102
Hashing 16 => 103
Hashing 17 => 104
Hashing 18 => 105
Hashing 19 => 106
Hashing 20 => 98
Hashing 21 => 99
Hashing 22 => 100
Hashing 23 => 101
Hashing 24 => 102
Hashing 25 => 103
Hashing 26 => 104
Hashing 27 => 105
Hashing 28 => 106
Hashing 29 => 107
Hashing 30 => 99
Hashing 31 => 100
Hashing 32 => 101
Hashing 33 => 102
Hashing 34 => 103
Hashing 35 => 104
Hashing 36 => 105
Hashing 37 => 106
Hashing 38 => 107
Hashing 39 => 108
Hashing 40 => 100
Hashing 41 => 101
Hashing 42 => 102
Hashing 43 => 103
Hashing 44 => 104
Hashing 45 => 105
Hashing 46 => 106
Hashing 47 => 107
Hashing 48 => 108
Hashing 49 => 109
Hashing 50 => 101
Hashing 51 => 102
Hashing 52 => 103
Hashing 53 => 104
Hashing 54 => 105
Hashing 55 => 106
Hashing 56 => 107
Hashing 57 => 108
Hashing 58 => 109
Hashing 59 => 110
Hashing 60 => 102
Hashing 61 => 103
Hashing 62 => 104
Hashing 63 => 105
Hashing 64 => 106
Hashing 65 => 107
Hashing 66 => 108
Hashing 67 => 109
Hashing 68 => 110
Hashing 69 => 111
Hashing 70 => 103
Hashing 71 => 104
Hashing 72 => 105
Hashing 73 => 106
Hashing 74 => 107
Hashing 75 => 108
Hashing 76 => 109
Hashing 77 => 110
Hashing 78 => 111
Hashing 79 => 112
Hashing 80 => 104
Hashing 81 => 105
Hashing 82 => 106
Hashing 83 => 107
Hashing 84 => 108
Hashing 85 => 109
Hashing 86 => 110
Hashing 87 => 111
Hashing 88 => 112
Hashing 89 => 113
Hashing 90 => 105
Hashing 91 => 106
Hashing 92 => 107
Hashing 93 => 108
Hashing 94 => 109
Hashing 95 => 110
Hashing 96 => 111
Hashing 97 => 112
Hashing 98 => 113
Hashing 99 => 114
Hashing 100 => 145
Hashing 101 => 146
Hashing 102 => 147
Hashing 103 => 148
Hashing 104 => 149
Hashing 105 => 150
Hashing 106 => 151
Hashing 107 => 152
Hashing 108 => 153
Hashing 109 => 154
Hashing 110 => 146
Hashing 111 => 147
Hashing 112 => 148
Hashing 113 => 149
Hashing 114 => 150
Hashing 115 => 151
Hashing 116 => 152
Hashing 117 => 153
Hashing 118 => 154
Hashing 119 => 155
Hashing 120 => 147
Hashing 121 => 148
Hashing 122 => 149
Hashing 123 => 150
Hashing 124 => 151
Hashing 125 => 152
Hashing 126 => 153
Hashing 127 => 154
Hashing 128 => 155
Hashing 129 => 156
Hashing 130 => 148
Hashing 131 => 149
Hashing 132 => 150
Hashing 133 => 151
Hashing 134 => 152
Hashing 135 => 153
Hashing 136 => 154
Hashing 137 => 155
Hashing 138 => 156
Hashing 139 => 157
Hashing 140 => 149
Hashing 141 => 150
Hashing 142 => 151
Hashing 143 => 152
Hashing 144 => 153
Hashing 145 => 154
Hashing 146 => 155
Hashing 147 => 156
Hashing 148 => 157
Hashing 149 => 158
Hashing 150 => 150
Hashing 151 => 151
Hashing 152 => 152
Hashing 153 => 153
Hashing 154 => 154
Hashing 155 => 155
Hashing 156 => 156
Hashing 157 => 157
Hashing 158 => 158
Hashing 159 => 159
Hashing 160 => 151
Hashing 161 => 152
Hashing 162 => 153
Hashing 163 => 154
Hashing 164 => 155
Hashing 165 => 156
Hashing 166 => 157
Hashing 167 => 158
Hashing 168 => 159
Hashing 169 => 160
Hashing 170 => 152
Hashing 171 => 153
Hashing 172 => 154
Hashing 173 => 155
Hashing 174 => 156
Hashing 175 => 157
Hashing 176 => 158
Hashing 177 => 159
Hashing 178 => 160
Hashing 179 => 161
Hashing 180 => 153
Hashing 181 => 154
Hashing 182 => 155
Hashing 183 => 156
Hashing 184 => 157
Hashing 185 => 158
Hashing 186 => 159
Hashing 187 => 160
Hashing 188 => 161
Hashing 189 => 162
Hashing 190 => 154
Hashing 191 => 155
Hashing 192 => 156
Hashing 193 => 157
Hashing 194 => 158
Hashing 195 => 159
Hashing 196 => 160
Hashing 197 => 161
Hashing 198 => 162
Hashing 199 => 163
Hashing 200 => 146
Hashing 201 => 147
Hashing 202 => 148
Hashing 203 => 149
Hashing 204 => 150
Hashing 205 => 151
Hashing 206 => 152
Hashing 207 => 153
Hashing 208 => 154
Hashing 209 => 155
Hashing 210 => 147
Hashing 211 => 148
Hashing 212 => 149
Hashing 213 => 150
Hashing 214 => 151
Hashing 215 => 152
Hashing 216 => 153
Hashing 217 => 154
Hashing 218 => 155
Hashing 219 => 156
Hashing 220 => 148
Hashing 221 => 149
Hashing 222 => 150
Hashing 223 => 151
Hashing 224 => 152
Hashing 225 => 153
Hashing 226 => 154
Hashing 227 => 155
Hashing 228 => 156
Hashing 229 => 157
Hashing 230 => 149
Hashing 231 => 150
Hashing 232 => 151
Hashing 233 => 152
Hashing 234 => 153
Hashing 235 => 154
Hashing 236 => 155
Hashing 237 => 156
Hashing 238 => 157
Hashing 239 => 158
Hashing 240 => 150
Hashing 241 => 151
Hashing 242 => 152
Hashing 243 => 153
Hashing 244 => 154
Hashing 245 => 155
Hashing 246 => 156
Hashing 247 => 157
Hashing 248 => 158
Hashing 249 => 159
Hashing 250 => 151
Hashing 251 => 152
Hashing 252 => 153
Hashing 253 => 154
Hashing 254 => 155
Hashing 255 => 156

Notare la tendenza verso numeri più alti. Questo risulta essere il nostro punto morto. Eseguire l'hash 4 volte ($ hash = ourHash ($ hash) `, per ogni elemento) finisce per darci:

Hashing 0 => 153
Hashing 1 => 154
Hashing 2 => 155
Hashing 3 => 156
Hashing 4 => 157
Hashing 5 => 158
Hashing 6 => 150
Hashing 7 => 151
Hashing 8 => 152
Hashing 9 => 153
Hashing 10 => 157
Hashing 11 => 158
Hashing 12 => 150
Hashing 13 => 154
Hashing 14 => 155
Hashing 15 => 156
Hashing 16 => 157
Hashing 17 => 158
Hashing 18 => 150
Hashing 19 => 151
Hashing 20 => 158
Hashing 21 => 150
Hashing 22 => 154
Hashing 23 => 155
Hashing 24 => 156
Hashing 25 => 157
Hashing 26 => 158
Hashing 27 => 150
Hashing 28 => 151
Hashing 29 => 152
Hashing 30 => 150
Hashing 31 => 154
Hashing 32 => 155
Hashing 33 => 156
Hashing 34 => 157
Hashing 35 => 158
Hashing 36 => 150
Hashing 37 => 151
Hashing 38 => 152
Hashing 39 => 153
Hashing 40 => 154
Hashing 41 => 155
Hashing 42 => 156
Hashing 43 => 157
Hashing 44 => 158
Hashing 45 => 150
Hashing 46 => 151
Hashing 47 => 152
Hashing 48 => 153
Hashing 49 => 154
Hashing 50 => 155
Hashing 51 => 156
Hashing 52 => 157
Hashing 53 => 158
Hashing 54 => 150
Hashing 55 => 151
Hashing 56 => 152
Hashing 57 => 153
Hashing 58 => 154
Hashing 59 => 155
Hashing 60 => 156
Hashing 61 => 157
Hashing 62 => 158
Hashing 63 => 150
Hashing 64 => 151
Hashing 65 => 152
Hashing 66 => 153
Hashing 67 => 154
Hashing 68 => 155
Hashing 69 => 156
Hashing 70 => 157
Hashing 71 => 158
Hashing 72 => 150
Hashing 73 => 151
Hashing 74 => 152
Hashing 75 => 153
Hashing 76 => 154
Hashing 77 => 155
Hashing 78 => 156
Hashing 79 => 157
Hashing 80 => 158
Hashing 81 => 150
Hashing 82 => 151
Hashing 83 => 152
Hashing 84 => 153
Hashing 85 => 154
Hashing 86 => 155
Hashing 87 => 156
Hashing 88 => 157
Hashing 89 => 158
Hashing 90 => 150
Hashing 91 => 151
Hashing 92 => 152
Hashing 93 => 153
Hashing 94 => 154
Hashing 95 => 155
Hashing 96 => 156
Hashing 97 => 157
Hashing 98 => 158
Hashing 99 => 150
Hashing 100 => 154
Hashing 101 => 155
Hashing 102 => 156
Hashing 103 => 157
Hashing 104 => 158
Hashing 105 => 150
Hashing 106 => 151
Hashing 107 => 152
Hashing 108 => 153
Hashing 109 => 154
Hashing 110 => 155
Hashing 111 => 156
Hashing 112 => 157
Hashing 113 => 158
Hashing 114 => 150
Hashing 115 => 151
Hashing 116 => 152
Hashing 117 => 153
Hashing 118 => 154
Hashing 119 => 155
Hashing 120 => 156
Hashing 121 => 157
Hashing 122 => 158
Hashing 123 => 150
Hashing 124 => 151
Hashing 125 => 152
Hashing 126 => 153
Hashing 127 => 154
Hashing 128 => 155
Hashing 129 => 156
Hashing 130 => 157
Hashing 131 => 158
Hashing 132 => 150
Hashing 133 => 151
Hashing 134 => 152
Hashing 135 => 153
Hashing 136 => 154
Hashing 137 => 155
Hashing 138 => 156
Hashing 139 => 157
Hashing 140 => 158
Hashing 141 => 150
Hashing 142 => 151
Hashing 143 => 152
Hashing 144 => 153
Hashing 145 => 154
Hashing 146 => 155
Hashing 147 => 156
Hashing 148 => 157
Hashing 149 => 158
Hashing 150 => 150
Hashing 151 => 151
Hashing 152 => 152
Hashing 153 => 153
Hashing 154 => 154
Hashing 155 => 155
Hashing 156 => 156
Hashing 157 => 157
Hashing 158 => 158
Hashing 159 => 159
Hashing 160 => 151
Hashing 161 => 152
Hashing 162 => 153
Hashing 163 => 154
Hashing 164 => 155
Hashing 165 => 156
Hashing 166 => 157
Hashing 167 => 158
Hashing 168 => 159
Hashing 169 => 151
Hashing 170 => 152
Hashing 171 => 153
Hashing 172 => 154
Hashing 173 => 155
Hashing 174 => 156
Hashing 175 => 157
Hashing 176 => 158
Hashing 177 => 159
Hashing 178 => 151
Hashing 179 => 152
Hashing 180 => 153
Hashing 181 => 154
Hashing 182 => 155
Hashing 183 => 156
Hashing 184 => 157
Hashing 185 => 158
Hashing 186 => 159
Hashing 187 => 151
Hashing 188 => 152
Hashing 189 => 153
Hashing 190 => 154
Hashing 191 => 155
Hashing 192 => 156
Hashing 193 => 157
Hashing 194 => 158
Hashing 195 => 159
Hashing 196 => 151
Hashing 197 => 152
Hashing 198 => 153
Hashing 199 => 154
Hashing 200 => 155
Hashing 201 => 156
Hashing 202 => 157
Hashing 203 => 158
Hashing 204 => 150
Hashing 205 => 151
Hashing 206 => 152
Hashing 207 => 153
Hashing 208 => 154
Hashing 209 => 155
Hashing 210 => 156
Hashing 211 => 157
Hashing 212 => 158
Hashing 213 => 150
Hashing 214 => 151
Hashing 215 => 152
Hashing 216 => 153
Hashing 217 => 154
Hashing 218 => 155
Hashing 219 => 156
Hashing 220 => 157
Hashing 221 => 158
Hashing 222 => 150
Hashing 223 => 151
Hashing 224 => 152
Hashing 225 => 153
Hashing 226 => 154
Hashing 227 => 155
Hashing 228 => 156
Hashing 229 => 157
Hashing 230 => 158
Hashing 231 => 150
Hashing 232 => 151
Hashing 233 => 152
Hashing 234 => 153
Hashing 235 => 154
Hashing 236 => 155
Hashing 237 => 156
Hashing 238 => 157
Hashing 239 => 158
Hashing 240 => 150
Hashing 241 => 151
Hashing 242 => 152
Hashing 243 => 153
Hashing 244 => 154
Hashing 245 => 155
Hashing 246 => 156
Hashing 247 => 157
Hashing 248 => 158
Hashing 249 => 159
Hashing 250 => 151
Hashing 251 => 152
Hashing 252 => 153
Hashing 253 => 154
Hashing 254 => 155
Hashing 255 => 156

Abbiamo ristretto noi stessi fino a 8 valori ... Questo è male ... La nostra funzione originale mappata S(∞)su S(256). Cioè abbiamo creato una mappatura delle funzioni Surjective$input a $output.

Poiché abbiamo una funzione Surjective, non abbiamo alcuna garanzia che la mappatura per qualsiasi sottoinsieme dell'input non avrà collisioni (in effetti, in pratica lo faranno).

Questo è quello che è successo qui! La nostra funzione era pessima, ma non è per questo che ha funzionato (ecco perché ha funzionato così rapidamente e così completamente).

La stessa cosa succede con MD5. Si mappa S(∞)su S(2^128). Dal momento che non esiste alcuna garanzia che la corsa MD5(S(output))sarà Iniettiva , il che significa che non avrà collisioni.

Sezione TL / DR

Pertanto, poiché l'alimentazione dell'output md5può generare direttamente collisioni, ogni iterazione aumenterà la possibilità di collisioni. Questo è comunque un aumento lineare, il che significa che mentre il set di risultati 2^128è ridotto, non è significativamente ridotto abbastanza velocemente da essere un difetto critico.

Così,

$output = md5($input); // 2^128 possibilities
$output = md5($output); // < 2^128 possibilities
$output = md5($output); // < 2^128 possibilities
$output = md5($output); // < 2^128 possibilities
$output = md5($output); // < 2^128 possibilities

Più volte ripeterai, maggiore sarà la riduzione.

La correzione

Fortunatamente per noi, c'è un modo banale per risolvere questo problema: fornire qualcosa in più alle successive iterazioni:

$output = md5($input); // 2^128 possibilities
$output = md5($input . $output); // 2^128 possibilities
$output = md5($input . $output); // 2^128 possibilities
$output = md5($input . $output); // 2^128 possibilities
$output = md5($input . $output); // 2^128 possibilities    

Si noti che le ulteriori iterazioni non sono 2 ^ 128 per ogni singolo valore per $input. Ciò significa che potremmo essere in grado di generare $inputvalori che si scontrano ancora lungo la linea (e quindi si stabilizzeranno o risuoneranno a molto meno delle 2^128possibili uscite). Ma il caso generale per $inputè ancora forte come lo era per un singolo round.

Aspetta, vero? Proviamo questo con la nostra ourHash()funzione. Passando a $hash = ourHash($input . $hash);, per 100 iterazioni:

Hashing 0 => 201
Hashing 1 => 212
Hashing 2 => 199
Hashing 3 => 201
Hashing 4 => 203
Hashing 5 => 205
Hashing 6 => 207
Hashing 7 => 209
Hashing 8 => 211
Hashing 9 => 204
Hashing 10 => 251
Hashing 11 => 147
Hashing 12 => 251
Hashing 13 => 148
Hashing 14 => 253
Hashing 15 => 0
Hashing 16 => 1
Hashing 17 => 2
Hashing 18 => 161
Hashing 19 => 163
Hashing 20 => 147
Hashing 21 => 251
Hashing 22 => 148
Hashing 23 => 253
Hashing 24 => 0
Hashing 25 => 1
Hashing 26 => 2
Hashing 27 => 161
Hashing 28 => 163
Hashing 29 => 8
Hashing 30 => 251
Hashing 31 => 148
Hashing 32 => 253
Hashing 33 => 0
Hashing 34 => 1
Hashing 35 => 2
Hashing 36 => 161
Hashing 37 => 163
Hashing 38 => 8
Hashing 39 => 4
Hashing 40 => 148
Hashing 41 => 253
Hashing 42 => 0
Hashing 43 => 1
Hashing 44 => 2
Hashing 45 => 161
Hashing 46 => 163
Hashing 47 => 8
Hashing 48 => 4
Hashing 49 => 9
Hashing 50 => 253
Hashing 51 => 0
Hashing 52 => 1
Hashing 53 => 2
Hashing 54 => 161
Hashing 55 => 163
Hashing 56 => 8
Hashing 57 => 4
Hashing 58 => 9
Hashing 59 => 11
Hashing 60 => 0
Hashing 61 => 1
Hashing 62 => 2
Hashing 63 => 161
Hashing 64 => 163
Hashing 65 => 8
Hashing 66 => 4
Hashing 67 => 9
Hashing 68 => 11
Hashing 69 => 4
Hashing 70 => 1
Hashing 71 => 2
Hashing 72 => 161
Hashing 73 => 163
Hashing 74 => 8
Hashing 75 => 4
Hashing 76 => 9
Hashing 77 => 11
Hashing 78 => 4
Hashing 79 => 3
Hashing 80 => 2
Hashing 81 => 161
Hashing 82 => 163
Hashing 83 => 8
Hashing 84 => 4
Hashing 85 => 9
Hashing 86 => 11
Hashing 87 => 4
Hashing 88 => 3
Hashing 89 => 17
Hashing 90 => 161
Hashing 91 => 163
Hashing 92 => 8
Hashing 93 => 4
Hashing 94 => 9
Hashing 95 => 11
Hashing 96 => 4
Hashing 97 => 3
Hashing 98 => 17
Hashing 99 => 13
Hashing 100 => 246
Hashing 101 => 248
Hashing 102 => 49
Hashing 103 => 44
Hashing 104 => 255
Hashing 105 => 198
Hashing 106 => 43
Hashing 107 => 51
Hashing 108 => 202
Hashing 109 => 2
Hashing 110 => 248
Hashing 111 => 49
Hashing 112 => 44
Hashing 113 => 255
Hashing 114 => 198
Hashing 115 => 43
Hashing 116 => 51
Hashing 117 => 202
Hashing 118 => 2
Hashing 119 => 51
Hashing 120 => 49
Hashing 121 => 44
Hashing 122 => 255
Hashing 123 => 198
Hashing 124 => 43
Hashing 125 => 51
Hashing 126 => 202
Hashing 127 => 2
Hashing 128 => 51
Hashing 129 => 53
Hashing 130 => 44
Hashing 131 => 255
Hashing 132 => 198
Hashing 133 => 43
Hashing 134 => 51
Hashing 135 => 202
Hashing 136 => 2
Hashing 137 => 51
Hashing 138 => 53
Hashing 139 => 55
Hashing 140 => 255
Hashing 141 => 198
Hashing 142 => 43
Hashing 143 => 51
Hashing 144 => 202
Hashing 145 => 2
Hashing 146 => 51
Hashing 147 => 53
Hashing 148 => 55
Hashing 149 => 58
Hashing 150 => 198
Hashing 151 => 43
Hashing 152 => 51
Hashing 153 => 202
Hashing 154 => 2
Hashing 155 => 51
Hashing 156 => 53
Hashing 157 => 55
Hashing 158 => 58
Hashing 159 => 0
Hashing 160 => 43
Hashing 161 => 51
Hashing 162 => 202
Hashing 163 => 2
Hashing 164 => 51
Hashing 165 => 53
Hashing 166 => 55
Hashing 167 => 58
Hashing 168 => 0
Hashing 169 => 209
Hashing 170 => 51
Hashing 171 => 202
Hashing 172 => 2
Hashing 173 => 51
Hashing 174 => 53
Hashing 175 => 55
Hashing 176 => 58
Hashing 177 => 0
Hashing 178 => 209
Hashing 179 => 216
Hashing 180 => 202
Hashing 181 => 2
Hashing 182 => 51
Hashing 183 => 53
Hashing 184 => 55
Hashing 185 => 58
Hashing 186 => 0
Hashing 187 => 209
Hashing 188 => 216
Hashing 189 => 219
Hashing 190 => 2
Hashing 191 => 51
Hashing 192 => 53
Hashing 193 => 55
Hashing 194 => 58
Hashing 195 => 0
Hashing 196 => 209
Hashing 197 => 216
Hashing 198 => 219
Hashing 199 => 220
Hashing 200 => 248
Hashing 201 => 49
Hashing 202 => 44
Hashing 203 => 255
Hashing 204 => 198
Hashing 205 => 43
Hashing 206 => 51
Hashing 207 => 202
Hashing 208 => 2
Hashing 209 => 51
Hashing 210 => 49
Hashing 211 => 44
Hashing 212 => 255
Hashing 213 => 198
Hashing 214 => 43
Hashing 215 => 51
Hashing 216 => 202
Hashing 217 => 2
Hashing 218 => 51
Hashing 219 => 53
Hashing 220 => 44
Hashing 221 => 255
Hashing 222 => 198
Hashing 223 => 43
Hashing 224 => 51
Hashing 225 => 202
Hashing 226 => 2
Hashing 227 => 51
Hashing 228 => 53
Hashing 229 => 55
Hashing 230 => 255
Hashing 231 => 198
Hashing 232 => 43
Hashing 233 => 51
Hashing 234 => 202
Hashing 235 => 2
Hashing 236 => 51
Hashing 237 => 53
Hashing 238 => 55
Hashing 239 => 58
Hashing 240 => 198
Hashing 241 => 43
Hashing 242 => 51
Hashing 243 => 202
Hashing 244 => 2
Hashing 245 => 51
Hashing 246 => 53
Hashing 247 => 55
Hashing 248 => 58
Hashing 249 => 0
Hashing 250 => 43
Hashing 251 => 51
Hashing 252 => 202
Hashing 253 => 2
Hashing 254 => 51
Hashing 255 => 53

C'è ancora un modello approssimativo lì, ma nota che non è più un modello della nostra funzione sottostante (che era già abbastanza debole).

Si noti, tuttavia, che 0e 3diventato collisioni, anche se non erano in un'unica seduta. Questa è un'applicazione di quello che ho detto prima (che la resistenza alla collisione rimane la stessa per l'insieme di tutti gli input, ma specifiche rotte di collisione possono aprirsi a causa di difetti dell'algoritmo sottostante).

Sezione TL / DR

Fornendo un input in ciascuna iterazione, interrompiamo efficacemente eventuali collisioni che potrebbero essersi verificate nella precedente iterazione.

Pertanto, md5($input . md5($input));dovrebbe essere ( almeno teoricamente ) forte come md5($input).

È importante?

Sì. Questo è uno dei motivi per cui PBKDF2 ha sostituito PBKDF1 in RFC 2898 . Considera i circuiti interni dei due ::

PBKDF1:

T_1 = Hash (P || S) ,
T_2 = Hash (T_1) ,
...
T_c = Hash (T_{c-1}) 

Dov'è cil conteggio delle iterazioni, Pè la password ed Sè il sale

PBKDF2:

U_1 = PRF (P, S || INT (i)) ,
U_2 = PRF (P, U_1) ,
...
U_c = PRF (P, U_{c-1})

Dove PRF è davvero solo un HMAC. Ma per i nostri scopi qui, diciamo solo che PRF(P, S) = Hash(P || S)(cioè, il PRF di 2 input è lo stesso, approssimativamente parlando, dell'hash con i due concatenati insieme). E 'molto non è , ma per i nostri scopi è.

Quindi PBKDF2 mantiene la resistenza alle collisioni della Hashfunzione sottostante , mentre PBKDF1 no.

Legando tutto insieme:

Conosciamo metodi sicuri per iterare un hash. Infatti:

$hash = $input;
$i = 10000;
do {
   $hash = hash($input . $hash);
} while ($i-- > 0);

Di solito è sicuro.

Ora, per approfondire il motivo per cui vorremmo hash, analizziamo il movimento entropico.

Un hash include l'insieme infinito: S(∞)e produce un insieme più piccolo e di dimensioni coerenti S(n). L'iterazione successiva (supponendo che l'ingresso viene passato a) mappa S(∞)su S(n)ancora:

S(∞) -> S(n)
S(∞) -> S(n)
S(∞) -> S(n)
S(∞) -> S(n)
S(∞) -> S(n)
S(∞) -> S(n)

Si noti che l'output finale ha esattamente la stessa quantità di entropia del primo . L'iterazione non "la renderà più oscurata". L'entropia è identica. Non esiste una fonte magica di imprevedibilità (è una funzione pseudo-casuale, non una funzione casuale).

C'è comunque un guadagno nell'iterazione. Rende artificialmente più lento il processo di hashing. Ed è per questo che l'iterazione può essere una buona idea. In realtà, è il principio di base della maggior parte dei moderni algoritmi di hashing delle password (il fatto che fare qualcosa di continuo lo rende più lento).

Lento è buono, perché sta combattendo la principale minaccia alla sicurezza: la forza bruta. Quanto più lentamente rendiamo il nostro algoritmo di hashing, tanto più gli aggressori devono lavorare per attaccare gli hash delle password rubati da noi. E questa è una buona cosa !!!


1
$output = md5($output); // < 2^128 possibilities--- è davvero severo <o <=?
zerkms,

2
@zerkms: non è assolutamente nulla. Dovremmo conoscere alcuni dettagli molto specifici della funzione sottostante ( md5()in questo caso) per saperlo con certezza. Ma in generale lo sarà <e non <=... Ricorda, stiamo parlando delle dimensioni del set di $outputper tutto il possibile $inputs. Quindi, se abbiamo anche una collisione sarà <, quindi, <è il generalizer meglio.
Ircmaxell,

2
@ TomášFejfar Penso che la domanda non riguardi le collisioni in generale, ma le collisioni nel set di output rigoroso (2 ^ 128 output, ciascuno esattamente largo 128 bit). Questo potrebbe essere iniettiva, ma per quanto ne so una prova generica non è possibile (solo un proof-by-esempio di una collisione di un algoritmo specifico). Considera la funzione hash che restituisce semplicemente l'input se è a 128 bit (e altrimenti gli hash). In generale sarebbe suriettivo, ma quando alimentato il suo output sarebbe sempre iniettivo ... Questo è il punto di contesa ...
ircmaxell,


6
Per coloro che vorrebbero risparmiare tempo non dovendo andare a vedere come è finita la discussione tra Dan e Ircmaxell, è andata bene : Dan è d'accordo con Ircmaxell.
Jeromej,

51

Sì, il re-hashing riduce lo spazio di ricerca, ma no, non importa: la riduzione effettiva è insignificante.

Il ri-hashing aumenta il tempo necessario per la forza bruta, ma farlo anche solo due volte non è ottimale.

Quello che vuoi davvero è hash della password con PBKDF2 - un metodo collaudato per utilizzare un hash sicuro con salt e iterazioni. Dai un'occhiata a questa risposta SO .

EDIT : ho quasi dimenticato - NON USARE MD5 !!!! Usa un hash crittografico moderno come la famiglia SHA-2 (SHA-256, SHA-384 e SHA-512).


2
@DFTR - concordato. bcrypt o scrypt sono opzioni migliori.
orip,

Non usare nemmeno quelli (famiglia SHA-2) che ora possono anche essere facilmente crackati, controlla crackstation.net per la prova. Se qualcosa usa scrypt o PBKDF2, che sono funzioni hash crittografiche basate sulla funzione di derivazione chiave (KDF).
Theodore,

3
Nel 2016, Argon2 e Scrypt sono quelli che tutti dovrebbero sforzarsi di usare
silkfire

10

Sì: riduce il numero di stringhe eventualmente corrispondenti alla stringa.

Come hai già detto, gli hash salati sono molto meglio.

Un articolo qui: http://websecurity.ro/blog/2007/11/02/md5md5-vs-md5/ , tenta una prova del perché sia ​​equivalente, ma non sono sicuro della logica. In parte suppongono che non ci sia software disponibile per analizzare md5 (md5 (testo)), ma ovviamente è abbastanza banale produrre le tabelle arcobaleno.

Sto ancora attaccando alla mia risposta che ci sono un numero minore di hash di tipo md5 (md5 (testo)) rispetto agli hash md5 (testo), aumentando le possibilità di collisione (anche se ancora con una probabilità improbabile) e riducendo lo spazio di ricerca.


5

La maggior parte delle risposte proviene da persone senza un background in crittografia o sicurezza. E si sbagliano. Usa un sale, se possibile unico per record. MD5 / SHA / etc sono troppo veloci, l'opposto di quello che vuoi. PBKDF2 e bcrypt sono più lenti (il che è buono) ma possono essere sconfitti con ASIC / FPGA / GPU (oggi molto abbordabili). Quindi è necessario un algoritmo per la memoria dura: inserire scrypt .

Ecco una spiegazione per laici su sali e velocità (ma non sugli algoritmi di memoria difficile).


4

Lo guardo da un punto di vista pratico. Cosa sta cercando l'hacker? Perché, la combinazione di caratteri che, quando viene passata attraverso la funzione hash, genera l'hash desiderato.

Stai salvando solo l'ultimo hash, quindi l'hacker deve solo forzare un hash. Supponendo che tu abbia all'incirca le stesse probabilità di inciampare nell'hash desiderato ad ogni passo di forza bruta, il numero di hash è irrilevante. Potresti fare un milione di iterazioni di hash e non aumenterebbe o ridurrebbe un po 'la sicurezza, poiché alla fine della linea c'è ancora un solo hash da interrompere e le probabilità di romperla sono le stesse di qualsiasi hash.

Forse i precedenti poster ritengono che l'input sia rilevante; non è. Fintanto che qualunque cosa tu inserisca nella funzione hash generi l'hash desiderato, ti porterà attraverso, input corretto o input errato.

Ora, i tavoli arcobaleno sono un'altra storia. Poiché una tabella arcobaleno contiene solo password non elaborate, l'hashing due volte può essere una buona misura di sicurezza, poiché una tabella arcobaleno che contiene ogni hash di ogni hash sarebbe troppo grande.

Ovviamente, sto solo prendendo in considerazione l'esempio fornito dall'OP, in cui si tratta solo di una password in testo semplice che viene l'hash. Se includi il nome utente o un salt nell'hash, è una storia diversa; l'hashing due volte è del tutto inutile, poiché il tavolo arcobaleno sarebbe già troppo grande per essere pratico e contenere l'hash giusto.

Comunque, non un esperto di sicurezza qui, ma è proprio quello che ho capito dalla mia esperienza.


Questa risposta è sbagliata sotto ogni aspetto. 1. Conoscere il penultimo hash non fornisce alcun valore a un utente malintenzionato, in quanto l'input di un hash iterato è la password , che viene quindi l'hash più volte (non una volta). 2. Lo spazio di input è una password, lo spazio di output è una password con hash. Lo spazio delle password tipiche è molto più piccolo dello spazio di output. 3. Le tabelle Rainbow per le password con doppio hash non salate non sono più grandi delle tabelle Rainbow per le password con hash singolo non salate. 4. I nomi utente sono a bassa entropia, un buon sale è casuale. 5. La salatura non sostituisce l'iterazione. Hai bisogno di entrambi.
Clemente Cherlin,

3

Da quello che ho letto, in realtà potrebbe essere consigliato ricodificare la password centinaia o migliaia di volte.

L'idea è che se riesci a impiegare più tempo per codificare la password, è più lavoro per un utente malintenzionato passare attraverso molte ipotesi per decifrare la password. Questo sembra essere il vantaggio del re-hashing - non che sia più sicuro dal punto di vista crittografico, ma richiede semplicemente più tempo per generare un attacco del dizionario.

Naturalmente i computer diventano sempre più veloci, quindi questo vantaggio diminuisce nel tempo (o richiede di aumentare le iterazioni).


Ne ho parlato anche in un altro commento, ma en.wikipedia.org/wiki/Key_stretching

2

Personalmente non mi preoccuperei di più hashse, ma mi assicurerei anche di eseguire l' hashing di UserName (o di un altro campo ID utente) e della password in modo che due utenti con la stessa password non finiscano con lo stesso hash. Inoltre probabilmente aggiungerei anche un'altra stringa costante nella stringa di input per una buona misura.

$hashed_password = md5( "xxx" + "|" + user_name + "|" + plaintext_password);

13
In realtà, dovrebbe essere una stringa generata casualmente per ogni utente, non una costante.
Bill the Lizard,

7
Un segreto costante funziona (ed è più facile da lavorare), se si inserisce il nome utente come suggerito. Ciò produce essenzialmente una chiave casuale specifica dell'utente.
SquareCog

4
Un sale segreto costante è la sicurezza attraverso l'oscurità. Se il "segreto" scopre che stai utilizzando "xxx" + nome utente + password, un utente malintenzionato non ha nemmeno bisogno dei dati dei tuoi tavoli per lanciare un attacco contro di esso.
Bill the Lizard,

8
Non penso che sia sicurezza attraverso l'oscurità. Il motivo dell'uso di un salt è che non è possibile calcolare una tabella arcobaleno su più hash md5 contemporaneamente. Costruire uno per "xxx" + password (stesso sale) avviene una volta. Costruire una tabella per "xxx" + nome utente + password è peggio della forzatura brutale.
FryGuy,

5
@Bill the Lizard: "l'attacco si riduce alla costruzione di un dizionario per attaccare un nome utente specifico" è solo un attacco a forza bruta (in realtà anche peggio, perché oltre a calcolare tutti gli hash devi memorizzarli), quindi il sale funziona perfettamente in questo caso.
Kornel,

2

Supponiamo che tu usi l'algoritmo di hashing: calcola rot13, prendi i primi 10 caratteri. Se lo fai due volte (o addirittura 2000 volte) è possibile effettuare una funzione che è più veloce, ma che dà lo stesso risultato (vale a dire basta prendere i primi 10 caratteri).

Allo stesso modo può essere possibile realizzare una funzione più veloce che dia lo stesso output di una funzione di hashing ripetuta. Quindi la scelta della funzione di hashing è molto importante: come nell'esempio rot13 non viene dato che un hashing ripetuto migliorerà la sicurezza. Se non ci sono ricerche che affermano che l'algoritmo è progettato per un uso ricorsivo, è più sicuro supporre che non ti fornirà una protezione aggiuntiva.

Detto questo: per tutti tranne che per le funzioni di hashing più semplici, molto probabilmente ci vorranno esperti di crittografia per calcolare le funzioni più veloci, quindi se stai proteggendo dagli aggressori che non hanno accesso agli esperti di crittografia è probabilmente più sicuro in pratica usare una funzione di hashing ripetuta .


1

In generale, non fornisce alcuna sicurezza aggiuntiva per raddoppiare l'hash o doppia crittografare qualcosa. Se riesci a rompere l'hash una volta, puoi rompere di nuovo. Di solito non fa male alla sicurezza farlo.

Nel tuo esempio di utilizzo di MD5, come probabilmente sai ci sono alcuni problemi di collisione. "Double Hashing" non aiuta davvero a proteggersi da questo, dal momento che le stesse collisioni comporteranno comunque lo stesso primo hash, che è quindi possibile MD5 di nuovo per ottenere il secondo hash.

Questo protegge dagli attacchi del dizionario, come quei "database MD5 inversi", ma anche la salatura.

Su una tangente, la doppia crittografia di qualcosa non fornisce alcuna sicurezza aggiuntiva perché tutto ciò che fa è generare una chiave diversa che è una combinazione delle due chiavi effettivamente utilizzate. Quindi lo sforzo di trovare la "chiave" non è raddoppiato perché non è necessario trovare due chiavi. Questo non è vero per l'hash, perché il risultato dell'hash non è in genere della stessa lunghezza dell'input originale.


1
Tutto corretto, ma voglio solo notare che l'effetto del forte compromesso della resistenza alle collisioni su MD5 è un po 'sproporzionato - la maggior parte degli scenari che usano funzioni di crittografia non si basano su una forte resistenza alle collisioni, ma solo su una resistenza debole. Non sono interessati da questa vulnerabilità.
SquareCog

1

Il doppio hashing ha senso per me solo se eseguo l'hashing della password sul client, quindi salvo l'hash (con differenti salt) di quell'hash sul server.

In questo modo, anche se qualcuno ha violato il server (ignorando così la sicurezza fornita da SSL), non è ancora in grado di ottenere le password chiare.

Sì, avrà i dati necessari per violare il sistema, ma non sarebbe in grado di utilizzare tali dati per compromettere gli account esterni dell'utente. E si sa che le persone usano la stessa password praticamente per qualsiasi cosa.

L'unico modo per ottenere le password chiare è installare un keygen sul client - e questo non è più il tuo problema.

Quindi in breve:

  1. Il primo hashing sul client protegge i tuoi utenti in uno scenario di "violazione del server".
  2. Il secondo hash sul server serve a proteggere il tuo sistema se qualcuno ha bloccato il backup del tuo database, quindi non può usare quelle password per connettersi ai tuoi servizi.

1
+1 Stavo aspettando una risposta come questa, perché ho pensato allo stesso scenario in cui non si desidera archiviare la password in chiaro sul client, ma non inviare la password crittografata finale tramite il filo per fare un semplice confronto con il DB.
Segna il

1
Non aiuta per le app Web. se il tuo server è compromesso, anche il codice che il tuo server sta inviando al client è compromesso. L'attaccante disabiliterà l'hash lato client e acquisirà password non elaborate.
Clemente Cherlin,

0

La preoccupazione di ridurre lo spazio di ricerca è matematicamente corretta, sebbene lo spazio di ricerca rimanga abbastanza grande da essere utilizzato per tutti gli scopi pratici (supponendo di usare sali), a 2 ^ 128. Tuttavia, poiché stiamo parlando di password, il numero di possibili stringhe di 16 caratteri (alfanumerico, maiuscolo, alcuni simboli inseriti) è all'incirca 2 ^ 98, secondo i miei calcoli sul retro della busta. Quindi la diminuzione percepita nello spazio di ricerca non è realmente rilevante.

A parte questo, non c'è davvero alcuna differenza, dal punto di vista crittografico.

Sebbene esista una cripto-primitiva chiamata "catena hash" - una tecnica che ti consente di fare alcuni trucchi interessanti, come rivelare una chiave di firma dopo che è stata utilizzata, senza sacrificare l'integrità del sistema - data la sincronizzazione temporale minima, questo consente di eludere in modo chiaro il problema della distribuzione iniziale delle chiavi. Fondamentalmente, pre-calcoli un grande set di hash di hash - h (h (h (h .... (h (k)) ...))), usa l'ennesimo valore per firmare, dopo un intervallo impostato, invii estrarre la chiave e firmarla utilizzando la chiave (n-1). I destinatari ora possono verificare di aver inviato tutti i messaggi precedenti e nessuno può falsificare la tua firma poiché è trascorso il periodo di tempo per il quale è valida.

Riscrivere centinaia di migliaia di volte come suggerisce Bill è solo uno spreco della tua cpu .. usa una chiave più lunga se sei preoccupato che le persone rompano 128 bit.


1
Il ri-hashing riguarda proprio il rallentamento dell'hash. Questa è una funzionalità di sicurezza chiave nella crittografia basata su password. Vedi i collegamenti per PCKS5 e PBKDF2.
orip,

0

Come diverse risposte in questo articolo suggeriscono, ci sono alcuni casi in cui può migliorare la sicurezza e altri in cui la danneggia in modo definitivo. Esiste una soluzione migliore che migliorerà sicuramente la sicurezza. Invece di raddoppiare il numero di volte in cui calcoli l'hash, raddoppia la dimensione del tuo sale o raddoppia il numero di bit utilizzati nell'hash, oppure fai entrambe le cose! Invece di SHA-245, salta su SHA-512.


Questo non risponde alla domanda.
Bill the Lizard,

1
Il doppio hashing non vale la pena, ma raddoppiare la dimensione dell'hash. Penso che questo sia un punto più prezioso.
Stefan Rusek,

-1

Il doppio hashing è brutto perché è più che probabile che un utente malintenzionato abbia creato un tavolo per creare la maggior parte degli hash. Meglio salare i tuoi hash e mescolarli insieme. Ci sono anche nuovi schemi per "firmare" gli hash (fondamentalmente la salatura), ma in modo più sicuro.


-1

Sì.

Non usare assolutamente più iterazioni di una funzione hash convenzionale, come md5(md5(md5(password))). Nella migliore delle ipotesi si otterrà un aumento marginale della sicurezza (uno schema come questo non offre praticamente alcuna protezione contro un attacco GPU; basta pipeline.) Nella peggiore delle ipotesi, stai riducendo lo spazio di hash (e quindi la sicurezza) con ogni iterazione che aggiungi . Per quanto riguarda la sicurezza, è consigliabile assumere il peggio.

Non utilizzare una password è che è stato progettato da un crittografo competente per essere un efficace hash della password, e resistenti sia forza bruta e gli attacchi del tempo-spazio. Questi includono bcrypt, scrypt e in alcune situazioni PBKDF2. Anche l'hash basato su glibc SHA-256 è accettabile.


-1

Ho intenzione di uscire su un arto e dire che è più sicuro in determinate circostanze ... non ridimensionarmi ancora!

Da un punto di vista matematico / crittografico, è meno sicuro, per ragioni che sono sicuro che qualcun altro ti darà una spiegazione più chiara di quanto potrei.

Tuttavia , esistono grandi database di hash MD5, che hanno maggiori probabilità di contenere il testo "password" rispetto al MD5 di esso. Quindi, con il doppio hashing, si riduce l'efficacia di tali database.

Naturalmente, se usi un sale, questo vantaggio (svantaggio?) Scompare.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.