O przechowywaniu haseł

Tak, znowu będzie o hasłach. Tym razem o przechowywaniu haseł. A bezpośrednią inspiracją jest (tak, zgadliście) to: Allegro: kontrowersje wokół sposobu przechowywania haseł, ale od razu uprzedzam – na ten temat nie napiszę nic.

Krótkie wprowadzenie

Na początek przypomnienie pewnego schematu:

Hasła są przechowywane w data store określone jako dane. Powinny być przechowywane tylko tam , pomijając oczywiście ewentualne zapisanie hasła przez użytkownika (przypominam, że przeszedłem na KeePass jakiś czas temu).

Intruz w posiadanie haseł przechowywanych w bazie może wejść w co najmniej kilka sposobów. Mnie kilka razy w trakcie testów udało się to osiągnąć przez sql injection , ale równie dobrze można sobie wyobrazić, że w ręce intruza trafia dysk, który po dożyciu swych lat przewidzianych zamiast do utylizacji, trafił na aukcję. W końcu kryzys jest i skoro na starym sprzęcie można zarobić, to czemu tego nie robić (a tak przynajmniej niektórzy myślą). Sposób w jaki intruz wejdzie w posiadanie bazy haseł nie jest jednak istotny, ważne jest natomiast to, co może zrobić z tymi danymi. A to, co może zrobić z tymi danymi zależy od tego w jakiej postaci przechowywane są hasła. Dla uproszczenia przyjmijmy, że przypadki mogą być dwa:

Jest baza, co dalej

Hasła w postaci jawnej

Jeśli hasła przechowywane są w formie jawnej, to dalej jest płacz i zgrzytanie zębami. Intruz (prawdopodobnie) dysponuje wówczas wszystkimi danymi potrzebnymi do uwierzytelnienia, może działać w kontekście dowolnego użytkownika, który ma nieszczęście znajdować się w tej bazie. Zakres działania intruza nie jest ograniczony tylko do tego serwisu, który padł jego łupem. Dlatego korzystanie z jednego hasła w wielu różnych serwisach nie jest najlepszym pomysłem. Wystarczy by jeden z serwisów stracił bazę haseł, konta w pozostałych serwisach, nie ważne jak dobrze i bezpiecznie serwisy te przechowują hasła, też mogą się stać “ofiarą”.

Hasła w postaci niejawnej

Zdecydowanie ciekawszym przypadkiem jest ten, w którym hasła przechowywane są w postaci niejawnej.

Hasła w postaci odwracalnej (zamierzenie lub nie)

Pierwsze pytanie – czy funkcja wykorzystywana do przechowywania haseł jest (lub czy może być) odwracalna. O ile nie ma takiej potrzeby – nie powinna być. Dla przypomnienia dwa przykłady, gdy teoretycznie zabezpieczone hasła w praktyce przechowywane są praktycznie w formie jawnej:

Jeśli baza haseł (lub poszczególne hasła) są szyfrowane “dobrym” algorytmem, trzeba pamiętać jeszcze, że Encrypted data is only as secure as the decryption key. Czyli mówiąc wprost zawarcie w tej samej bazie zaszyfrowanych haseł oraz klucza kryptograficznego użytego do szyfrowania byłoby spektakularnym przykładem głupoty.

Hashe (wynik funkcji jednokierunkowej)

Przekształcenie hasło – > hash powinno być jednokierunkowe. Teoretycznie wymaganie to spełnia każda funkcja hashująca (np. MD5, SHA1), w praktyce bezpośrednie wykorzystanie takich funkcji nie jest rozwiązaniem poprawnym.

Skoro nie istnieje przekształcenie pozwalające na uzyskanie z hasha oryginalnego hasła, działanie intruza musi opierać się na sprawdzeniu, jakie hasło daje określony hash. Pomijając atak na sam algorytm hashowania, może to zrobić na dwa główne sposoby:

Krytyczną dla takiego ataku kwestią jest to, ile haseł w jednostce czasu będzie mógł sprawdzić atakujący. Im więcej, tym lepiej dla niego, zwłaszcza jeśli atak słownikowy nie przynosi oczekiwanych efektów i konieczny jest brute force. Temat wydłużania czasu potrzebnego na sprawdzenie poprawności hasła zresztą już poruszałem, patrz: Bootcamp XI: opóźnienia przy logowaniu, tu jednak intruz ma bezpośredni dostęp do hasha hasła, więc pokazywane w tym przykładzie sposoby na spowolnienie brute force nie są skuteczne.

To ile haseł intruz będzie w stanie sprawdzić w jednostce czasu zależy bezpośrednio od tego, w jaki sposób hasła są przechowywane, czyli od tego jak generowany jest hash. Przy okazji warto jest mieć na uwadze kilka dodatkowych kwestii:

Z rainbow tables poradzić sobie jest stosunkowo łatwo – wystarczy wprowadzić unikalny dla każdego hasła salt. Wówczas wynik operacji hash(salt, haslo) jest zależny nie tylko od hasła, ale również od owej (losowej i długiej) wartości salt. Salt oczywiście musi być przechowywany w bazie po to, by móc zweryfikować poprawność hasła, tak więc jego zastosowanie nie wnosi praktycznie żadnej wartości dodanej w odniesieniu od ataku słownikowego lub brute force.

Sposób zwiększenia kosztu obliczeniowego sprawdzenia poprawności hasła jest również stosunkowo prosty. Można na przykład operację hash(salt, haslo) wykonać wielokrotnie, przy czym w kroku n+1 jako haslo należy wykorzystać wynik uzyskany w kroku n. Ilość iteracji może być wartością określoną parametrami systemu. Przy tak realizowanej funkcji wyliczania hasha hasła postęp w zakresie wydajności komputerów może być kompensowany przez zwiększenie ilości iteracji.

I na koniec podsumowanie

Przy składowaniu haseł trzeba pamiętać, że:

Przykładem algorytmu przechowywania haseł, który właśnie w ten sposób działa jest blowfish crypt.

W przypadku aplikacji internetowych warto się zastanowić, czy implementowanie haseł i ich przechowywanie jest potrzebne: Jak rozsupłać hasło, czyli alternatywne przemyślenia o haseł implementacji?

Oryginał tego wpisu dostępny jest pod adresem O przechowywaniu haseł

Autor: Paweł Goleń