Autentykacja to ochroniarz stojący u drzwi Twojej aplikacji. Za każdym razem, gdy ktoś się rejestruje lub loguje, Twój system musi zdecydować, czy dana osoba jest tym, za kogo się podaje. Jeśli popełnisz błąd, nie będziesz tylko debugować nieudanej próby logowania. Ryzykujesz, że prawdziwe dane użytkowników po prostu wyjdą drzwiami. Prawidłowe wykonanie tego zadania wymaga dwóch niezawodnych narzędzi: Bcrypt do zabezpieczania haseł w spoczynku oraz JSON Web Tokens do weryfikacji tożsamości podczas poruszania się użytkownika po aplikacji.
Dlaczego przechowywanie tekstu jawnego zawodzi
Jeśli masz zapamiętać tylko jedną zasadę z tego artykułu, niech to będzie ta: nigdy nie przechowuj haseł w formie tekstu jawnego w swojej bazie danych. Nie ma znaczenia, czy Twoja baza danych znajduje się za firewallem, ani czy ufasz każdemu inżynierowi w zespole. Zapisywanie haseł w tabelach w ich surowej formie jest jak zostawianie kluczy do domu pod wycieraczką. W momencie, gdy ktoś uzyska dostęp do tej bazy danych — poprzez błędnie skonfigurowane API, wyciek kopii zapasowej lub atak typu injection — każde poświadczenie zostaje natychmiast ujawnione.
Szkody mnożą się, ponieważ ludzie używają tych samych haseł wielokrotnie. Pojedynczy wyciek może narazić nie tylko Twoją aplikację, ale także pocztę e-mail, konto bankowe i media społecznościowe użytkownika. Właśnie dlatego stosujemy hashowanie haseł. Hashowanie przekształca hasło w pomieszany ciąg znaków, który nie wykazuje żadnego oczywistego podobieństwa do oryginału. Proces ten jest jednostronny i nieodwracalny. Nie można polać hasha „matematycznym kwasem”, aby rozpuścić go z powrotem do hasła, które go stworzyło.
Hashowanie haseł za pomocą Bcrypt
Bcrypt to funkcja haszująca zbudowana specjalnie z myślą o hasłach. Pobiera ona zwykły ciąg znaków, przepuszcza go przez szyfr Blowfish i zwraca wynik, który wygląda mniej więcej tak: $2b$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy. Ten prefiks informuje o algorytmie i współczynniku kosztu (cost factor). Dłuższy ogon to kombinacja soli (salt) i samego hasha.
Sól to losowe dane mieszane z hasłem przed rozpoczęciem hashowania. Ponieważ sól jest unikalna dla każdego użytkownika, dwie osoby, które wybiorą „password123”, otrzymają w bazie danych zupełnie inne hashe. Ta drobna różnica niweluje skuteczność tablic tęczowych (rainbow tables) — czyli wstępnie obliczonych słowników hashy dla popularnych haseł — ponieważ atakujący musieliby generować całą tabelę dla każdej unikalnej soli.
Bcrypt jest również celowo wolny. Nowoczesny sprzęt potrafi odgadnąć miliardy hashy na sekundę przy użyciu szybkich algorytmów, takich jak SHA-256. Bcrypt natomiast celowo spowalnia proces. Uruchamia proces hashowania wielokrotnie, a jest on kontrolowany przez współczynnik kosztu, który można zwiększać wraz z postępem wydajności komputerów. To spowolnienie karze każdego, kto próbuje przeprowadzić atak brute-force na skradzioną bazę danych. Użytkownik, który podczas logowania poczeka dodatkowe dwieście milisekund, nie zauważy różnicy. Atakujący próbujący przetestować miliony kombinacji – z pewnością tak.
Jak działają JSON Web Tokens
Podczas gdy Bcrypt pilnuje drzwi wejściowych, JWT pełni rolę przepustki na korytarzu. JSON Web Token to kompaktowy, bezpieczny dla URL ciąg znaków, który dowodzi, że użytkownik został już uwierzytelniony. Można o nim myśleć jak o cyfrowym dowodzie tożsamości, który wystawia serwer, a użytkownik nosi przy sobie.
JWT składa się z trzech segmentów oddzielonych kropkami: nagłówka (header), ładunku (payload) oraz podpisu (signature). Nagłówek określa typ tokena i algorytm podpisywania. Ładunek zawiera roszczenia (claims) — stwierdzenia dotyczące użytkownika i samego tokena — takie jak identyfikator użytkownika, nazwa użytkownika oraz znacznik czasu wygaśnięcia. Podpis jest pieczęcią kryptograficzną. Serwer tworzy go poprzez zakodowanie nagłówka i ładunku, a następnie przepuszczenie ich przez klucz tajny. Jeśli ktokolwiek zmodyfikuje ładunek, podpis przestanie pasować, a serwer natychmiast odrzuci token.
