Jak solić hasła
Dziś po TKonferencji jedna osoba pytała się mnie, jak bezpiecznie przechowywać salt użyty przy hashowaniu hasła w bazie. Odpowiedź brzmi – salta nie trzeba przechowywać bezpiecznie, ponieważ jest jawny. Albo inaczej – dostęp do bazy danych trzeba ograniczać (co jest oczywiste), jednak sam salt nie wymaga specjalnej ochrony.
Dlaczego hasła nie są przechowywane w formie jawnej? Przecież zgodnie z założeniami nikt do nich się nie dostanie. Chodzi o zasadę defence in depth. Jeśli jednak ktoś w jakiś sposób (a sposobów może być wiele, od sql injection aż po dostęp do “zużytego” dysku, z którego dane nie zostały do końca dobrze usunięte) uzyska dostęp do tej bazy, będzie miał dostęp do haseł. Pierwsza warstwa zabezpieczeń (fosa?) padła, pora na kolejne zabezpieczenie.
W tym miejscu załóżmy, że atakujący ma już dostęp do bazy haseł użytkowników (w tym ich haseł), ale chcemy, by nie mógł jej bezpośrednio wykorzystać. By to osiągnąć stosujemy jakąś funkcję jednokierunkową, która przekształca hasło użytkownika na hash. Często jest to po prostu funkcja skrótu (cryptographic hash function). Funkcja ta jest jednokierunkowa, więc na podstawie hasha nie można odtworzyć hasło hasła, można natomiast próbować wykorzystać różne hasła, może hash któregoś z nich odpowiada temu, który pozyskaliśmy.
Mamy kolejną warstwę zabezpieczeń, ale jest również sposób, by ją pokonać. Potrzeba tylko “trochę” czasu i mocy obliczeniowej. Trzeba przy okazji pamiętać, że funkcje skrótu projektowane są po to, by były szybkie co działa na korzyść atakującego. Atakujący może jednak działać jeszcze skuteczniej. Ponieważ funkcja skrótu jest deterministyczna (dla tego samego wejścia daje takie samo wyjście), atakujący może obliczenia wykonać raz, a z ich wyników korzystać wielokrotnie (patrz: rainbow table).
Dodajmy do tego trochę soli. Jej celem jest zmuszenie atakującego, by obliczenia musiał powtórzyć dla każdego użytkownika z osobna. Jeśli soli nie ma, atakujący liczy hash ze słowa P@$$w0rd, a następnie sprawdza, czy którykolwiek z hashy haseł w bazie ma wartość 6b283bb060c269432d08ac33b47a337c0a40035d. Jeśli natomiast wykorzystana jest sól, dla każdego rekordu musi wyliczyć hash dla testowanego hasła i właściwej (widocznej w bazie) wartości soli. Nie może skorzystać z tablic, bo musiałby przygotować oddzielną wersję tablic dla każdej możliwej wartości soli, co zajęłoby zarówno “trochę” czasu, jak i miejsca. Nadal mamy unikalny salt dla każdego użytkownika, ale by go otrzymać, trzeba uzyskać dostęp lub zgadnąć wartość sekretu.
Sól nie broni nas przed użytkownikiem, który wybiera proste, słownikowe hasła. Jeśli baza haseł wycieknie, istnieje szansa, że jego hasło zostanie złamane. Atakujący może mieć wystarczająco czasu i chęci, by sprawdzić typowe wartości haseł dla każdego użytkownika (a więc i soli) z osobna. By atakującemu utrudnić życie jeszcze bardziej, możemy wybrać taki sposób hashowania haseł, który jest obliczeniowo ciężki (wspominałem już kilkukrotnie o funkcjach typu bcrypt czy scrypt).
Czy można jakoś poradzić sobie z użytkownikami, którzy używają prostych haseł? W tym kontekście o spowodowanie, że w przypadku wycieku bazy ich hasła pozostaną bezpieczne, mimo tego, że są słabe. Do głowy przychodzi mi pewna konstrukcja polegająca na dodaniu do systemu jeszcze jednego sekretu (nazwijmy go mastersalt). Przed hashowaniem hasła na podstawie tego dodatkowego sekretu oraz jawnej i unikalnej dla użytkownika soli, generowany jest nowy salt, który dopiero jest wykorzystywany przy hashowaniu hasła. Atakujący musiałby odgadnąć prawidłowo dwa sekrety, czyli hasło użytkownika i mastersalt , a jeden sekret łatwiej jest chronić. Mimo wszystko zastanawiałbym się, czy gra jest warta świeczki...
Oryginał tego wpisu dostępny jest pod adresem Jak solić hasła
Autor: Paweł Goleń