Ist "Double Hashing" ein Passwort weniger sicher als nur ein einziges Hashing?


293

Ist das zweimalige Hashing eines Passworts vor dem Speichern mehr oder weniger sicher als das einmalige Hashing?

Ich spreche davon:

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

statt nur dieses:

$hashed_password = hash($plaintext_password);

Wenn es weniger sicher ist, können Sie eine gute Erklärung (oder einen Link zu einer) geben?

Macht die verwendete Hash-Funktion auch einen Unterschied? Macht es einen Unterschied, wenn Sie md5 und sha1 (zum Beispiel) mischen, anstatt dieselbe Hash-Funktion zu wiederholen?

Hinweis 1: Wenn ich "Double Hashing" sage, spreche ich davon, ein Passwort zweimal zu haschen, um es verdeckter zu machen. Ich spreche nicht über die Technik zum Auflösen von Kollisionen .

Anmerkung 2: Ich weiß, dass ich ein zufälliges Salz hinzufügen muss, um es wirklich sicher zu machen. Die Frage ist, ob zweimaliges Hashing mit demselben Algorithmus dem Hash hilft oder schadet.


2
Hash(password)und Hash(Hash(password))sind ebenso unsicher. Beiden fehlt der Begriff der semantischen Sicherheit . Das heißt, die Ausgabe ist von zufällig zu unterscheiden. Zum Beispiel MD5("password")ist 5f4dcc3b5aa765d61d8327deb882cf99. Ich weiß, dass dies der MD5-Hash ist password, und er ist von zufällig zu unterscheiden. Stattdessen sollten Sie einen HMAC verwenden. Es ist nachweislich sicher und ein PRF.
JWW

Antworten:


267

Das einmalige Hashing eines Passworts ist unsicher

Nein, mehrere Hashes sind nicht weniger sicher. Sie sind ein wesentlicher Bestandteil der sicheren Verwendung von Passwörtern.

Das Iterieren des Hash erhöht die Zeit, die ein Angreifer benötigt, um jedes Kennwort in seiner Kandidatenliste auszuprobieren. Sie können die Zeit, die benötigt wird, um ein Passwort anzugreifen, leicht von Stunden auf Jahre verlängern.

Eine einfache Iteration reicht nicht aus

Die bloße Verkettung der Hash-Ausgabe mit der Eingabe reicht aus Sicherheitsgründen nicht aus. Die Iteration sollte im Kontext eines Algorithmus erfolgen, der die Entropie des Passworts beibehält. Glücklicherweise gibt es mehrere veröffentlichte Algorithmen, die ausreichend geprüft wurden, um Vertrauen in ihr Design zu schaffen.

Ein guter Algorithmus zur Schlüsselableitung wie PBKDF2 fügt das Kennwort in jede Hashing-Runde ein und verringert so die Bedenken hinsichtlich Kollisionen bei der Hash-Ausgabe. PBKDF2 kann unverändert für die Kennwortauthentifizierung verwendet werden. Bcrypt folgt der Schlüsselableitung mit einem Verschlüsselungsschritt. Auf diese Weise muss ein Angreifer einen bekannten Klartextangriff ausführen, wenn ein schneller Weg zum Umkehren der Schlüsselableitung gefunden wird.

So brechen Sie ein Passwort

Gespeicherte Passwörter müssen vor einem Offline-Angriff geschützt werden. Wenn Passwörter nicht gesalzen sind, können sie durch einen vorberechneten Wörterbuchangriff (z. B. mithilfe einer Regenbogentabelle) beschädigt werden. Andernfalls muss der Angreifer Zeit aufwenden, um für jedes Kennwort einen Hash zu berechnen und festzustellen, ob er mit dem gespeicherten Hash übereinstimmt.

Nicht alle Passwörter sind gleich wahrscheinlich. Angreifer suchen möglicherweise ausführlich nach allen kurzen Passwörtern, wissen jedoch, dass ihre Chancen auf Brute-Force-Erfolg mit jedem weiteren Charakter stark sinken. Stattdessen verwenden sie eine geordnete Liste der wahrscheinlichsten Passwörter. Sie beginnen mit "password123" und gehen zu weniger häufig verwendeten Passwörtern über.

Nehmen wir an, eine Angreiferliste ist mit 10 Milliarden Kandidaten lang. Angenommen, ein Desktop-System kann 1 Million Hashes pro Sekunde berechnen. Der Angreifer kann ihre gesamte Liste in weniger als drei Stunden testen, wenn nur eine Iteration verwendet wird. Wenn jedoch nur 2000 Iterationen verwendet werden, erstreckt sich diese Zeit auf fast 8 Monate. Um einen anspruchsvolleren Angreifer zu besiegen, der beispielsweise ein Programm herunterladen kann, das die Leistung seiner GPU nutzen kann, benötigen Sie mehr Iterationen.

Wieviel ist genug?

Die Anzahl der zu verwendenden Iterationen ist ein Kompromiss zwischen Sicherheit und Benutzererfahrung. Spezialisierte Hardware, die von Angreifern verwendet werden kann, ist billig, kann aber dennoch Hunderte Millionen Iterationen pro Sekunde ausführen. Die Leistung des Systems des Angreifers bestimmt, wie lange es dauert, ein Kennwort bei einer Reihe von Iterationen zu brechen. Es ist jedoch unwahrscheinlich, dass Ihre Anwendung diese spezielle Hardware verwendet. Wie viele Iterationen Sie ausführen können, ohne die Benutzer zu belasten, hängt von Ihrem System ab.

Sie können Benutzer wahrscheinlich eine zusätzliche ¾ Sekunde oder so während der Authentifizierung warten lassen. Profilieren Sie Ihre Zielplattform und verwenden Sie so viele Iterationen, wie Sie sich leisten können. Von mir getestete Plattformen (ein Benutzer auf einem mobilen Gerät oder viele Benutzer auf einer Serverplattform) können PBKDF2 mit 60.000 bis 120.000 Iterationen oder bcrypt mit einem Kostenfaktor von 12 oder 13 bequem unterstützen .

Mehr Hintergrund

In PKCS # 5 finden Sie maßgebliche Informationen zur Rolle von Salt und Iterationen beim Hashing. Obwohl PBKDF2 zum Generieren von Verschlüsselungsschlüsseln aus Kennwörtern gedacht war, eignet es sich gut als Einweg-Hash für die Kennwortauthentifizierung. Jede Iteration von bcrypt ist teurer als ein SHA-2-Hash, sodass Sie weniger Iterationen verwenden können, aber die Idee ist dieselbe. Bcrypt geht auch einen Schritt über die meisten PBKDF2-basierten Lösungen hinaus, indem der abgeleitete Schlüssel zum Verschlüsseln eines bekannten Klartextes verwendet wird. Der resultierende Chiffretext wird zusammen mit einigen Metadaten als "Hash" gespeichert. Nichts hindert Sie jedoch daran, dasselbe mit PBKDF2 zu tun.

Hier sind andere Antworten, die ich zu diesem Thema geschrieben habe:


68
Das absichtliche Erstellen eines langsamen Algorithmus ist eine akzeptierte Vorgehensweise, wenn Sie versuchen, Wörterbuchangriffe auf gefährdete Authentifizierungsspeicher zu verhindern. Die Technik wird als "Schlüsselverstärkung" oder "Schlüsseldehnung" bezeichnet. Siehe en.wikipedia.org/wiki/Key_stretching

17
@RoBorg: Es spielt keine Rolle, wie langsam Ihre Implementierung ist, aber wie langsam die Implementierung eines Angreifers sein wird: Wenn der Hash selbst tausende Male langsamer ist, benötigt ein Angreifer tausende Male so lange, um das Kennwort brutal zu erzwingen.
Orip

5
Möglicherweise möchten Sie Kollisionen innerhalb des 128-Bit-Bereichs 0 bis 2 ^ 128-1. Wenn der 2 ^ 128-Ausgaberaum des Hash-Algorithmus perfekt ist, haben Sie theoretisch nur eine Substitutions-Chiffre mit einem Alphabet von 2 ^ 128 Glyphen.
jmucchiello

13
@devin - es ist nicht "meine Lösung", es ist eine weit verbreitete Praxis, die in passwortbasierte Kryptografiestandards wie PKCS # 5 integriert ist und von Experten wie Robert Morris empfohlen wird. Es ist extrem skalierbar, da der Zeitaufwand für die Authentifizierung von Benutzern in einer legitimen Anwendung gering ist. Die Skalierung wird nur dann schwierig, wenn Ihre Anwendung Kennwörter knackt - daher die Empfehlung. Natürlich ist der Suchraum eines Hash kleiner als der von möglichen Passwörtern, aber selbst ein 128-Bit-Raum ist zu groß für eine Brute-Force-Suche. Die Bedrohung, gegen die man sich verteidigen muss, ist ein Offline-Wörterbuchangriff.
Erickson

6
Ich bezog mich nicht auf die Unannehmlichkeiten für den einzelnen Benutzer, sondern auf die Belastung des Servers, wenn Sie eine große Benutzerbasis hätten, da Sie sich auf die CPU-Auslastung verlassen, um die Anzahl der Anforderungen zu verlangsamen. Wenn Sie mehr CPU-Leistung hinzufügen, verringern Sie die Beschränkung für diese Brute-Force-Angreifer. - Sie sind jedoch in Bezug auf die Skalierbarkeit und die allgemein akzeptierte Praxis völlig korrekt. Ich habe mich in fast allen Dingen geirrt, die ich in meinen früheren Kommentaren gesagt habe. Entschuldigung :)
DevinB

227

Für diejenigen, die sagen, dass es sicher ist, sind sie im Allgemeinen korrekt . "Double" -Hashing (oder die logische Erweiterung davon, Iteration einer Hash-Funktion) ist für ein bestimmtes Anliegen absolut sicher, wenn es richtig gemacht wird.

Für diejenigen, die sagen, dass es unsicher ist, sind sie in diesem Fall richtig . Der in der Frage angegebene Code ist unsicher. Sprechen wir darüber, warum:

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

Es gibt zwei grundlegende Eigenschaften einer Hash-Funktion, die uns Sorgen machen:

  1. Pre-Image Resistance - Bei einem Hash $hsollte es schwierig sein, eine $msolche Nachricht zu finden$h === hash($m)

  2. Second-Pre-Bild Resistance - eine Meldung gegeben $m1, sollte es schwierig sein , eine andere Botschaft zu finden , $m2so dasshash($m1) === hash($m2)

  3. Kollisionsresistenz - Es sollte schwierig sein, ein Nachrichtenpaar ($m1, $m2)so zu finden, dass hash($m1) === hash($m2)(beachten Sie, dass dies dem Widerstand vor dem zweiten Bild ähnelt, sich jedoch darin unterscheidet, dass der Angreifer hier die Kontrolle über beide Nachrichten hat) ...

Für die Speicherung von Passwörtern ist alles, was uns wirklich wichtig ist, die Pre-Image-Resistenz . Die anderen beiden wären umstritten, da $m1es sich um das Passwort des Benutzers handelt, das wir schützen möchten . Wenn der Angreifer es also bereits hat, hat der Hash nichts zu schützen ...

HAFTUNGSAUSSCHLUSS

Alles, was folgt, basiert auf der Prämisse, dass uns nur die Pre-Image-Resistenz wichtig ist . Die beiden anderen grundlegenden Eigenschaften von Hash-Funktionen halten sich möglicherweise nicht (und normalerweise auch nicht) auf die gleiche Weise. Die Schlussfolgerungen in diesem Beitrag gelten daher nur, wenn Hash-Funktionen zum Speichern von Passwörtern verwendet werden. Sie sind im Allgemeinen nicht anwendbar ...

Lass uns anfangen

Lassen Sie uns für diese Diskussion unsere eigene Hash-Funktion erfinden:

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

Jetzt sollte es ziemlich offensichtlich sein, was diese Hash-Funktion bewirkt. Es summiert die ASCII-Werte jedes Eingabezeichens und nimmt dann das Modulo dieses Ergebnisses mit 256.

Testen wir es also:

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

Nun wollen wir sehen, was passiert, wenn wir es einige Male um eine Funktion herum ausführen:

$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";
}

Das gibt aus:

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

Hm, wow. Wir haben Kollisionen erzeugt !!! Versuchen wir herauszufinden, warum:

Hier ist die Ausgabe des Hashens einer Zeichenfolge jeder möglichen Hash-Ausgabe:

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

Beachten Sie die Tendenz zu höheren Zahlen. Das stellt sich als unser Tod heraus. Wenn Sie den Hash viermal ausführen ($ hash = ourHash ($ hash) `für jedes Element), erhalten Sie Folgendes:

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

Wir haben uns verengte bis 8 Werte nach unten ... Das ist schlecht ... Unsere ursprüngliche Funktion abgebildet S(∞)auf S(256). Das heißt, wir haben eine Zuordnung der Surjektivfunktion$input zu erstellt $output.

Da wir eine Surjektivfunktion haben, können wir nicht garantieren, dass die Zuordnung für eine Teilmenge der Eingabe keine Kollisionen aufweist (in der Praxis sogar).

Das ist hier passiert! Unsere Funktion war schlecht, aber deshalb hat das nicht funktioniert (deshalb hat es so schnell und so vollständig funktioniert).

Das gleiche passiert mit MD5. Es wird S(∞)auf abgebildet S(2^128). Da gibt es keine Garantie , dass das Laufen MD5(S(output))wird Injektive , was bedeutet , dass es nicht Kollisionen haben.

TL / DR-Abschnitt

Da die md5direkte Rückkopplung der Ausgabe Kollisionen erzeugen kann, erhöht jede Iteration die Wahrscheinlichkeit von Kollisionen. Dies ist jedoch ein linearer Anstieg, was bedeutet, dass die Ergebnismenge 2^128zwar reduziert wird, aber nicht schnell genug signifikant reduziert wird, um ein kritischer Fehler zu sein.

So,

$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

Je öfter Sie iterieren, desto weiter geht die Reduzierung.

Die Reparatur

Zum Glück gibt es eine triviale Möglichkeit, dies zu beheben: Geben Sie etwas in die weiteren Iterationen zurück:

$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    

Beachten Sie, dass die weiteren Iterationen nicht 2 ^ 128 für jeden einzelnen Wert für sind $input. Dies bedeutet, dass wir möglicherweise $inputWerte generieren können, die auf der ganzen Linie immer noch kollidieren (und sich daher bei weit weniger als 2^128möglichen Ausgängen niederlassen oder mitschwingen ). Aber das allgemeine Argument für $inputist immer noch so stark wie für eine einzelne Runde.

Warten Sie, war es? Lassen Sie uns dies mit unserer ourHash()Funktion testen . Umschalten auf $hash = ourHash($input . $hash);für 100 Iterationen:

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

Es gibt dort immer noch ein grobes Muster, aber beachten Sie, dass es nicht mehr ein Muster ist als unsere zugrunde liegende Funktion (die bereits ziemlich schwach war).

Beachten Sie jedoch, dass 0und 3Kollisionen wurden, obwohl sie nicht im einzigen Lauf waren. Dies ist eine Anwendung dessen, was ich zuvor gesagt habe (dass der Kollisionswiderstand für alle Eingaben gleich bleibt, sich jedoch aufgrund von Fehlern im zugrunde liegenden Algorithmus bestimmte Kollisionsrouten öffnen können).

TL / DR-Abschnitt

Indem wir die Eingabe in jede Iteration zurückkoppeln, brechen wir effektiv alle Kollisionen, die in der vorherigen Iteration aufgetreten sind.

Daher md5($input . md5($input));sollte ( theoretisch mindestens) so stark sein wie md5($input).

Ist das wichtig?

Ja. Dies ist einer der Gründe, warum PBKDF2 PBKDF1 in RFC 2898 ersetzt hat . Betrachten Sie die inneren Schleifen der beiden ::

PBKDF1:

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

Wo cist die Iterationszahl, Pist das Passwort und Sist das Salz

PBKDF2:

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

Wo PRF wirklich nur ein HMAC ist. Aber für unsere Zwecke hier sagen wir einfach das PRF(P, S) = Hash(P || S)(das heißt, der PRF von 2 Eingängen ist ungefähr der gleiche wie der Hash mit den beiden miteinander verketteten). Es ist sehr viel nicht , aber für unsere Zwecke ist es.

PBKDF2 behält also die Kollisionsbeständigkeit der zugrunde liegenden HashFunktion bei, während PBKDF1 dies nicht tut.

Alles zusammenbinden:

Wir kennen sichere Möglichkeiten, einen Hash zu iterieren. Eigentlich:

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

Ist normalerweise sicher.

Um nun zu untersuchen, warum wir es haschen möchten, analysieren wir die Entropiebewegung.

Ein Hash nimmt die unendliche Menge auf: S(∞)und erzeugt eine kleinere Menge mit gleichbleibender Größe S(n). Die nächste Iteration (vorausgesetzt, die Eingabe wird zurückgegeben) S(∞)wird S(n)erneut zugeordnet:

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

Beachten Sie, dass die endgültige Ausgabe genau die gleiche Entropie aufweist wie die erste . Durch Iterieren wird es nicht " dunkler ". Die Entropie ist identisch. Es gibt keine magische Quelle für Unvorhersehbarkeit (es ist eine Pseudozufallsfunktion, keine Zufallsfunktion).

Das Iterieren hat jedoch einen Vorteil. Es macht den Hashing-Prozess künstlich langsamer. Und deshalb kann das Iterieren eine gute Idee sein. Tatsächlich ist es das Grundprinzip der meisten modernen Passwort-Hashing-Algorithmen (die Tatsache, dass immer wieder etwas langsamer wird).

Langsam ist gut, weil es die primäre Sicherheitsbedrohung bekämpft: Brute Forcing. Je langsamer wir unseren Hashing-Algorithmus machen, desto schwerer müssen Angreifer arbeiten, um uns gestohlene Passwort-Hashes anzugreifen. Und das ist gut so !!!


1
$output = md5($output); // < 2^128 possibilities--- ist es wirklich streng <oder <=?
Zerkms

2
@ Zerkms: Es ist nicht unbedingt alles. Wir müssten einige sehr spezifische Details der zugrunde liegenden Funktion kennen ( md5()in diesem Fall), um wirklich sicher zu sein. Aber im Allgemeinen wird es so sein <und nicht <=... Denken Sie daran, wir sprechen über die Größe des Satzes $outputfür alle möglichen $inputs. Wenn wir also auch nur eine Kollision haben <, <ist dies der bessere Generalisierer.
Ircmaxell

2
@ TomášFejfar Ich denke, die Frage bezieht sich nicht auf Kollisionen im Allgemeinen, sondern auf Kollisionen im strengen Ausgabesatz (2 ^ 128 Ausgänge mit jeweils genau 128 Bit Breite). Das könnte injektiv sein, aber meines Wissens ist ein generischer Beweis nicht möglich (nur ein Beispiel für eine Kollision für einen bestimmten Algorithmus). Betrachten Sie die Hash-Funktion, die die Eingabe einfach zurückgibt, wenn sie 128 Bit beträgt (und ansonsten Hashes). Im Allgemeinen wäre es surjektiv, aber wenn es seine Ausgabe speist, wäre es immer injektiv ... Das ist der Streitpunkt ...
ircmaxell


6
Für diejenigen, die Zeit sparen möchten, indem sie nicht überprüfen müssen, wie diese Diskussion zwischen Dan und ircmaxell beendet wurde, endete sie gut : Dan stimmte ircmaxell zu.
Jeromej

51

Ja, erneutes Hashing reduziert den Suchraum, aber nein, das spielt keine Rolle - die effektive Reduzierung ist unbedeutend.

Re-Hashing erhöht die Zeit, die für Brute-Force benötigt wird, aber nur zweimal ist auch nicht optimal.

Was Sie wirklich wollen, ist das Hash-Passwort mit PBKDF2 - eine bewährte Methode zur Verwendung eines sicheren Hashs mit Salt und Iterationen. Schauen Sie sich diese SO-Antwort an .

EDIT : Ich hätte fast vergessen - NICHT MD5 VERWENDEN !!!! Verwenden Sie einen modernen kryptografischen Hash wie die SHA-2-Familie (SHA-256, SHA-384 und SHA-512).


2
@DFTR - vereinbart. bcrypt oder scrypt sind bessere Optionen.
Orip

Verwenden Sie auch diese nicht (SHA-2-Familie), sie können jetzt auch leicht geknackt werden. Überprüfen Sie crackstation.net auf Beweise. Wenn überhaupt, verwenden Sie scrypt oder PBKDF2, die auf KDFs (Key Derivation Function) basierende kryptografische Hash-Funktionen sind.
Theodore

3
Im Jahr 2016 sind Argon2 und Scrypt diejenigen, die jeder verwenden sollte
Seidenfeuer

10

Ja - es reduziert die Anzahl der möglichen Zeichenfolgen, die mit der Zeichenfolge übereinstimmen.

Wie Sie bereits erwähnt haben, sind gesalzene Hashes viel besser.

Ein Artikel hier: http://websecurity.ro/blog/2007/11/02/md5md5-vs-md5/ versucht einen Beweis dafür, warum es gleichwertig ist, aber ich bin mir mit der Logik nicht sicher. Teilweise gehen sie davon aus, dass keine Software zur Analyse von md5 (md5 (Text)) verfügbar ist, aber es ist offensichtlich ziemlich trivial, die Regenbogentabellen zu erstellen.

Ich halte immer noch an meiner Antwort fest, dass es weniger Hashes vom Typ md5 (md5 (Text)) als Hashes vom Typ md5 (Text) gibt, was die Wahrscheinlichkeit einer Kollision erhöht (auch wenn dies immer noch unwahrscheinlich ist) und den Suchraum verringert.


5

Die meisten Antworten stammen von Personen ohne kryptografischen oder sicherheitstechnischen Hintergrund. Und sie sind falsch. Verwenden Sie ein Salz, wenn möglich einmalig pro Datensatz. MD5 / SHA / etc sind zu schnell, das Gegenteil von dem, was Sie wollen. PBKDF2 und bcrypt sind langsamer (was gut ist), können aber mit ASICs / FPGA / GPUs besiegt werden (heutzutage sehr erschwinglich). Daher wird ein speicherintensiver Algorithmus benötigt: Geben Sie scrypt ein .

Hier ist eine Erklärung für Laien zu Salz und Geschwindigkeit (aber nicht zu speicherintensiven Algorithmen).


4

Ich betrachte dies nur von einem praktischen Standpunkt aus. Was ist der Hacker danach? Die Kombination von Zeichen, die beim Durchlaufen der Hash-Funktion den gewünschten Hash generiert.

Sie speichern nur den letzten Hash, daher muss der Hacker nur einen Hash bruteforce. Angenommen, Sie haben ungefähr die gleiche Wahrscheinlichkeit, bei jedem Bruteforce-Schritt über den gewünschten Hash zu stolpern, ist die Anzahl der Hashes irrelevant. Sie könnten eine Million Hash-Iterationen durchführen, und dies würde die Sicherheit nicht um ein Bit erhöhen oder verringern, da am Ende der Zeile immer noch nur ein Hash zu brechen ist und die Wahrscheinlichkeit, ihn zu brechen, die gleiche ist wie bei jedem Hash.

Vielleicht denken die vorherigen Poster, dass die Eingabe relevant ist; es ist nicht. Solange alles, was Sie in die Hash-Funktion eingeben, den gewünschten Hash generiert, werden Sie durch, korrekte Eingabe oder falsche Eingabe.

Jetzt sind Regenbogentische eine andere Geschichte. Da eine Regenbogentabelle nur unformatierte Passwörter enthält, kann zweimaliges Hashing eine gute Sicherheitsmaßnahme sein, da eine Regenbogentabelle, die jeden Hash jedes Hash enthält, zu groß wäre.

Natürlich betrachte ich nur das Beispiel, das das OP gegeben hat, bei dem es sich nur um ein Klartext-Passwort handelt, das gehasht wird. Wenn Sie den Benutzernamen oder ein Salt in den Hash aufnehmen, ist das eine andere Geschichte. zweimaliges Hashing ist völlig unnötig, da der Regenbogentisch bereits zu groß wäre, um praktisch zu sein und den richtigen Hash zu enthalten.

Jedenfalls kein Sicherheitsexperte hier, aber genau das habe ich aus meiner Erfahrung herausgefunden.


Diese Antwort ist in jeder Hinsicht falsch. 1. Die Kenntnis des vorletzten Hashs bietet einem Angreifer keinen Wert, da die Eingabe in einen iterierten Hash das Kennwort ist , das dann viele Male (nicht einmal) gehasht wird. 2. Der Eingabebereich besteht aus Kennwörtern, der Ausgabebereich besteht aus gehashten Kennwörtern. Der Speicherplatz typischer Passwörter ist viel kleiner als der Ausgabebereich. 3. Rainbow-Tabellen für ungesalzene Double-Hash-Passwörter sind nicht größer als Rainbow-Tabellen für ungesalzene Single-Hash-Passwörter. 4. Benutzernamen haben eine niedrige Entropie, ein gutes Salz ist zufällig. 5. Das Salzen ersetzt nicht die Iteration. Du brauchst beides.
Clement Cherlin

3

Nach dem, was ich gelesen habe, kann es tatsächlich empfohlen werden, das Passwort hunderte oder tausende Male neu zu hashen.

Die Idee ist, dass es für einen Angreifer mehr Arbeit ist, viele Vermutungen durchzugehen, um das Kennwort zu knacken, wenn Sie mehr Zeit für die Codierung des Kennworts benötigen. Dies scheint der Vorteil eines erneuten Hashings zu sein - nicht, dass es kryptografisch sicherer ist, aber es dauert einfach länger, einen Wörterbuchangriff zu generieren.

Natürlich werden Computer immer schneller, daher nimmt dieser Vorteil mit der Zeit ab (oder Sie müssen die Iterationen erhöhen).


Ich erwähnte dies auch in einem anderen Kommentar, aber en.wikipedia.org/wiki/Key_stretching

2

Persönlich würde ich mich nicht mit mehreren Hashs beschäftigen, aber ich würde sicherstellen, dass auch der Benutzername (oder ein anderes Benutzer-ID-Feld) sowie das Kennwort gehasht werden, damit zwei Benutzer mit demselben Kennwort nicht denselben Hash erhalten. Außerdem würde ich wahrscheinlich auch eine andere konstante Zeichenfolge in die Eingabezeichenfolge werfen, um ein gutes Maß zu erreichen.

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

13
Eigentlich sollte es sich um eine zufällig für jeden Benutzer generierte Zeichenfolge handeln, nicht um eine Konstante.
Bill the Lizard

7
Ein konstantes Geheimnis funktioniert (und ist einfacher zu bearbeiten), wenn Sie den Benutzernamen wie vorgeschlagen eingeben. Dies erzeugt im Wesentlichen einen zufälligen benutzerspezifischen Schlüssel.
SquareCog

4
Ein ständiges geheimes Salz ist Sicherheit durch Dunkelheit. Wenn das "Geheimnis" herauskommt, dass Sie "xxx" + Benutzername + Passwort verwenden, benötigt ein Angreifer nicht einmal Daten aus Ihren Tabellen, um einen Angriff dagegen zu starten.
Bill the Lizard

8
Ich denke nicht, dass es Sicherheit durch Dunkelheit ist. Der Grund für die Verwendung eines Salzes ist, dass Sie eine Regenbogentabelle nicht für mehrere MD5-Hashes gleichzeitig berechnen können. Das Erstellen eines für "xxx" + Passwort (dasselbe Salz) erfolgt einmal. Das Erstellen einer Tabelle für "xxx" + Benutzername + Passwort ist schlimmer als das brutale Erzwingen.
FryGuy

5
@ Bill the Lizard: "Der Angriff wird darauf reduziert, ein Wörterbuch zu erstellen, um einen bestimmten Benutzernamen anzugreifen" ist nur ein Brute-Force-Angriff (sogar noch schlimmer, da Sie zusätzlich zur Berechnung aller Hashes diese speichern müssen), damit das Salz funktioniert perfekt in diesem Fall.
Kornel

2

Nehmen wir an, Sie verwenden den Hashing-Algorithmus: Berechnen Sie rot13 und nehmen Sie die ersten 10 Zeichen. Wenn Sie dies zweimal (oder sogar 2000 Mal) tun, ist es möglich, eine Funktion zu erstellen, die schneller ist, aber das gleiche Ergebnis liefert (nämlich nur die ersten 10 Zeichen).

Ebenso kann es möglich sein, eine schnellere Funktion zu erstellen, die dieselbe Ausgabe wie eine wiederholte Hashing-Funktion liefert. Daher ist Ihre Wahl der Hashing-Funktion sehr wichtig: Wie beim Beispiel rot13 wird nicht angegeben, dass wiederholtes Hashing die Sicherheit verbessert. Wenn es keine Untersuchungen gibt, die besagen, dass der Algorithmus für die rekursive Verwendung ausgelegt ist, ist es sicherer anzunehmen, dass er Ihnen keinen zusätzlichen Schutz bietet.

Das heißt: Für alle außer den einfachsten Hashing-Funktionen sind höchstwahrscheinlich Kryptografie-Experten erforderlich, um die schnelleren Funktionen zu berechnen. Wenn Sie sich also vor Angreifern schützen, die keinen Zugriff auf Kryptografie-Experten haben, ist es in der Praxis wahrscheinlich sicherer, eine wiederholte Hashing-Funktion zu verwenden .


1

Im Allgemeinen bietet es keine zusätzliche Sicherheit, um etwas zu verdoppeln oder zu verschlüsseln. Wenn Sie den Hash einmal brechen können, können Sie ihn erneut brechen. Normalerweise schadet dies jedoch nicht der Sicherheit.

In Ihrem Beispiel für die Verwendung von MD5 gibt es, wie Sie wahrscheinlich wissen, einige Kollisionsprobleme. "Double Hashing" schützt nicht wirklich davor, da dieselben Kollisionen immer noch zu demselben ersten Hash führen, den Sie dann erneut mit MD5 abrufen können, um den zweiten Hash zu erhalten.

Dies schützt vor Wörterbuchangriffen wie diesen "Reverse MD5-Datenbanken", aber auch vor Salting.

Bei einer Tangente bietet die doppelte Verschlüsselung keine zusätzliche Sicherheit, da sie lediglich zu einem anderen Schlüssel führt, der eine Kombination der beiden tatsächlich verwendeten Schlüssel ist. Der Aufwand, den "Schlüssel" zu finden, wird also nicht verdoppelt, da zwei Schlüssel nicht gefunden werden müssen. Dies gilt nicht für Hashing, da das Ergebnis des Hash normalerweise nicht dieselbe Länge wie die ursprüngliche Eingabe hat.


1
Alles in Ordnung, aber ich möchte nur darauf hinweisen, dass die Auswirkung des Kompromisses zwischen starker Kollisionsresistenz und MD5 etwas überproportional ist - die meisten Szenarien, in denen Krypto-Hash-Funktionen verwendet werden, beruhen nicht auf starker Kollisionsresistenz, sondern nur auf schwacher Resistenz. Sie sind von dieser Sicherheitsanfälligkeit nicht betroffen.
SquareCog

1

Doppeltes Hashing ist für mich nur dann sinnvoll, wenn ich das Kennwort auf dem Client hashe und dann den Hash (mit unterschiedlichem Salz) dieses Hash auf dem Server speichere.

Auf diese Weise kann jemand, der sich in den Server gehackt hat (wodurch die von SSL bereitgestellte Sicherheit ignoriert wird), immer noch nicht an die eindeutigen Kennwörter gelangen.

Ja, er verfügt über die erforderlichen Daten, um in das System einzudringen, kann diese Daten jedoch nicht verwenden, um externe Konten des Benutzers zu gefährden. Und es ist bekannt, dass Menschen für praktisch alles dasselbe Passwort verwenden.

Der einzige Weg, um an die eindeutigen Passwörter zu gelangen, ist die Installation eines Keygens auf dem Client - und das ist nicht mehr Ihr Problem.

Also kurz gesagt:

  1. Das erste Hashing auf dem Client schützt Ihre Benutzer in einem Szenario mit "Serververletzung".
  2. Das zweite Hashing auf dem Server dient zum Schutz Ihres Systems, wenn jemand Ihre Datenbanksicherung abgerufen hat, sodass er diese Kennwörter nicht verwenden kann, um eine Verbindung zu Ihren Diensten herzustellen.

1
+1 Ich habe auf eine Antwort wie diese gewartet, weil ich an dasselbe Szenario gedacht habe, in dem Sie kein Nur-Text-Passwort auf dem Client speichern möchten, aber auch nicht das endgültige verschlüsselte Passwort über die Leitung senden möchten, um eine Antwort zu erhalten einfacher Vergleich mit der DB.
Mark

1
Hilft nicht für Web-Apps. Wenn Ihr Server gefährdet ist, ist auch der Code, den Ihr Server an den Client sendet, gefährdet. Der Angreifer deaktiviert Ihren clientseitigen Hash und erfasst unformatierte Kennwörter.
Clement Cherlin

0

Die Sorge um die Reduzierung des Suchraums ist mathematisch korrekt, obwohl der Suchraum groß genug bleibt, um für alle praktischen Zwecke (vorausgesetzt, Sie verwenden Salze) bei 2 ^ 128 zu liegen. Da es sich jedoch um Passwörter handelt, beträgt die Anzahl der möglichen Zeichenfolgen mit 16 Zeichen (alphanumerisch, Großbuchstaben, einige eingeworfene Symbole) nach meinen Berechnungen auf der Rückseite des Umschlags ungefähr 2 ^ 98. Die wahrgenommene Verringerung des Suchraums ist also nicht wirklich relevant.

Abgesehen davon gibt es kryptografisch gesehen wirklich keinen Unterschied.

Obwohl es ein Krypto-Grundelement gibt, das als "Hash-Kette" bezeichnet wird - eine Technik, mit der Sie einige coole Tricks ausführen können, z. B. die Offenlegung eines Signaturschlüssels nach seiner Verwendung, ohne die Integrität des Systems zu beeinträchtigen - bei minimaler Zeitsynchronisation ermöglicht es Ihnen, das Problem der anfänglichen Schlüsselverteilung sauber zu umgehen. Grundsätzlich berechnen Sie eine große Menge von Hashes von Hashes - h (h (h (h .... (h (k)) ...)). Verwenden Sie den n-ten Wert, um nach einem festgelegten Intervall, das Sie senden, zu signieren Ziehen Sie den Schlüssel heraus und signieren Sie ihn mit dem Schlüssel (n-1). Die Empfänger können jetzt überprüfen, ob Sie alle vorherigen Nachrichten gesendet haben, und niemand kann Ihre Unterschrift fälschen, seit der Zeitraum, für den sie gültig ist, abgelaufen ist.

Hunderttausende Male neu zu hashen, wie Bill vorschlägt, ist nur eine Verschwendung Ihrer CPU. Verwenden Sie einen längeren Schlüssel, wenn Sie sich Sorgen machen, dass Leute 128 Bit brechen.


1
Beim erneuten Hashing geht es genau darum, den Hash zu verlangsamen. Dies ist eine wichtige Sicherheitsfunktion in der kennwortbasierten Kryptografie. Siehe die Links für PCKS5 und PBKDF2.
Orip

0

Wie mehrere Antworten in diesem Artikel nahe legen, gibt es einige Fälle, in denen die Sicherheit verbessert werden kann, und andere, in denen dies definitiv schadet. Es gibt eine bessere Lösung, die die Sicherheit definitiv verbessert. Anstatt die Anzahl der Berechnungen des Hashs zu verdoppeln, die Größe Ihres Salzes zu verdoppeln oder die Anzahl der im Hash verwendeten Bits zu verdoppeln, oder beides! Springen Sie anstelle von SHA-245 zu SHA-512.


Dies beantwortet die Frage nicht.
Bill the Lizard

1
Doppeltes Hashing ist die Mühe nicht wert, aber das Verdoppeln Ihrer Hash-Größe ist es. Ich denke, das ist ein wertvollerer Punkt.
Stefan Rusek

-1

Double Hashing ist hässlich, da ein Angreifer höchstwahrscheinlich eine Tabelle erstellt hat, um die meisten Hashes zu erstellen. Besser ist es, die Hashes zu salzen und die Hashes miteinander zu mischen. Es gibt auch neue Schemata zum "Signieren" von Hashes (im Grunde Salzen), jedoch auf sicherere Weise.


-1

Ja.

Absolut nicht mehr Iterationen einer herkömmlichen Hash - Funktion verwenden, wie md5(md5(md5(password))). Im besten Fall erhalten Sie eine geringfügige Erhöhung der Sicherheit (ein solches Schema bietet kaum Schutz vor einem GPU-Angriff; leiten Sie ihn einfach weiter). Im schlimmsten Fall reduzieren Sie mit jeder hinzugefügten Iteration Ihren Hash-Speicherplatz (und damit die Sicherheit) . In Bezug auf die Sicherheit ist es ratsam, das Schlimmste anzunehmen.

Sie verwenden ist ein Kennwort , das ist schon entworfen von einem kompetenten Kryptograph zu sein ein wirksames Passwort - Hash, und resistent gegen sowohl Brute-Force - und Raum - Zeit-Attacken. Dazu gehören bcrypt, scrypt und in einigen Situationen PBKDF2. Der glibc SHA-256-basierte Hash ist ebenfalls akzeptabel.


-1

Ich werde mich auf die Beine stellen und sagen, dass es unter bestimmten Umständen sicherer ist ... aber stimmen Sie mich noch nicht ab!

Aus mathematischer / kryptografischer Sicht ist es weniger sicher, aus Gründen, von denen ich sicher bin, dass jemand anderes Ihnen eine klarere Erklärung geben wird, als ich könnte.

jedoch existieren große Datenbanken von MD5 - Hashes, die eher den „Passwort“ Text als die MD5 davon enthalten. Durch Double-Hashing verringern Sie also die Effektivität dieser Datenbanken.

Wenn Sie ein Salz verwenden, verschwindet dieser Vorteil (Nachteil?) Natürlich.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.