Czy da się przechowywać hasło użytkownika w taki sposób aby móc je zweryfikować podczas logowania ale nie znać jego treści? Brzmi to jak niezły paradoks, prawda? Okazuje się, że istnieje sposób aby tego dokonać. Wykorzystuje się w tym celu właśnie tytułowe funkcje skrótu.
Trochę teorii
Funkcje skrótu, nazywane często funkcjami haszującymi, to funkcje generujące unikatowy, stałej długości skrót z dowolnej ilości danych. Ważne jest również to aby taka funkcja była jednokierunkowa. Znaczy to tyle, że na podstawie skrótu nie jesteśmy w stanie wygenerować danych, z którego on powstał.
Nawet minimalna modyfikacja danych powoduje diametralną zmianę ich skrótu. Poniżej przykład z zastosowaniem funkcji haszującej SHA-1. Na czerwono zaznaczyłem różnicę w obu zdaniach.
Dane | Skrót SHA-1 |
becomeapro.pl to najlepsza strona internetowa pod słońcem |
c94363dfee89e19b5f71e9d587a64f256f587ef7 |
becomeapro.pl to najlepsza strona internetowa pod słoncem |
8809bdb9b54cd615c2a0b55cea32d4e2803b28a2 |
Bez względu na ilość haszowanych danych skrót będzie miał zawsze jednakową długość.
Dane | Skrót SHA-1 |
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus nec dictum ex. Sed pulvinar enim lacus, at mattis nulla porttitor in. Curabitur in pretium turpis. Donec pretium, massa at venenatis varius, enim ligula elementum tellus, ac luctus arcu turpis ac nulla. Ut leo ligula, dignissim a tellus sit amet, ultrices vestibulum quam. Ut laoreet velit sit amet ex suscipit, vel tempor justo auctor. Sed at nulla at orci faucibus porttitor at in arcu. Duis efficitur ipsum efficitur augue ullamcorper pellentesque. Donec faucibus consectetur lorem, interdum rhoncus metus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Maecenas vulputate a lacus eu laoreet. Etiam commodo consequat elit eu posuere. Phasellus tempus vitae nunc nec vehicula. Nulla venenatis accumsan semper. Nunc enim orci, laoreet eu posuere at, maximus vel risus. |
9c31a0bfeb70fab64ee2d99db46edb25afc1012f |
ABC |
3c01bdbb26f358bab27f267924aa2c9a03fcfdb8 |
Na deser stworzyłem małe demo, które haszuje wprowadzony tekst wybraną przez Ciebie funkcją skrótu.
Jak to wszystko ma się do haseł?
Podczas procesu uwierzytelniania użytkownika zachodzi potrzeba sprawdzenia czy podane przez niego hasło jest prawidłowe. Oczywiste prawda?
Jaką wadę ma to rozwiązanie? Hasło przechowywane jest w formie jawnej i w razie włamania atakujący będzie w stanie je bez problemu poznać. Wbrew pozorom takie sytuacje zdarzają się często. Niestety… Możesz się o tym przekonać wchodząc na stronę haveibeenpwned.com gdzie podając swojego maila bądź nazwę użytkownika sprawdzisz czy jesteś ofiarą któregoś z dużych wycieków baz danych.
Na szczęście jako programiści możemy utrudnić atakującemu zadanie tak aby w razie włamania nie był w stanie poznać haseł naszych użytkowników. Wystarczy, że przechowamy je w naszej bazie danych w formie haszy:
Musimy również zmodyfikować kod odpowiedzialny za weryfikację użytkowników. Funkcja licząca skrót SHA-1 pochodzi z przykładu z GitHuba.
Teraz w razie nieautoryzowanego dostępu do bazy danych atakujący będzie miał nie lada wyzwanie aby poznać oryginały haseł.
Sól i tęczowe tablice
Okazuje się, że same funkcje skrótu nie są wystarczającą warstwą obrony przed potencjalnymi atakującymi. Przecież przeciętny użytkownik nie używa długich i skomplikowanych haseł. A gdyby tak stworzyć bazę danych różnych kombinacji haseł i ich skrótów?
Takie bazy danych są nazywane właśnie rainbow tables czyli tęczowe tablice. Zwykle zajmują setki gigabajtów ale umożliwiają stosunkowo szybkie odzyskanie haseł na podstawie hashy. Genialne w swojej prostocie, prawda? Poniżej masz zestaw kilku haseł zahaszowanych algorytmem MD5. Spróbuj je złamać przy pomocy strony crackstation.net, która korzysta właśnie z tęczowych tablic.
098f6bcd4621d373cade4e832627b4f6 49aa66843380c377e93b198b966eb699 bbb2c5e63d2ef893106fdd0d797aa97a 34819d7beeabb9260a5c854bc85b3e44 7df254d0fdbbc64a9317265a5084a29b
Udało się?
Jak się przed tym bronić?
Tutaj z pomocą przychodzi nam właśnie wspomniana wcześniej sól. Jest to losowo generowana wartość dodawana do danych przed ich zahaszowaniem. Jeśli chcielibyśmy złamać posolone wcześniej hasła musielibyśmy wygenerować tyle tęczowych tablic ile soli jest w bazie danych. Pamiętasz jak wspominałem ile pamięci potrafią zajmować? Setki gigabajtów! Właśnie to ograniczenie czyni ten atak niepraktycznym (co nie znaczy, że niemożliwym 😉 ).
Przykład? Rozważmy sobie taki zestaw danych:
W tabeli mamy dwóch użytkowników, którzy mają takie same hasła. Dlaczego ich skróty są różne? To właśnie dzięki zastosowaniu soli. Poniżej nasz kod zmodyfikowany w taki sposób aby był w stanie obsługiwać solone hasła.
Tylko tyle i aż tyle.
Podsumowanie
Jak widać w stosunkowo prosty sposób możemy zapewnić dodatkową warstwę ochrony danych użytkowników naszych aplikacji. Jednak nie dajmy się zwieść pozorom. Napastnik, który ma wystarczająco dużo cierpliwości i zasobów prędzej czy później i tak pozna zabezpieczone w ten sposób hasła. Warto jednak zapewnić sobie czas na ewentualne poinformowanie użytkowników o konieczności ich zmiany. Jeśli interesujesz się tą tematyką polecam obejrzeć sobie prelekcję Pana Marcina Rybaka z konferencji SECURE 2016 o wymownym tytule „Wczoraj złamałem Twoje hasło, jutro złamię je ponownie„. Daje do myślenia.
19 kwietnia 2017 at 16:24
„unikatowy, stałej długości skrót z dowolnej ilości danych” – hmm, to bardzo ryzykowne stwierdzenie 🙂
Jestem pewien, że wiesz o co chodzi i to skrót myślowy, ale znam przypadki gdy założenie o unikatowości prowadziło do dużych problemów.
Przykładowo, w pewnym systemie stosowano dla widoków w bazie danych funkcję skrótu jako identyfikator wiersza (bo ORM-y wymagają id dla wierszy, a joiny nie pozwalały użyć żadnej z kolumn jako id). Wszystko działa super, ale po paru latach działania systemu gdy liczba danych jest już duża, pojawiają się pierwsze kolizje. Cały problem w tym, że funkcja skrótu nie daje gwarancji unikalności, a wręcz daje gwarancję jej braku przy odpowiedniej ilości danych 🙂
Spoko artykuł z podsumowaniem tematu 🙂
19 kwietnia 2017 at 17:29
Dzięki za trafną uwagę. To faktycznie skrót myślowy 🙂 Mogłem wspomnieć o tak zwanym ataku urodzinowym, który sprowadza się właśnie do wygenerowania odpowiednio dużej ilości danych w celu znalezienia kolizji. Nie wspomnę już o słabościach samych funkcji haszujących. Tutaj przykład powszechnie stosowanego algorytmu MD5:
http://www.computerworld.pl/news/Szybsze-kolizje-MD5,85133.html
24 kwietnia 2017 at 13:52
Jest jeszcze jedna rzecz, o której warto pamiętać w temacie haszy. Zarówno MD5, jak i rodzina SHA zostały stworzone głównie z myślą o weryfikacji treści, tj. porównywanie haszy zamiast porównywania całych treści dużych zbiorów danych. W związku z tym jednym z głównych wymagań dla nich jest to, by były szybkie i tym samym odpowiednio szybkie było wyliczenie skrótu w wielogigabajtowych plików.
W wypadku haseł, które są z reguły krótkie, taka szybkość niekoniecznie jest zaletą, gdyż pozwala na sprawdzanie wielu milionów kombinacji w bardzo krótkim czasie. Dlatego powstały inne funkcje, jak bcrypt czy scrypt, które wyliczają hasza odpowiednio długo, tym samym ograniczając bardzo mocno możliwości ataków typu bruteforce.
24 kwietnia 2017 at 14:11
Słuszna uwaga, dzięki!