Jak haszować długie hasła (> 72 znaki) za pomocą blowfish

91

W zeszłym tygodniu przeczytałem wiele artykułów na temat haszowania haseł, a Blowfish wydaje się być (jednym z) najlepszych obecnie algorytmów haszujących - ale to nie jest temat tego pytania!

Limit 72 znaków

Blowfish bierze pod uwagę tylko pierwsze 72 znaki wpisanego hasła:

<?php
$password = "Wow. This is a super secret and super, super long password. Let's add some special ch4r4ct3rs a#d everything is fine :)";
$hash = password_hash($password, PASSWORD_BCRYPT);
var_dump($password);

$input = substr($password, 0, 72);
var_dump($input);

var_dump(password_verify($input, $hash));
?>

Wynik to:

string(119) "Wow. This is a super secret and super, super long password. Let's add some special ch4r4ct3rs a#d everything is fine :)"
string(72) "Wow. This is a super secret and super, super long password. Let's add so"
bool(true)

Jak widać, liczą się tylko pierwsze 72 znaki. Twitter używa Blowfish czyli bcrypt do przechowywania swoich haseł ( https://shouldichangemypassword.com/twitter-hacked.php ) i zgadnij, co: zmień swoje hasło do twittera na długie hasło z więcej niż 72 znakami i możesz zalogować się na swoje konto przez wpisując tylko pierwsze 72 znaki.

Rozdymka i pieprz

Istnieje wiele różnych opinii na temat „pieprzenia” haseł. Niektórzy twierdzą, że jest to niepotrzebne, ponieważ trzeba założyć, że tajny łańcuch pieprzu jest również znany / opublikowany, więc nie poprawia haszyszu. Mam oddzielny serwer bazy danych, więc jest całkiem możliwe, że wyciekła tylko baza danych, a nie stały pieprz.

W tym przypadku (pieprz nie wyciekł) utrudniasz atak w oparciu o słownik (popraw mnie, jeśli to nie jest w porządku). Jeśli wycieknie również papryka: nie jest tak źle - nadal masz sól i jest tak dobrze chroniona, jak haszysz bez pieprzu.

Więc myślę, że pieprzenie hasła nie jest przynajmniej złym wyborem.

Sugestia

Moja sugestia, aby uzyskać skrót Blowfish dla hasła składającego się z więcej niż 72 znaków (i pieprzu) to:

<?php
$pepper = "foIwUVmkKGrGucNJMOkxkvcQ79iPNzP5OKlbIdGPCMTjJcDYnR";

// Generate Hash
$password = "Wow. This is a super secret and super, super long password. Let's add some special ch4r4ct3rs a#d everything is fine :)";
$password_peppered = hash_hmac('sha256', $password, $pepper);
$hash = password_hash($password_peppered, PASSWORD_BCRYPT);

// Check
$input = substr($password, 0, 72);
$input_peppered = hash_hmac('sha256', $input, $pepper);

var_dump(password_verify($input_peppered, $hash));
?>

Opiera się to na tym pytaniu : password_verifypowrót false.

Pytanie

Jaki jest bezpieczniejszy sposób? Uzyskujesz najpierw skrót SHA-256 (który zwraca 64 znaki), czy rozważasz tylko pierwsze 72 znaki hasła?

Plusy

  • Użytkownik nie może się zalogować, wprowadzając tylko pierwsze 72 znaki
  • Możesz dodać pieprz bez przekraczania limitu znaków
  • Wynik funkcji hash_hmac miałby prawdopodobnie większą entropię niż samo hasło
  • Hasło jest szyfrowane przez dwie różne funkcje

Cons

  • Tylko 64 znaki są używane do zbudowania haszyszu blowfish


Edycja 1: To pytanie dotyczy tylko integracji Blowfish / bcrypt z PHP. Dziękuję za komentarze!

Frederik Kammer
źródło
3
Blowfish nie jest jedynym, który skraca hasło, wprowadzając ludzi w błąd, myśląc, że jest bezpieczniejszy niż w rzeczywistości. Oto interesująca historia limitu 8 znaków.
DOK
2
Czy 72-znakowe obcięcie jest fundamentalne dla algorytmu Blowfish, czy tylko implementacja PHP? IIRC Blowfish jest również używany na (przynajmniej niektórych) nixach do szyfrowania haseł użytkowników.
Douglas B. Staple
3
Problem dotyczy Bcrypt, a nie Blowfish. Mogę odtworzyć ten problem tylko w Pythonie i Bcrypt.
Blender
@Blender: Dziękuję za komentarz i twoją pracę. Nie mogłem znaleźć różnych funkcji w php dla Blowfish i bcrypt i chociaż są one takie same. Ale czy nie ma to dla mnie różnicy w php? Wolałbym użyć standardowej funkcji php.
Frederik Kammer
1
Zobacz także framework do haszowania haseł PHP Openwall (PHPass). Jest przenośny i zabezpieczony przed wieloma typowymi atakami na hasła użytkowników. Facet, który napisał framework (SolarDesigner) to ten sam facet, który napisał John The Ripper i zasiada jako juror w konkursie Password Hashing . Więc wie co nieco o atakach na hasła.
jww

Odpowiedzi:

137

Tutaj problem jest zasadniczo problemem entropii. Zacznijmy więc tam szukać:

Entropia na znak

Liczba bitów entropii na bajt to:

  • Znaki szesnastkowe
    • Bity: 4
    • Wartości: 16
    • Entropia w 72 znakach: 288 bitów
  • Alfanumeryczne
    • Bity: 6
    • Wartości: 62
    • Entropia w 72 znakach: 432 bity
  • „Wspólne” symbole
    • Bity: 6,5
    • Wartości: 94
    • Entropia w 72 znakach: 468 bitów
  • Pełne bajty
    • Bity: 8
    • Wartości: 255
    • Entropia w 72 znakach: 576 bitów

Zatem to, jak działamy, zależy od tego, jakich postaci oczekujemy.

Pierwszy problem

Pierwszym problemem z twoim kodem jest to, że twój krok mieszający "pepper" generuje znaki szesnastkowe (ponieważ czwarty parametr hash_hmac()to nie jest ustawiony).

Dlatego haszując pieprz, skutecznie zmniejszasz maksymalną entropię dostępną dla hasła o współczynnik 2 (od 576 do 288 możliwych bitów).

Drugi problem

Jednak w pierwszej kolejności sha256dostarcza tylko 256bitów entropii. Więc skutecznie zmniejszasz możliwe 576 bitów do 256 bitów. Twój haszujący krok * natychmiast *, z samej definicji traci co najmniej 50% możliwej entropii hasła.

Możesz częściowo rozwiązać ten problem, przełączając się na SHA512, gdzie zmniejszyłbyś dostępną entropię tylko o około 12%. Ale to wciąż niemała różnica. Te 12% zmniejsza liczbę permutacji o współczynnik 1.8e19. To duża liczba ... I to jest czynnik, który ją zmniejsza o ...

Problem podstawowy

Podstawową kwestią jest to, że istnieją trzy rodzaje haseł przekraczających 72 znaki. Wpływ, jaki ten system stylu będzie miał na nich, będzie bardzo różny:

Uwaga: od tego momentu zakładam, że porównujemy do systemu pieprzowego, który używa SHA512surowego wyjścia (nie szesnastkowego).

  • Losowe hasła o wysokiej entropii

    To są twoi użytkownicy używający generatorów haseł, które generują duże klucze do haseł. Są losowe (generowane, a nie wybrane przez człowieka) i mają wysoką entropię na znak. Te typy używają dużych bajtów (znaki> 127) i niektórych znaków sterujących.

    W przypadku tej grupy funkcja haszująca znacznie zmniejszy ich dostępną entropię do bcrypt.

    Powtórzę to. W przypadku użytkowników, którzy używają długich haseł o dużej entropii, Twoje rozwiązanie znacznie zmniejsza siłę hasła o wymierną wartość. (62 bity entropii utracone dla 72-znakowego hasła i więcej dla dłuższych haseł)

  • Losowe hasła o średniej entropii

    Ta grupa używa haseł zawierających popularne symbole, ale bez dużych bajtów ani znaków sterujących. To są Twoje hasła, które możesz wpisać.

    Dla tej grupy zamierzasz nieco odblokować większą entropię (nie twórz jej, ale pozwól, aby większa entropia pasowała do hasła bcrypt). Kiedy mówię trochę, mam na myśli trochę. Próg rentowności występuje, gdy maksymalnie przekroczysz 512 bitów, które ma SHA512. Dlatego szczyt wynosi 78 znaków.

    Powtórzę to jeszcze raz. W przypadku tej klasy haseł można przechowywać tylko 6 dodatkowych znaków, zanim zabraknie entropii.

  • Nielosowe hasła o niskiej entropii

    To jest grupa, która używa znaków alfanumerycznych, które prawdopodobnie nie są generowane losowo. Coś w rodzaju cytatu z Biblii lub czegoś takiego. Te frazy mają około 2,3 bitów entropii na znak.

    W przypadku tej grupy możesz znacznie odblokować większą entropię (nie tworzyć jej, ale pozwolić, aby więcej pasowało do wpisanego hasła bcrypt) przez haszowanie. Próg rentowności wynosi około 223 znaków, zanim zabraknie entropii.

    Powtórzmy to jeszcze raz. W przypadku tej klasy haseł wstępne haszowanie zdecydowanie zwiększa bezpieczeństwo.

Powrót do prawdziwego świata

Tego rodzaju obliczenia entropii nie mają tak naprawdę większego znaczenia w prawdziwym świecie. Liczy się zgadywanie entropii. To bezpośrednio wpływa na to, co mogą zrobić atakujący. To właśnie chcesz zmaksymalizować.

Chociaż niewiele jest badań poświęconych odgadywaniu entropii, jest kilka punktów, na które chciałbym zwrócić uwagę.

Szanse na przypadkowe odgadnięcie 72 poprawnych znaków z rzędu są niezwykle niskie. Bardziej prawdopodobne jest, że wygrasz w loterii Powerball 21 razy, niż zderzenie ... Tak duża liczba, o której mówimy.

Ale statystycznie nie możemy się na to natknąć. W przypadku fraz szansa na to, że pierwsze 72 znaki będą takie same, jest dużo większa niż w przypadku hasła losowego. Ale wciąż jest trywialnie niski (bardziej prawdopodobne jest, że wygrasz w loterii Powerball 5 razy, w oparciu o 2,3 bita na postać).

Praktycznie

Praktycznie to nie ma znaczenia. Szanse na prawidłowe odgadnięcie pierwszych 72 znaków, w przypadku których te ostatnie robią znaczącą różnicę, są tak niskie, że nie warto się tym martwić. Czemu?

Cóż, powiedzmy, że bierzesz zdanie. Jeśli dana osoba może poprawnie uzyskać pierwsze 72 znaki, to albo naprawdę ma szczęście (mało prawdopodobne), albo jest to powszechne zdanie. Jeśli jest to powszechna fraza, jedyną zmienną jest czas jej wykonania.

Weźmy przykład. Weźmy cytat z Biblii (tylko dlatego, że jest to częste źródło długiego tekstu, a nie z żadnego innego powodu):

Nie będziesz pożądał domu bliźniego. Nie będziesz pożądał żony bliźniego, jego niewolnicy lub niewolnicy, jego wołu lub osła, ani niczego, co należy do twojego bliźniego.

To 180 znaków. 73. znak jest gw drugim neighbor's. Jeśli tak dużo zgadłeś, prawdopodobnie nie zatrzymasz się na tym nei, ale przejdziesz do reszty wersetu (ponieważ w ten sposób prawdopodobnie zostanie użyte hasło). Dlatego twój „hash” niewiele dodał.

BTW: ABSOLUTNIE NIE jestem zwolennikiem używania cytatu z Biblii. W rzeczywistości jest dokładnie odwrotnie.

Wniosek

Tak naprawdę nie pomożesz ludziom, którzy używają długich haseł, najpierw je zhaszuj. Niektórym grupom zdecydowanie możesz pomóc. Niektórych zdecydowanie możesz zranić

Ale ostatecznie nic z tego nie jest zbyt istotne. Liczby, z którymi mamy do czynienia, są po prostu DUŻO za wysokie. Różnica w entropii nie będzie duża.

Lepiej zostaw bcrypt takim, jakim jest. Bardziej prawdopodobne jest, że schrzanisz hasz (dosłownie, już to zrobiłeś i nie jesteś pierwszy ani ostatni, który popełnia ten błąd), niż atak, któremu próbujesz zapobiec.

Skoncentruj się na zabezpieczeniu pozostałej części witryny. I dodaj miernik entropii hasła do pola hasła podczas rejestracji, aby wskazać siłę hasła (i wskazać, czy hasło jest zbyt długie, aby użytkownik mógł chcieć je zmienić) ...

To co najmniej moje 0,02 dolara (lub prawdopodobnie znacznie więcej niż 0,02 dolara) ...

O ile używa się „sekretnej” papryki:

Nie ma dosłownie żadnych badań nad wprowadzeniem jednej funkcji skrótu do bcrypt. Dlatego w najlepszym przypadku nie jest jasne, czy wprowadzenie „zasypanego” hasha do bcrypt kiedykolwiek spowoduje nieznane luki w zabezpieczeniach (wiemy, że hash1(hash2($value))może to ujawnić znaczące luki w zakresie odporności na kolizje i ataków typu preimage).

Biorąc pod uwagę, że już rozważasz przechowywanie tajnego klucza („pieprzu”), dlaczego nie użyć go w dobrze przestudiowany i zrozumiały sposób? Dlaczego nie zaszyfrować hasha przed jego przechowywaniem?

Zasadniczo, po zaszyfrowaniu hasła, wprowadź całe dane wyjściowe skrótu do silnego algorytmu szyfrowania. Następnie zapisz zaszyfrowany wynik.

Teraz atak SQL-Injection nie spowoduje wycieku niczego użytecznego, ponieważ nie mają klucza szyfrującego. A jeśli klucz wycieknie, atakującym nie będzie lepiej, niż gdybyś użył zwykłego skrótu (co można udowodnić, coś z pieprzowym „pre-hashem” nie zapewnia).

Uwaga: jeśli zdecydujesz się to zrobić, skorzystaj z biblioteki. W przypadku PHP zdecydowanie polecam Zend Framework 2Zend\Crypt pakiet . Właściwie to jedyny, który polecam w obecnym momencie. Został mocno sprawdzony i podejmuje wszystkie decyzje za Ciebie (co jest bardzo dobrą rzeczą) ...

Coś jak:

use Zend\Crypt\BlockCipher;

public function createHash($password) {
    $hash = password_hash($password, PASSWORD_BCRYPT, ["cost"=>$this->cost]);

    $blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
    $blockCipher->setKey($this->key);
    return $blockCipher->encrypt($hash);
}

public function verifyHash($password, $hash) {
    $blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
    $blockCipher->setKey($this->key);
    $hash = $blockCipher->decrypt($hash);

    return password_verify($password, $hash);
}

Jest to korzystne, ponieważ używasz wszystkich algorytmów w sposób, który jest dobrze zrozumiany i dobrze zbadany (przynajmniej względnie). Zapamiętaj:

Każdy, od najbardziej bezmyślnego amatora po najlepszego kryptografa, może stworzyć algorytm, którego sam nie może złamać.

ircmaxell
źródło
6
Bardzo, bardzo dziękuję za tę szczegółową odpowiedź. To naprawdę mi pomaga!
Frederik Kammer
1
Mój komplement za tę odpowiedź. Jeden mały wybór, to ogromna większość użytkowników, która używa bardzo słabych haseł, słów i pochodnych zawartych w słowniku do łamania haseł, pieprz chroniłby ich niezależnie od pytań o entrofię. Aby uniknąć utraty entrofii, możesz po prostu połączyć hasło i pieprz. Jednak Twoja sugestia dotycząca szyfrowania wartości skrótu jest prawdopodobnie najlepszym rozwiązaniem do dodania klucza tajnego po stronie serwera.
martinstoeckli
2
@martinstoeckli: Mój problem z pojęciem pieprzu nie polega na jego wartości. Chodzi o to, że zastosowanie „pieprzu” wkracza na niezbadane terytorium w zakresie algorytmów kryptograficznych. To nie jest dobra rzecz. Zamiast tego uważam, że prymitywy kryptograficzne należy łączyć w taki sposób, aby były ze sobą zaprojektowane. Zasadniczo, rdzenna koncepcja pieprzu brzmi dla mnie w uszach, jak niektórzy ludzie, którzy nie wiedzieli nic o kryptografii, powiedzieli: „Więcej haszy jest lepsze! Mamy sól, pieprz też jest dobry!” . Wolałbym mieć prostszy, bardziej przetestowany i bardziej bezpośredni impl
ircmaxell
@ircmaxell - Tak, znam Twój punkt widzenia i zgadzam się, o ile wartości skrótu zostaną później zaszyfrowane. Jeśli nie wykonasz tego dodatkowego kroku, atak słownikowy ujawni po prostu zbyt wiele słabych haseł, nawet z dobrym algorytmem mieszania.
martinstoeckli
1
@martinstoeckli: Nie zgadzam się z tym. Przechowywanie tajemnic nie jest rzeczą trywialną. Zamiast tego, jeśli używasz bcrypt z dobrym kosztem (dzisiaj 12), wszystkie hasła oprócz najsłabszej są bezpieczne (słownik, a hasła trywialne są słabe). Dlatego wolałbym raczej polecić ludziom skupienie się na edukacji użytkownika za pomocą mierników siły i
skłonieniu
5

Podawanie haseł jest z pewnością dobrą rzeczą, ale zobaczmy, dlaczego.

Najpierw powinniśmy odpowiedzieć na pytanie, kiedy dokładnie pieprz pomaga. Pieprz chroni tylko hasła, o ile pozostaje w tajemnicy, więc jeśli atakujący ma dostęp do samego serwera, nie jest to przydatne. Dużo łatwiejszym atakiem jest jednak SQL-injection, który umożliwia odczyt bazy danych (do naszych wartości hash), przygotowałem demo SQL-injection, aby pokazać, jakie to proste (kliknij w następną strzałkę, aby przygotować Wejście).

W takim razie w czym tak naprawdę pomaga pieprz? Dopóki pieprz pozostaje tajemnicą, chroni słabe hasła przed atakiem słownikowym. Hasło 1234stałoby się wtedy czymś w rodzaju 1234-p*deDIUZeRweretWy+.O. To hasło jest nie tylko znacznie dłuższe, ale zawiera również znaki specjalne i nigdy nie będzie częścią żadnego słownika.

Teraz możemy oszacować, jakich haseł będą używać nasi użytkownicy, prawdopodobnie więcej użytkowników będzie wprowadzać słabe hasła, ponieważ są użytkownicy z hasłami o długości od 64 do 72 znaków (w rzeczywistości będzie to bardzo rzadkie).

Kolejną kwestią jest zakres brutalnej siły. Funkcja skrótu sha256 zwróci wyjście 256 bitów lub kombinację 1.2E77, to o wiele za dużo dla brutalnego wymuszania, nawet dla GPU (jeśli obliczyłem poprawnie, wymagałoby to około 2E61 lat na GPU w 2013). Nie mamy więc prawdziwej wady stosując pieprz. Ponieważ wartości skrótu nie są systematyczne, nie można przyspieszyć brutalnego wymuszania za pomocą typowych wzorców.

PS O ile wiem, limit 72 znaków jest specyficzny dla samego algorytmu BCrypt. Najlepsza odpowiedź, jaką znalazłem, jest taka .

PPS Myślę, że twój przykład jest wadliwy, nie możesz wygenerować skrótu z pełną długością hasła i zweryfikować go skróconym. Prawdopodobnie zamierzałeś zastosować pieprz w ten sam sposób do generowania skrótu i ​​do weryfikacji skrótu.

martinstoeckli
źródło
Jeśli chodzi o twój PPS, mogę po prostu powiedzieć: Tak, może zweryfikować skrócone hasło za pomocą skrótu nie obciętego i nadal uzyskać true. O to właśnie chodzi w tym pytaniu. Zobaczcie
Sliq
@Panique - Problem nie polega na obliczeniu skrótu BCrypt, to wcześniejszy HMAC. Do generowania skrótu SHA OP używa hasła o pełnej długości i używa wyniku jako danych wejściowych dla BCrypt. W celu weryfikacji obcina hasło przed obliczeniem skrótu SHA, a następnie używa tego zupełnie innego wyniku jako danych wejściowych dla BCrypt. HMAC akceptuje dane wejściowe o dowolnej długości.
martinstoeckli
2

Bcrypt wykorzystuje algorytm oparty na drogim algorytmie konfiguracji klucza Blowfish.

Zalecany 56-bajtowy limit hasła (w tym zerowy bajt zakończenia) dla bcrypt odnosi się do 448-bitowego limitu klucza Blowfish. Żadne bajty wykraczające poza ten limit nie są w pełni mieszane z wynikowym hashem. Bezwzględny limit 72 bajtów haseł bcrypt jest zatem mniej istotny, jeśli weźmie się pod uwagę rzeczywisty wpływ tych bajtów na wynikowy hash.

Jeśli myślisz, że Twoi użytkownicy zwykle wybierają hasła o długości ponad 55 bajtów, pamiętaj, że zawsze możesz zamiast tego zwiększyć liczbę rund rozciągania haseł, aby zwiększyć bezpieczeństwo w przypadku naruszenia tabeli haseł (chociaż musi to być dużo w porównaniu z dodaniem dodatkowych postacie). Jeśli prawa dostępu użytkowników są tak krytyczne, że użytkownicy normalnie wymagaliby bardzo długiego hasła, wówczas wygaśnięcie hasła również powinno być krótkie, np. 2 tygodnie. Oznacza to, że prawdopodobieństwo, że hasło pozostanie ważne, jest znacznie mniejsze, podczas gdy haker inwestuje swoje zasoby w pokonanie czynnika pracy związanego z testowaniem każdego hasła próbnego, aby sprawdzić, czy wygeneruje pasujący skrót.

Oczywiście w przypadku braku naruszenia tablicy haseł powinniśmy pozwolić hakerom na co najwyżej dziesięć prób odgadnięcia 55-bajtowego hasła użytkownika, przed zablokowaniem konta użytkownika;)

Jeśli zdecydujesz się wstępnie zaszyfrować hasło, które jest dłuższe niż 55 bajtów, powinieneś użyć SHA-384, ponieważ ma największy wynik bez przekraczania limitu.

Phil
źródło
1
„wygaśnięcie hasła również powinno być krótkie, na przykład 2 tygodnie” „masywnie długich haseł”, naprawdę, po co w takim razie zawracać sobie głowę zapisywaniem hasła, po prostu używaj resetowania hasła za każdym razem. Poważnie, to złe rozwiązanie, przejdź do uwierzytelniania dwuskładnikowego za pomocą tokena.
zaph
Dzięki @zaph. Czy możesz wskazać mi na to przykład? To brzmi interesująco.
Phil
[DRAFT NIST Special Publication 800-63B Digital Authentication Guideline] ( pages.nist.gov/800-63-3/sp800-63b.html ), 5.1.1.2. Zapamiętane tajne weryfikatory: Weryfikatorzy NIE POWINNI wymagać, aby zapamiętane sekrety były dowolnie zmieniane (np. Okresowo) . Zobacz także „ Toward Better Password Requirements” autorstwa Jima Fentona.
zaph
1
Chodzi o to, że im częściej użytkownik jest zobowiązany do zmiany hasła, tym gorsze stają się wybory hasła, co zmniejsza bezpieczeństwo. Użytkownicy mają ograniczoną liczbę dobrych haseł, które można zapamiętać, i kończą się, albo wybierają naprawdę złe hasła, albo zapisują je na
karteczkach samoprzylepnych