Czy „podwójne hashowanie” jest mniej bezpieczne niż jednorazowe hashowanie?

293

Czy hashowanie dwa razy przed przechowywaniem jest mniej lub bardziej bezpieczne niż jednorazowe hashowanie?

Mówię o tym:

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

zamiast tego:

$hashed_password = hash($plaintext_password);

Jeśli jest mniej bezpieczny, czy możesz podać dobre wyjaśnienie (lub link do jednego)?

Czy używana funkcja skrótu ma znaczenie? Czy robi to różnicę, jeśli miksujesz md5 i sha1 (na przykład) zamiast powtarzać tę samą funkcję skrótu?

Uwaga 1: Kiedy mówię „podwójne haszowanie”, mówię o haszowaniu hasła dwukrotnie, aby uczynić go bardziej zaciemnionym. Nie mówię o technice rozwiązywania kolizji .

Uwaga 2: Wiem, że muszę dodać losową sól, aby naprawdę była bezpieczna. Pytanie brzmi, czy mieszanie dwukrotnie tym samym algorytmem pomaga, czy boli hash.

Bill jaszczurka
źródło
2
Hash(password)i Hash(Hash(password))są równie niepewni. Obaj nie mają pojęcia bezpieczeństwa semantycznego . Oznacza to, że wynik można odróżnić od losowej. Na przykład MD5("password")jest 5f4dcc3b5aa765d61d8327deb882cf99. Wiem, że to hash MD5 password, a to jest odróżnić od losowych. Zamiast tego powinieneś użyć HMAC. Jest to możliwe do udowodnienia bezpieczeństwo i jego PRF.
jww

Odpowiedzi:

267

Jednorazowe haszowanie hasła jest niepewne

Nie, wiele skrótów nie jest mniej bezpieczne; są istotną częścią bezpiecznego używania hasła.

Iteracja skrótu wydłuża czas atakującego na wypróbowanie każdego hasła na liście kandydatów. Możesz łatwo wydłużyć czas potrzebny do zaatakowania hasła z kilku godzin na lata.

Prosta iteracja nie wystarczy

Zwykłe łączenie danych wyjściowych mieszania z danymi wejściowymi nie jest wystarczające dla bezpieczeństwa. Iteracja powinna odbywać się w kontekście algorytmu, który zachowuje entropię hasła. Na szczęście istnieje kilka opublikowanych algorytmów, które poddano wystarczającej kontroli, aby dać pewność co do ich projektu.

Dobry algorytm wyprowadzania klucza, taki jak PBKDF2, wstrzykuje hasło do każdej rundy mieszania, łagodząc obawy dotyczące kolizji w wyniku mieszania. PBKDF2 może być używany do uwierzytelniania hasłem bez zmian. Bcrypt śledzi wyprowadzenie klucza z krokiem szyfrowania; w ten sposób, jeśli zostanie wykryty szybki sposób na odwrócenie wyprowadzania klucza, atakujący nadal musi wykonać atak znanego tekstu jawnego.

Jak złamać hasło

Przechowywane hasła wymagają ochrony przed atakiem offline. Jeśli hasła nie są solone, można je złamać za pomocą wstępnie obliczonego ataku słownikowego (na przykład przy użyciu Tęczowej tabeli). W przeciwnym razie atakujący musi poświęcić czas na obliczenie skrótu dla każdego hasła i sprawdzenie, czy pasuje ono do przechowywanego skrótu.

Wszystkie hasła nie są jednakowo prawdopodobne. Atakujący mogą wyczerpująco przeszukiwać wszystkie krótkie hasła, ale wiedzą, że ich szanse na sukces brutalnej siły gwałtownie spadają z każdą dodatkową postacią. Zamiast tego używają uporządkowanej listy najbardziej prawdopodobnych haseł. Zaczynają się od „password123” i przechodzą do rzadziej używanych haseł.

Powiedzmy, że lista atakujących jest długa, z 10 miliardami kandydatów; załóżmy również, że system komputerowy może obliczyć 1 milion skrótów na sekundę. Atakujący może przetestować całą listę na mniej niż trzy godziny, jeśli zastosowana zostanie tylko jedna iteracja. Ale jeśli zastosuje się tylko 2000 iteracji, czas ten wydłuży się do prawie 8 miesięcy. Aby pokonać bardziej wyrafinowanego atakującego - na przykład zdolnego do pobrania programu, który może wykorzystać moc swojego GPU - potrzebujesz więcej iteracji.

Jak duzo wystarczy?

Liczba iteracji do wykorzystania jest kompromisem między bezpieczeństwem a wygodą użytkownika. Specjalistyczny sprzęt, z którego mogą korzystać osoby atakujące, jest tani, ale wciąż może wykonywać setki milionów iteracji na sekundę. Wydajność systemu atakującego decyduje o tym, ile czasu zajmuje złamanie hasła przy danej liczbie iteracji. Ale Twoja aplikacja prawdopodobnie nie używa tego specjalistycznego sprzętu. Ile iteracji możesz wykonać bez obciążania użytkowników, zależy od twojego systemu.

Prawdopodobnie możesz pozwolić użytkownikom czekać około extra sekundy podczas uwierzytelniania. Profiluj platformę docelową i używaj tyle iteracji, ile możesz sobie pozwolić. Platformy, które przetestowałem (jeden użytkownik na urządzeniu mobilnym lub wielu użytkowników na platformie serwerowej) mogą wygodnie obsługiwać PBKDF2 z 60 000 do 120 000 iteracji lub bcrypt o współczynniku kosztu 12 lub 13.

Więcej tła

Przeczytaj PKCS # 5, aby uzyskać wiarygodne informacje na temat roli soli i iteracji w mieszaniu. Mimo że PBKDF2 był przeznaczony do generowania kluczy szyfrowania z haseł, działa dobrze jako jednokierunkowy skrót do uwierzytelniania hasła. Każda iteracja bcrypt jest droższa niż skrót SHA-2, więc możesz użyć mniejszej liczby iteracji, ale idea jest taka sama. Bcrypt wykracza także poza większość rozwiązań opartych na PBKDF2, używając klucza pochodnego do szyfrowania znanego zwykłego tekstu. Powstały tekst zaszyfrowany jest przechowywany jako „skrót” wraz z niektórymi metadanymi. Jednak nic nie stoi na przeszkodzie, abyś zrobił to samo z PBKDF2.

Oto inne odpowiedzi, które napisałem na ten temat:

erickson
źródło
68
Celowe tworzenie powolnego algorytmu jest przyjętą praktyką, gdy próbujesz zapobiec atakom słownikowym na zainfekowane sklepy uwierzytelniające. Technika ta nazywana jest „wzmocnieniem klucza” lub „rozciąganiem klucza”. Zobacz en.wikipedia.org/wiki/Key_stretching
17
@RoBorg: nie ma znaczenia, jak powolna jest twoja implementacja, ale jak powolna będzie implementacja atakującego: jeśli sam hash jest tysiące razy wolniejszy, atakujący zajmie tysiące razy więcej czasu, zanim użyjesz siły.
orip
5
Prawdopodobnie chcesz kolizji w przestrzeni 128-bitowej od 0 do 2 ^ 128-1. Jeśli przestrzeń wyjściowa algorytmu skrótu 2 ^ 128 jest idealna, to teoretycznie masz po prostu szyfr zastępczy z alfabetem 2 ^ 128 glifów.
jmucchiello
13
@devin - to nie jest „moje rozwiązanie”, to powszechnie akceptowana praktyka, wbudowana w standardy kryptografii oparte na hasłach, takie jak PKCS # 5, i zalecane przez ekspertów takich jak Robert Morris. Jest niezwykle skalowalny, ponieważ ułamek czasu poświęcanego na uwierzytelnianie użytkowników jest niewielki w legalnej aplikacji. Skalowanie staje się trudne tylko wtedy, gdy aplikacja łamie hasła - stąd zalecenie. Z pewnością przestrzeń wyszukiwania skrótu jest mniejsza niż możliwych haseł, ale nawet 128-bitowa przestrzeń jest zbyt duża, aby szukać z użyciem siły. Groźba obrony przed atakiem słownikowym offline.
erickson
6
Nie mówiłem o niedogodnościach dla poszczególnych użytkowników, ale raczej o stresie, jaki nałożyłby się na serwer, gdybyś miał dużą bazę użytkowników, ponieważ polegasz na obciążeniu procesora w celu spowolnienia liczby żądań. Oznacza to, że jeśli dodasz więcej mocy procesora, zmniejszysz ograniczenie tych atakujących brutalną siłą. - Masz jednak całkowitą rację co do skalowalności i powszechnie przyjętej praktyki. Myliłem się co do prawie wszystkich rzeczy, które powiedziałem w moich wcześniejszych komentarzach. Przepraszamy :)
DevinB
227

Ci, którzy twierdzą, że jest bezpieczny, ogólnie mają rację . „Podwójne” mieszanie (lub logiczne rozwinięcie tego, iteracja funkcji skrótu) jest absolutnie bezpieczne, jeśli jest wykonane właściwie , z uwagi na konkretny problem.

Tym, którzy twierdzą, że jest niepewna, mają rację w tym przypadku . Kod opublikowany w pytaniu jest niepewny. Porozmawiajmy o tym, dlaczego:

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

Istnieją dwie podstawowe właściwości funkcji skrótu, które nas niepokoją:

  1. Pre-Image Resistance - Biorąc pod uwagę skrót $h, znalezienie $mtakiego komunikatu powinno być trudne$h === hash($m)

  2. Odporność na drugie zdjęcie wstępne - biorąc pod uwagę wiadomość $m1, znalezienie innej wiadomości $m2takiej jak ta powinno być trudnehash($m1) === hash($m2)

  3. Odporność na kolizje - znalezienie ($m1, $m2)takich wiadomości powinno być trudne hash($m1) === hash($m2)(należy pamiętać, że jest to podobne do odporności na drugie zdjęcie wstępne, ale różni się tym, że tutaj atakujący kontroluje obie wiadomości) ...

Jeśli chodzi o przechowywanie haseł , wszystko, na czym nam zależy, to Odporność na obraz wstępny . Pozostałe dwa byłyby dyskusyjne, ponieważ $m1to hasło użytkownika staramy się chronić. Więc jeśli atakujący już go ma, skrót nie ma nic do ochrony ...

ZRZECZENIE SIĘ

Wszystko, co następuje, opiera się na założeniu, że wszystkim, na czym nam zależy, jest odporność na obraz wstępny . Pozostałe dwie podstawowe właściwości funkcji skrótu mogą nie (i zwykle nie) utrzymywać się w ten sam sposób. Tak więc wnioski zawarte w tym poście mają zastosowanie tylko w przypadku używania funkcji skrótu do przechowywania haseł. Nie mają one ogólnie zastosowania ...

Zacznijmy

Na potrzeby tej dyskusji wymyślmy naszą własną funkcję skrótu:

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

Teraz powinno być całkiem oczywiste, co robi ta funkcja skrótu. Sumuje wartości ASCII każdego znaku wejściowego, a następnie przyjmuje modulo tego wyniku z 256.

Przetestujmy to:

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

Zobaczmy teraz, co się stanie, jeśli uruchomimy go kilka razy wokół funkcji:

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

To daje:

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

Hm, wow. Wygenerowaliśmy kolizje !!! Spróbujmy sprawdzić, dlaczego:

Oto wynik mieszania ciągu każdego możliwego wyniku mieszania:

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

Zwróć uwagę na tendencję do wyższych liczb. To okazuje się naszym martwym punktem. Uruchomienie skrótu 4 razy ($ hash = ourHash ($ hash) `, dla każdego elementu) kończy się dając nam:

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

Mamy zwęziły się w dół do 8 wartości ... To zły ... Nasza pierwotna funkcja odwzorowywane S(∞)na S(256). To jest stworzyliśmy suriekcją funkcji mapowania $inputdo $output.

Ponieważ mamy funkcję Surjective, nie mamy gwarancji, że mapowanie dla dowolnego podzbioru danych wejściowych nie będzie miało kolizji (w rzeczywistości tak się stanie).

Tak się tutaj stało! Nasza funkcja była zła, ale nie dlatego to działało (dlatego działało tak szybko i tak całkowicie).

To samo dzieje się z MD5. Mapuje S(∞)na S(2^128). Ponieważ nie ma gwarancji, że działanie MD5(S(output))będzie Iniektywne , co oznacza, że ​​nie spowoduje kolizji.

Sekcja TL / DR

Dlatego, ponieważ podawanie danych wyjściowych z powrotem md5bezpośrednio może generować kolizje, każda iteracja zwiększy prawdopodobieństwo kolizji. Jest to jednak wzrost liniowy, co oznacza, że ​​chociaż zestaw wyników 2^128jest zmniejszony, nie jest on wystarczająco szybko redukowany, aby być krytyczną wadą.

Więc,

$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

Im więcej razy iterujesz, tym bardziej idzie redukcja.

Poprawka

Na szczęście dla nas istnieje trywialny sposób, aby to naprawić: przekaż coś w dalszych iteracjach:

$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    

Zauważ, że dalsze iteracje nie są 2 ^ 128 dla każdej wartości dla $input. Oznacza to, że możemy być w stanie wygenerować $inputwartości, które nadal zderzają się wzdłuż linii (a tym samym ustabilizują się lub rezonują przy znacznie mniejszych niż 2^128możliwe wyjściach). Ale ogólny przypadek $inputjest nadal tak silny, jak w przypadku pojedynczej rundy.

Czekaj, prawda? Przetestujmy to za pomocą naszej ourHash()funkcji. Przełączam na $hash = ourHash($input . $hash);, dla 100 iteracji:

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

Nadal istnieje tam szorstki wzór, ale zauważ, że nie jest to bardziej wzór niż nasza podstawowa funkcja (która była już dość słaba).

Zauważ jednak, że 0i 3stał kolizji, choć nie były one w jednym przebiegu. Jest to zastosowanie tego, co powiedziałem wcześniej (że odporność na kolizje pozostaje taka sama dla zestawu wszystkich danych wejściowych, ale określone trasy kolizji mogą się otworzyć z powodu wad w podstawowym algorytmie).

Sekcja TL / DR

Dostarczając dane wejściowe do każdej iteracji, skutecznie przełamujemy wszelkie kolizje, które mogły wystąpić w poprzedniej iteracji.

Dlatego md5($input . md5($input));powinien być ( przynajmniej teoretycznie ) tak silny jak md5($input).

Czy to ważne?

Tak. Jest to jeden z powodów, dla których PBKDF2 zastąpił PBKDF1 w RFC 2898 . Rozważ wewnętrzne pętle dwóch:

PBKDF1:

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

Gdzie cjest liczba iteracji, Phasło i Ssól

PBKDF2:

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

Gdzie PRF to tak naprawdę tylko HMAC. Ale dla naszych celów tutaj powiedzmy tylko PRF(P, S) = Hash(P || S)(to znaczy, że PRF 2 danych wejściowych jest taki sam, z grubsza mówiąc, jak skrót z dwoma połączonymi razem). Tak bardzo nie jest , ale tak jest dla naszych celów.

Tak więc PBKDF2 utrzymuje odporność na zderzenia Hashfunkcji bazowej , a PBKDF1 tego nie robi.

Wiązanie wszystkiego razem:

Wiemy o bezpiecznych sposobach iteracji skrótu. W rzeczywistości:

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

Zazwyczaj jest bezpieczny.

Teraz, aby dowiedzieć się, dlaczego chcielibyśmy go haszować, przeanalizujmy ruch entropii.

Hash pobiera zestaw nieskończony: S(∞)i tworzy mniejszy zestaw o stałej wielkości S(n). Następnej iteracji (zakładając, że wejście jest przekazywana z powrotem w) mapy S(∞)na S(n)raz:

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

Zauważ, że końcowy wynik ma dokładnie taką samą ilość entropii jak pierwszy . Iteracja nie „sprawi, że będzie bardziej zaciemniona”. Entropia jest identyczna. Nie ma magicznego źródła nieprzewidywalności (jest to funkcja pseudolosowa, a nie funkcja losowa).

Jest jednak korzyść z iteracji. Powoduje to sztucznie spowolnienie procesu mieszania. I dlatego iteracja może być dobrym pomysłem. W rzeczywistości jest to podstawowa zasada najnowocześniejszych algorytmów haszujących hasła (fakt, że robienie czegoś w kółko spowalnia go).

Powolność jest dobra, ponieważ walczy z podstawowym zagrożeniem bezpieczeństwa: brutalnym wymuszaniem. Im wolniej wprowadzamy algorytm mieszania, tym trudniejsi atakujący muszą pracować, aby atakować skradzione nam hasła. I to dobrze!

ircmaxell
źródło
1
$output = md5($output); // < 2^128 possibilities--- czy to naprawdę surowe <, czy <=?
zerkms
2
@zerkms: To nie jest absolutnie nic. Musimy znać pewne bardzo szczegółowe szczegóły funkcji bazowej ( md5()w tym przypadku), aby naprawdę wiedzieć. Ale ogólnie będzie <i nie <=... Pamiętaj, mówimy o wielkości zestawu $outputdla wszystkich możliwych $inputs. Więc jeśli mamy jeszcze jedną kolizję to będzie <, dlatego <jest lepiej generalizer.
ircmaxell
2
@ TomášFejfar Myślę, że pytanie nie dotyczy ogólnie kolizji, ale kolizji w ścisłym zestawie wyjściowym (2 ^ 128 wyjść, każde dokładnie o szerokości 128 bitów). Które mogłyby być injective, ale o ile wiem, ogólny dowód nie jest możliwe (tylko dowód przez przykład kolizji dla algorytmu określonego). Rozważ funkcję skrótu, która po prostu zwraca wartość wejściową, jeśli jest to 128 bitów (i w przeciwnym razie ma wartość skrótu). Ogólnie rzecz biorąc, byłby on przesadny, ale gdy byłby zasilany, zawsze byłby iniekcyjny ... To jest punkt sporny ...
ircmaxell
3
Daj nam kontynuować tę dyskusję w czacie .
ircmaxell,
6
Dla tych, którzy chcieliby zaoszczędzić czas, nie musząc sprawdzać, jak zakończyła się ta dyskusja między Danem i ircmaxellem, zakończyła się dobrze : Dan zgadza się z ircmaxellem.
jeromej
51

Tak, ponowne mieszanie zmniejsza przestrzeń wyszukiwania, ale nie, to nie ma znaczenia - skuteczne zmniejszenie jest nieznaczne.

Ponowne mieszanie wydłuża czas potrzebny na brutalną siłę, ale robienie tego tylko dwa razy jest również nieoptymalne.

To, czego naprawdę chcesz, to haszować hasło za pomocą PBKDF2 - sprawdzonej metody używania bezpiecznego skrótu z solą i iteracjami. Sprawdź tę odpowiedź SO .

EDYCJA : Prawie zapomniałem - NIE UŻYWAJ MD5 !!!! Użyj nowoczesnego skrótu kryptograficznego, takiego jak rodzina SHA-2 (SHA-256, SHA-384 i SHA-512).

orip
źródło
2
@DFTR - uzgodniony. bcrypt lub scrypt to lepsze opcje.
orip
Nie używaj też tych (rodzina SHA-2), które można teraz łatwo złamać, sprawdź crackstation.net w celu uzyskania dowodu. Jeśli cokolwiek używa scrypt lub PBKDF2, które są kryptograficznymi funkcjami skrótu opartymi na funkcji klucza pochodnego (KDF).
theodore
3
W 2016 r. Argon2 i scrypt są tymi, z których wszyscy powinni korzystać
Silkfire,
10

Tak - zmniejsza liczbę możliwych ciągów pasujących do ciągu.

Jak już wspomniałeś, solone hasze są znacznie lepsze.

Artykuł tutaj: http://websecurity.ro/blog/2007/11/02/md5md5-vs-md5/ , próbuje udowodnić, dlaczego jest równoważny, ale nie jestem pewien co do logiki. Częściowo zakładają, że nie ma dostępnego oprogramowania do analizy md5 (md5 (tekst)), ale oczywiście tworzenie tablic tęczowych jest dość trywialne.

Nadal trzymam się mojej odpowiedzi, że liczba skrótów typu md5 (md5 (tekst)) jest mniejsza niż skrótów md5 (tekst), co zwiększa prawdopodobieństwo kolizji (nawet jeśli jest to mało prawdopodobne) i zmniejsza przestrzeń wyszukiwania.

Rich Bradshaw
źródło
5

Większość odpowiedzi pochodzi od osób bez doświadczenia w dziedzinie kryptografii lub bezpieczeństwa. I są w błędzie. Użyj soli, jeśli to możliwe, unikalnej dla każdego rekordu. MD5 / SHA / etc są zbyt szybkie, wręcz przeciwnie, niż chcesz. PBKDF2 i bcrypt są wolniejsze (co jest dobre), ale można je pokonać za pomocą układów ASIC / FPGA / GPU (obecnie bardzo przystępnych). Potrzebny jest więc algorytm wymagający dużej ilości pamięci: wpisz scrypt .

Oto świeckie wyjaśnienie soli i szybkości (ale nie algorytmów wymagających dużej pamięci).

alecco
źródło
4

Patrzę na to z praktycznego punktu widzenia. Po czym jest haker? Dlaczego kombinacja znaków, która po przejściu przez funkcję skrótu generuje pożądany skrót.

Zapisujesz tylko ostatni hash, dlatego haker musi brutalnie wymusić tylko jeden hash. Zakładając, że masz mniej więcej takie same szanse natknięcia się na pożądany skrót z każdym krokiem brutalnej siły, liczba skrótów jest nieistotna. Możesz wykonać milion iteracji skrótu, a to nie zwiększy ani nie zmniejszy bezpieczeństwa, ponieważ na końcu linii jest jeszcze tylko jeden skrót do złamania, a szanse na jego złamanie są takie same jak dla każdego skrótu.

Być może poprzednie plakaty uważają, że dane wejściowe są istotne; to nie jest. Tak długo, jak cokolwiek wpiszesz w funkcję skrótu, wygeneruje pożądany skrót, przeprowadzi cię przez, prawidłowe lub nieprawidłowe wejście.

Teraz tęczowe stoły to inna historia. Ponieważ tablica tęczy zawiera tylko surowe hasła, dwukrotne mieszanie może być dobrym środkiem bezpieczeństwa, ponieważ tablica tęczy zawierająca każdy skrót każdego skrótu byłaby zbyt duża.

Oczywiście, rozważam tylko przykład podany przez OP, w którym to hasło jest szyfrowane zwykłym tekstem. Jeśli do skrótu podasz nazwę użytkownika lub sól, będzie to inna historia; dwukrotne haszowanie jest całkowicie niepotrzebne, ponieważ tęczowy stół byłby już zbyt duży, aby był praktyczny i zawierał odpowiedni hasz.

W każdym razie nie jestem tutaj ekspertem od bezpieczeństwa, ale tak właśnie wyciągnąłem wnioski z mojego doświadczenia.

Poklepać
źródło
Ta odpowiedź jest błędna pod każdym względem. 1. Znajomość skrótu przedostatniego nie zapewnia żadnej wartości atakującemu, ponieważ dane wejściowe do iterowanego skrótu to hasło , które następnie jest mieszane wiele razy (nie raz). 2. Przestrzeń wejściowa to hasła, przestrzeń wyjściowa to hasła mieszane. Przestrzeń typowych haseł jest znacznie mniejsza niż przestrzeń wyjściowa. 3. Tęczowe tabele dla niesolonych haseł z podwójnym haszowaniem nie są większe niż tabele tęczy dla niesolonych haseł z jednym hashem. 4. Nazwy użytkowników mają niską entropię, dobra sól jest losowa. 5. Solenie nie zastępuje iteracji. Potrzebujesz obu.
Clement Cherlin,
3

Z tego, co przeczytałem, może być zalecane ponowne hashowanie hasła setki lub tysiące razy.

Chodzi o to, że jeśli możesz sprawić, że kodowanie hasła zajmie więcej czasu, atakujący musi pracować nad wieloma domysłami, aby złamać hasło. Wydaje się to być zaletą ponownego mieszania - nie dlatego, że jest bardziej kryptograficznie bezpieczny, ale generowanie ataku słownikowego zajmuje więcej czasu.

Oczywiście komputery stają się coraz szybsze, więc ta przewaga zmniejsza się z czasem (lub wymaga zwiększenia iteracji).

Bill Karwin
źródło
Wspomniałem o tym także w innym komentarzu, ale en.wikipedia.org/wiki/Key_stretching
2

Osobiście nie zawracałbym sobie głowy wieloma hashami, ale upewniłbym się również, że hashame UserName (lub inne pole User ID), a także hasło, aby dwóch użytkowników z tym samym hasłem nie skończyło się tym samym hashem. Prawdopodobnie wrzuciłbym też do łańcucha wejściowego inny stały ciąg znaków, na wszelki wypadek.

$hashed_password = md5( "xxx" + "|" + user_name + "|" + plaintext_password);
CodeAndCats
źródło
13
W rzeczywistości powinien to być ciąg losowo generowany dla każdego użytkownika, a nie stała.
Bill the Lizard
7
Stały sekret działa (i jest łatwiejszy do pracy), jeśli podasz nazwę użytkownika zgodnie z sugestią. To generuje losowy klucz specyficzny dla użytkownika.
SquareCog,
4
Stałą tajemnicą jest bezpieczeństwo poprzez niejasność. Jeśli „sekret” wyjdzie na jaw, że używasz „xxx” + nazwa użytkownika + hasło, wówczas atakujący nie potrzebuje nawet danych z twoich tabel, aby przeprowadzić na niego atak.
Bill the Lizard
8
Nie sądzę, że jest to bezpieczeństwo poprzez zaciemnienie. Powodem używania soli jest to, że nie można obliczyć tabeli tęczy dla wielu skrótów md5 jednocześnie. Zbudowanie jednego dla „xxx” + hasło (ta sama sól) następuje raz. Budowanie tabeli dla „xxx” + nazwa użytkownika + hasło jest gorsze niż brutalne wymuszanie.
FryGuy,
5
@ Bill jaszczurka: „atak sprowadza się do zbudowania jednego słownika, aby zaatakować określoną nazwę użytkownika” to tylko atak siłowy (właściwie nawet gorzej, ponieważ oprócz obliczania wszystkich skrótów musisz je przechowywać), więc sól działa idealnie w tym przypadku.
Kornel
2

Załóżmy, że używasz algorytmu mieszającego: oblicz rot13, weź pierwsze 10 znaków. Jeśli zrobisz to dwa razy (a nawet 2000 razy), możesz stworzyć funkcję, która jest szybsza, ale daje ten sam wynik (mianowicie wystarczy pobrać pierwsze 10 znaków).

Podobnie może być możliwe utworzenie szybszej funkcji, która daje taki sam wynik, jak powtarzająca się funkcja skrótu. Zatem wybór funkcji skrótu jest bardzo ważny: podobnie jak w przykładzie zgnilca 13 nie jest powiedziane, że powtarzanie skrótu poprawi bezpieczeństwo. Jeśli nie ma badań mówiących, że algorytm jest zaprojektowany do użytku rekurencyjnego, bezpieczniej jest założyć, że nie zapewni on dodatkowej ochrony.

To powiedziawszy: dla wszystkich oprócz najprostszych funkcji haszujących najprawdopodobniej eksperci w dziedzinie kryptografii obliczą szybsze funkcje, więc jeśli chronisz się przed atakującymi, którzy nie mają dostępu do ekspertów w dziedzinie kryptografii, prawdopodobnie bezpieczniej jest w praktyce korzystać z powtarzającej się funkcji haszującej .

Ole Tange
źródło
1

Ogólnie rzecz biorąc, nie zapewnia żadnego dodatkowego bezpieczeństwa podwójnemu hashowi lub podwójnemu szyfrowaniu. Jeśli możesz raz złamać skrót, możesz go ponownie złamać. Zazwyczaj jednak nie szkodzi to bezpieczeństwu.

W twoim przykładzie użycia MD5, jak zapewne wiesz, są pewne problemy z kolizją. „Podwójne mieszanie” tak naprawdę nie chroni przed tym, ponieważ te same kolizje nadal będą skutkowały tym samym pierwszym haszem, który możesz następnie MD5 ponownie uzyskać drugi hasz.

Chroni to przed atakami słownikowymi, takimi jak „odwrotne bazy danych MD5”, ale także solenie.

W stycznej Podwójne szyfrowanie czegoś nie zapewnia żadnego dodatkowego bezpieczeństwa, ponieważ wszystko, co robi, powoduje powstanie innego klucza, który jest kombinacją dwóch faktycznie używanych kluczy. Tak więc wysiłek znalezienia „klucza” nie jest podwojony, ponieważ tak naprawdę nie trzeba szukać dwóch kluczy. Nie jest to prawdą w przypadku mieszania, ponieważ wynik skrótu nie jest zwykle tej samej długości, co oryginalne wejście.

SoapBox
źródło
1
Wszystko w porządku, ale chcę tylko zauważyć, że wpływ kompromisu silnej odporności na kolizję na MD5 jest nieco nieproporcjonalny - większość scenariuszy, w których używane są funkcje skrótu kryptograficznego, nie opiera się na silnej odporności na kolizje, tylko na słabej odporności. Luka nie dotyczy ich.
SquareCog,
1

Podwójne haszowanie ma dla mnie sens tylko wtedy, gdy haszuję hasło na kliencie, a następnie zapisuję skrót (z inną solą) tego skrótu na serwerze.

W ten sposób nawet jeśli ktoś włamał się na serwer (ignorując w ten sposób bezpieczeństwo zapewniane przez SSL), nadal nie może uzyskać dostępu do czystych haseł.

Tak, będzie miał dane wymagane do włamania się do systemu, ale nie byłby w stanie wykorzystać tych danych do naruszenia bezpieczeństwa kont zewnętrznych użytkownika. Ludzie znani są z używania tego samego hasła do praktycznie wszystkiego.

Jedynym sposobem, w jaki mógł dostać się do jasnych haseł, jest zainstalowanie keygen na kliencie - i to już nie jest twój problem.

W skrócie:

  1. Pierwsze haszowanie na kliencie chroni użytkowników w scenariuszu „naruszenia bezpieczeństwa serwera”.
  2. Drugi skrót na serwerze służy do ochrony systemu, jeśli ktoś przechwycił kopię zapasową bazy danych, więc nie może użyć tych haseł do łączenia się z twoimi usługami.
Vedran
źródło
1
+1 Czekałem na odpowiedź taką jak ta, ponieważ pomyślałem o tym samym scenariuszu, w którym nie chcesz przechowywać hasła w postaci zwykłego tekstu na kliencie, ale także nie wysyłam ostatecznego zaszyfrowanego hasła za pomocą drutu, aby zrobić proste porównanie z DB.
Mark
1
Nie pomaga aplikacjom internetowym. jeśli twój serwer jest zagrożony, kod, który serwer wysyła do klienta, jest również zagrożony. Atakujący wyłączy Twój skrót po stronie klienta i przechwyci surowe hasła.
Clement Cherlin,
0

Obawa o zmniejszenie przestrzeni wyszukiwania jest matematycznie poprawna, chociaż przestrzeń wyszukiwania pozostaje na tyle duża, że ​​dla wszystkich praktycznych celów (zakładając, że używasz soli), przy 2 ^ 128. Ponieważ jednak mówimy o hasłach, liczba możliwych 16-znakowych ciągów znaków (alfanumeryczne, wielkie litery, kilka symboli wrzuconych) wynosi około 2 ^ 98, zgodnie z moimi obliczeniami z tyłu koperty. Tak postrzegane zmniejszenie przestrzeni wyszukiwania nie jest tak naprawdę istotne.

Poza tym kryptograficznie nie ma żadnej różnicy.

Chociaż istnieje krypto-prymityw zwany „łańcuchem mieszania” - technika, która pozwala na wykonanie kilku fajnych sztuczek, takich jak ujawnienie klucza sygnatury po jego użyciu, bez poświęcania integralności systemu - przy minimalnej synchronizacji czasu, to pozwala na ominięcie problemu początkowej dystrybucji kluczy. Zasadniczo obliczasz duży zestaw skrótów haszowania - h (h (h (h (h .... (h (k)) ...))), użyj n-tej wartości do podpisania, po ustalonym okresie, wyślij wypisz klucz i podpisz go za pomocą klucza (n-1). Odbiorcy mogą teraz sprawdzić, czy wysłałeś wszystkie poprzednie wiadomości, i nikt nie może sfałszować twojego podpisu, ponieważ upłynął okres, przez który jest on ważny.

Ponowne mieszanie setek tysięcy razy, jak sugeruje Bill, to tylko marnowanie procesora. Użyj dłuższego klucza, jeśli obawiasz się, że ludzie złamią 128 bitów.

SquareCog
źródło
1
Ponowne mieszanie polega właśnie na spowolnieniu skrótu. Jest to kluczowa funkcja bezpieczeństwa w kryptografii opartej na hasłach. Zobacz linki do PCKS5 i PBKDF2.
orip
0

Jak sugeruje kilka odpowiedzi w tym artykule, istnieją przypadki, w których może to poprawić bezpieczeństwo, a inne, w których to zdecydowanie boli. Istnieje lepsze rozwiązanie, które zdecydowanie poprawi bezpieczeństwo. Zamiast podwoić liczbę obliczeń wartości skrótu, podwoić rozmiar soli lub podwoić liczbę bitów użytych w wartości skrótu lub wykonać obie te czynności! Zamiast SHA-245, wskocz na SHA-512.

Stefan Rusek
źródło
To nie odpowiada na pytanie.
Bill the Lizard,
1
Podwójne haszowanie nie jest warte wysiłku, ale podwojenie rozmiaru hasza jest. Myślę, że jest to cenniejszy punkt.
Stefan Rusek
-1

Podwójne haszowanie jest brzydkie, ponieważ jest bardziej niż prawdopodobne, że atakujący zbudował stół, aby wymyślić większość skrótów. Lepiej jest solić swoje skróty i mieszać skróty razem. Istnieją również nowe schematy „podpisywania” skrótów (w zasadzie solenia), ale w bardziej bezpieczny sposób.

Sargun Dhillon
źródło
-1

Tak.

Absolutnie nie używaj wielu iteracji konwencjonalnej funkcji skrótu, takiej jak md5(md5(md5(password))). W najlepszym razie marginalnie zwiększysz bezpieczeństwo (taki schemat nie zapewnia prawie żadnej ochrony przed atakiem GPU; po prostu potokuj go.) W najgorszym przypadku zmniejszasz przestrzeń mieszania (a tym samym bezpieczeństwo) z każdą dodaną iteracją . Jeśli chodzi o bezpieczeństwo, mądrze jest zakładać najgorsze.

Czy użyć hasła, które zostało zaprojektowane przez właściwy szyfrant być skutecznym hash hasła i odporny zarówno brute-force oraz ataki czasowo-przestrzennych. Należą do nich bcrypt, scrypt, aw niektórych sytuacjach PBKDF2. Hash oparty na glibc SHA-256 jest również akceptowalny.

Hobbs
źródło
-1

Wyjdę na kończynę i powiem, że w pewnych okolicznościach jest bezpieczniejsza ... nie głosuj jednak jeszcze!

Z matematycznego / kryptograficznego punktu widzenia jest to mniej bezpieczne, z tego powodu jestem pewien, że ktoś inny da ci jaśniejsze wyjaśnienie niż ja.

Istnieją jednak duże bazy danych skrótów MD5, które częściej zawierają tekst „hasła” niż jego MD5. Podwójne haszowanie zmniejsza więc skuteczność tych baz danych.

Oczywiście, jeśli użyjesz soli, wówczas ta zaleta (wada?) Zniknie.

Greg
źródło