Jednym ze sposobów na przechowywanie podanych przez użytkowników haseł jest wykorzystanie funkcji skrótu. W tym przypadku, wejście podane np. podczas rejestracji użytkownika jest przetwarzane przez wybraną funkcję skrótu, a jej wynik zapisywany jest w bazie danych. Funkcje skrótu, dzięki temu, że są jednokierunkowe, zapewniają brak możliwości łatwego odzyskania danych. Można spróbować je łamać, ale nie jest to operacja równie łatwa co np. odszyfrowywanie, natomiast jak najbardziej możliwa. Zapewnia to pewien poziom bezpieczeństwa względem programistów i administratorów, oraz nie powoduje bezpośredniego wycieku haseł przy kradzieży całej bazy. Hasła przechowywanego w ten sposób nie można przypomnieć, jednakże problemy powinna rozwiązać funkcjonalność ponownego jego ustalenia. Przechowywanie haseł w formie skrótu jest jedną z bezpieczniejszych form tego działania. Dodatkowo, w celu podniesienia bezpieczeństwa i kosztu ataku na nie, stosuje się dodatkowe zabiegi – sól, pieprz i rozszerzanie klucza. Po takich operacjach, następuje de facto zamiana skrótu na klucz funkcji PBKDF, który jest aktualnie najbezpieczniejszym sposobem na przechowywanie informacji niezbędnych do uwierzytelnienia użytkownika.
Pierwszą z metod zwiększenia trudności ataku na system przechowujący hasze jest dodawanie tzw. soli. Jest to pewien losowo wygenerowany ciąg znaków, który dodawany jest do hasła użytkownika przed zastosowaniem na nim funkcji skrótu. Jest on przechowywany w bazie równolegle z samym hasłem. Jeżeli atakujący zdobędzie same skróty, zdecydowanie trudniej będzie mu odgadnąć kryjące się za nimi hasła wraz z dołączonym losowym, często skomplikowanym, ciągiem znaków. Należy jednak założyć, że skoro uzyskano dostęp do skrótów, pobrać można również sole. W takim przypadku może je dołączać do każdej próbowanej kombinacji, co zwiększa w pewnym stopniu trudność ataku. Jednakże, nawet znana sól, chroni dobrze przed sytuacją, gdzie atakujący ma już wygenerowane wszystkie skróty z danego zakresu słów (np. tęczowe tablice), i porównuje z tymi z danego wycieku.
Inną odmianą dodawanego do hasła ciągu tzw. pieprz. Jest to pewna stała (powinna być także skomplikowana) wartość, którą dodaje się do wszystkich haseł przewidzianych do przechowania w bazie. Jego cechą jest to, że sam znajduje się poza nią. Dzięki temu, znacząco zwiększa się koszt ataku, zarówno metodami słownikowymi, jak i tęczowymi tablicami. Możliwa jest jego „neutralizacja” (dodawanie do każdego testowanego ciągu), ale wymaga to dodatkowych nakładów pracy i zwiększa ogólną trudność. Trzeba wykraść go z innej, konfiguracyjnej części serwera. Tak samo jak w przypadku soli, nawet znany, zwiększa koszt niektórych metod łamania skrótów.
Trzecią z operacji służącą zwiększeniu trudności ataku na hasła użytkowników jest rozszerzanie klucza (ang. key streching). Technika ta służy wydłużaniu obliczeń, np. poprzez wymuszenie działania funkcji skrótu w pętli określoną ilość razy. Wyjście z danego przebiegu podawane jest wtedy jako wejście następnego.
W takim przypadku, np. podczas rejestracji i logowania należy co prawda wykonać pętlę wiele razy, jednakże użytkownik nie zauważy tak nieznacznego wydłużenia (np. o kilkadziesiąt milisekund). Czas łamania zwiększy się natomiast znacznie (np. z godzin do miesięcy). Jest to bezpieczne, ponieważ udowodniono, że wielokrotne haszowanie za pomocą kryptograficznych funkcji skrótu nie zwiększa szansy na kolizję.
Z połączenia metody przechowywania danych opartej o skrót oraz jej rozszerzeń powstały funkcje dedykowane przechowywaniu haseł. Są to tak zwane funkcje z rodziny PBKDF (ang. Password Based Key Derivation Function). Ich ogólny schemat działania został przedstawiony na rysunku poniżej.
Na wejściu funkcja przyjmuje 2 lub 3 parametry. Obowiązkowymi są hasło i koszt obliczeń (ang. work factor). Ten drugi odpowiada za stopień skomplikowania obliczeń, zwiększając czas konieczny do wygenerowania wartości. Utrudnia to przeprowadzenie ataku, ale spowalnia proces generowania i weryfikacji. Przykładowo w funkcji bcrypt, przyjmując koszt jako X, w praktyce oznacza to, że wykona ona 2^X iteracji algorytmu, np. X = 10 oznacza 1024 powtórzenia.
Opcjonalnym parametrem jest zaś sól (ang. salt). W przypadku braku jej podania, zostanie ona wygenerowana automatycznie w wewnętrznym generatorze liczb pseudolosowych. Parametr ten jest niezbędny podczas weryfikacji hasła.
Na wyjściu funkcji znajduje się natomiast klucz (ang. master key), który należy przechować w bazie danych. Klucz ten zawiera w sobie informację o zastosowanym algorytmie, soli i stopniu skomplikowania obliczeń. Przykładowy klucz wraz ze strukturą na przykładzie wyniku funkcji bcrypt został pokazany na rysunku poniżej.
Pierwsze cztery znaki oznaczają wersję i typ algorytmu. Następne 3 to koszt obliczeń, a kolejne 22 odpowiadają użytej soli. Reszta wygenerowanego klucza to hasz podanego przez użytkownika hasła. W przypadku bcrypta zaleca się koszt na poziomie 12-15.
Optymalny koszt można wyznaczyć samemu - należy założyć określony czas działania funkcji (np. 100 milisekund) i dobrać koszt tak, żeby na dostępnym sprzęcie nie przekroczyć wyznaczonego czasu. Ustawiając zbyt wysoki koszt, możemy doprowadzić do sytuacji, w której logujący się użytkownicy nie zostaną obsłużeni z powodu wyczerpania zasobów serwera.
Chcąc zweryfikować, czy podany sekret jest prawidłowy, dokonuje się obliczeń korzystając z klucza z bazy. Wyodrębniony z niego zostaje typ algorytmu, parametr kosztu oraz sól. Następnie wygenerowany jest nowy klucz z użyciem hasła podanego przez użytkownika i soli pobranej z bazy. Jeżeli wyniki pokrywają się, użytkownik zostaje uwierzytelniony. Należy zauważyć, że bcrypt posiada limit długości podawanego hasła - w zależności od implementacji do 72 bajtów. Może zdarzyć się sytuacja, w której przy podaniu dłuższego hasła zostanie ono po prostu skrócone do maksymalnej dozwolonej długości.
Istnieje wiele algorytmów opartych o podobną zasadę. Popularne z nich są:
bcrypt
PBKDF2
phpass
argon2
scrypt
Posiadają one różną charakterystykę (np. możliwość parametryzacji wymagań pamięciowych w scrypt i argon2) i wewnętrzny sposób działania. Niezależnie od wybranego schematu, są one najbezpieczniejszą formą przechowywania informacji o sekrecie po stronie serwera, a użycie i dostrojenie konkretnej z nich zależy od specyfiki konkretnego zastosowania. Ważnym jest, aby dobrze dopasować wybrane rozwiązanie i spowolnić potencjalnego atakującego tak bardzo, jak tylko się da.