Rzecz o przechowywaniu haseł

Rzecz o przechowywaniu haseł

data: 16 lipca, 2012
czas czytania: 6 min
autor: Michał Bentkowski

W ostatnim czasie w internecie głośno jest o różnych przypadkach wycieków haseł. Pisał już o tym jakiś czas temu nasz pracownik Jacek Sowiński, który wspominał o tym, że my jako użytkownicy powinniśmy dbać o swoje hasła – mieć różne hasła do różnych serwisów, a najlepiej zainstalować do tego program.
Tutaj skupię się na innym aspekcie bezpieczeństwa haseł. Mianowicie: jak zabezpieczać hasła naszych użytkowników po stronie serwera, żeby w razie wycieku danych znacząco utrudnić włamywaczom złamanie hasła.

Plaintext?

Pierwsza rzecz, której absolutnie nie należy robić − to przechowywać hasła tekstem jawnym. Tak jak to robi np. Billabong.
Nie trzeba chyba dodatkowo wyjaśniać, że w razie udanego włamania atakujący mają wszystkie hasła podane na tacy i nie muszą nic więcej robić. Pozostaje im tylko te hasła sprzedać, udostępnić publicznie albo próbować się takimi samymi hasłami logować
na inne strony.

Haszowanie?

Skoro tekst jawny jest zły, naturalną koleją rzeczy jest teraz próba „zaciemnienia” hasła − niepodania go bezpośrednio.
Z pomocą przychodzą tutaj funkcje haszujące. Jak podaje Wikipedia, funkcja haszująca to funkcja, która przyporządkowuje dowolnie dużej liczbie krótką, zwykle posiadającą stały rozmiar, niespecyficzną, quasi-losową wartość, tzw. skrót nieodwracalny.
Wynika z tego, że nie istnieje funkcja, która pozwala na odwrócenie procesu, tj. otrzymania pierwotnej wiadomości z samego haszu. Przykładowo, zahaszujmy słowo „futureprocessing”:

md5("futureprocessing")
"5dc3864bd44811fa2085e6c4bdad2536"

Jeśli w serwisie internetowym jest używany md5 do haszowania haseł, to gdy ktoś ustawi sobie hasło „futureprocessing” – zamiast niego do bazy trafia „5dc…”. Rzeczywiście, nie da się algorytmicznie uzyskać wiadomości z hasza, ale w internecie znajdują się bazy danych, które zawierają gigantyczne ilości przeliczonych haszy (tzw. Tablice tęczowe).
Przykładowo, hasz z „futureprocessing” można (z powodzeniem) spróbować rozkodować na stronie xdecrypt.com.

​Haszowanie z solą?

Musimy zatem spróbować obronić się przed tęczowymi tablicami. Możemy to zrobić poprzez dodanie soli do hasła.
Sól − jest to losowa wartość doklejana do każdego hasła przed wykonaniem samego haszowania; może być statyczna (taka sama dla wszystkich) lub dynamiczna (każde hasło ma inną sól).
Sól powinna być wartością odpowiednio długą i nieprzewidywalną. Przykład zastosowania soli:

md5('futureprocessing'+'BaAaaaArDzO Dluga So00ol!@')
'f2e96f88f5629e52b1329385accf2fea'

Takiego hasza już raczej nie znajdziemy w tablicach tęczowych. Pisałem o soli statycznej i dynamicznej. Którą z nich lepiej stosować? Najlepiej obie – tj. mieć jedną sól, która będzie doklejana do każdego hasła, a oprócz tego drugą sól, którą będziemy przechowywać w bazie w innej kolumnie obok hasła. Dzięki takiemu rozwiązaniu, nawet jeśli włamywacz wykradnie nasze bazy danychi będzie znał sole dynamiczne, wciąż będzie miał przeszkodę do pokonania: poznanie soli statycznej.

Szybkość?

OK, czyli zwiększamy jakość hasza poprzez dodanie soli. Takie hasze nie powinny już być do znalezienia w tablicach tęczowych. Musimy jednak pamiętać, że wciąż istnieją najprostsze metody łamania haseł: brute-force i metoda słownikowa.
Przed tymi atakami wciąż w żaden sposób się nie chronimy, co więcej − na haszu md5 brute-force można wykonywać bardzo, bardzo szybko. A w całej dzisiejszej gonitwie za megahercami i za coraz większą szybkością jest jedna rzecz, która powinna pozostać wolna: właśnie haszowanie haseł. Zrobiłem w Pythonie krótki test, sprawdzający jak szybki jest algorytm md5:

In [9]: %timeit md5('futureprocessing')
100000 loops, best of 3: 2.34 us per loop

Wygenerowanie hasza zajęło zaledwie 2,34 mikrosekundy. Ten czas jeszcze można spokojnie skrócić, mając dostęp do CUDA
czy po prostu do komputerów o większej mocy obliczeniowej.

Bcrypt!

Najwyższa pora przejść do kolejnego algorytmu – Bcrypt. Bcrypt jest algorytmem, który jest przystosowany do haszowania haseł
(w przeciwieństwie do md5, sha1 czy sha512, które są haszami ogólnego przeznaczenia). Jego charakterystyczną cechą jest to,
że ma bardzo rozbudowaną fazę inicjującą hasz (po szczegóły odsyłam do wiki), której złożoność można samemu kontrolować. Dodatkowo, bcrypt chroni też przed atakami z tęczowych tablic, ponieważ sól jest wymaganym parametrem do działania algorytmu.

Pierwszym, co należy zrobić, by zahaszować coś bcryptem, jest wygenerowanie soli.

bcrypt.gensalt()
'$2a$12$5Ht.6aoLKCMxyQvVhIbkAu'

Zwróćmy uwagę, że sól zaczyna się od $2a$12$. Ta „dwunastka” jest złożonością obliczeniową, z jaką będzie później generowany hasz. „$2a$” na początku jest wymagane, zaś wszystko to, co znajduje się po trzecim znaku dolara, jest właściwą solą.
Funkcja gensalt przyjmuje jeden parametr i w nim właśnie można zdefiniować złożoność algorytmu. Gdy parametr nie jest podany,
to przyjmuje, jak widać wyżej, domyślną wartość: 12.

Funkcja generująca hasz przyjmuje dwa parametry: tekst do zaszyfrowania i sól. Zobaczmy na przykładzie, jak wygląda wynik działania tej funkcji:

bcrypt.hashpw("futureprocessing", '$2a$12$5Ht.6aoLKCMxyQvVhIbkAu')
+'$2a$12$5Ht.6aoLKCMxyQvVhIbkAuf0v95FcFclVPk4qeW0EFy9DuKY4qeaO'

Na początku wynikowego hasza znajduje się sól (tak jak było mówione przy solach dynamicznych − one są jawne), a potem zaszyfrowane hasło. To teraz sprawdźmy zależność między argumentem podanym do generacji soli, a czasem generowania się hasza:

In [23]: %timeit bcrypt.hashpw("futureprocessing",
bcrypt.gensalt(12))
1 loops, best of 3: 331 ms per loop
In [24]: %timeit bcrypt.hashpw("futureprocessing",
bcrypt.gensalt(13))
1 loops, best of 3: 663 ms per loop
In [25]: %timeit bcrypt.hashpw("futureprocessing",
bcrypt.gensalt(14))
1 loops, best of 3: 1.32 s per loop
In [26]: %timeit bcrypt.hashpw("futureprocessing",
bcrypt.gensalt(15))
1 loops, best of 3: 2.7 s per loop

Każde zwiększenie wartości argumentu dla gensalt o 1 zwiększa dwukrotnie czas obliczenia hasza. Zauważmy jednak, że już dla standardowej złożoności obliczeniowej obliczenie hasza zajmuje 331 ms, czyli prawie 150 000 razy dłużej niż dla md5.
Wniosek jest prosty: dzięki temu ataki brute-force wydłużą się 150000-krotnie. Atakujący będą potrzebowali znacznie większej mocy obliczeniowej i znacznie więcej czasu, by z powodzeniem złamać hasła. A jeśli uznamy, że komputery na tyle przyspieszyły,
że złożoność „12” jest zbyt niska, po prostu zwiększymy ją do „13” i w ten prosty sposób utrudniamy życie włamywaczom.

Podsumowanie

Reasumując, w razie wycieku haseł po stronie serwera musimy się liczyć z tym, że włamywacze spróbują dwóch głównych metod ich złamania. Po pierwsze: spróbują tablic tęczowych. Przed tym bronimy się, używając soli. Po drugie: spróbują brute-force.
W tym przypadku metodą ochrony jest zastosowanie powolnych algorytmów w celu jak największego wydłużenia czasu łamania haseł. Możemy do tego użyć bcrypt albo innych, podobnie działających algorytmów, np. scrypt.

Newsletter IT leaks

Dzielimy się inspiracjami i nowinkami z branży IT. Szanujemy Twój czas - obiecujemy nie spamować i wysyłać wiadomości raz na dwa miesiące.

Subscribe to our newsletter

Administratorem Twoich danych osobowych jest Future Processing S.A. z siedzibą w Gliwicach. Twoje dane będziemy przetwarzać w celu przesyłania cyklicznego newslettera dot. branży IT. W każdej chwili możesz się wypisać lub edytować swoje dane. Więcej informacji znajdziesz w naszej polityce prywatności.

Subscribe to our newsletter

Administratorem Twoich danych osobowych jest Future Processing S.A. z siedzibą w Gliwicach. Twoje dane będziemy przetwarzać w celu przesyłania cyklicznego newslettera dot. branży IT. W każdej chwili możesz się wypisać lub edytować swoje dane. Więcej informacji znajdziesz w naszej polityce prywatności.