Preorder drugiego tomu książki sekuraka: Wprowadzenie do bezpieczeństwa IT. -15% z kodem: sekurak-book

Jak bezpiecznie przechowywać hasło w bazie? Odpowiedź zapewne zaskoczy większość z Was…

12 stycznia 2023, 12:56 | Teksty | komentarzy 28

Wyciek czyli co? Najczęściej jest to sytuacja, gdy ktoś otrzymał dostęp do bazy danych (np. aplikacji) i pobrał z niej loginy / hasła.

Hasła (najczęściej) nie są przechowywane w formie jawnej. I w ten bardzo szybki sposób dochodzimy do sedna tego wpisu. W jaki sposób powinny być przechowywane hasła w bazie, aby były jak najbardziej bezpieczne w momencie gdy dojdzie do wycieku?

TLDR: argon2 lub bcrypt lub PBKDF2 lub scrypt. Jednak każdy z tych algorytmów zapewnia parametryzację (sprowadzającą się najczęściej do ustalenia ile zasobów CPU czy pamięci ma zajmować wyliczenie jednego hasha).

1. Obecnie najbezpieczniejszą polecaną opcją jest algorytm Argon2. W 2015 został on zwycięzcą w konkursie Password Hashing Competition. Jak czytamy w dokumencie OWASPu dotyczącym haseł:

Use Argon2id with a minimum configuration of 15 MiB of memory, an iteration count of 2, and 1 degree of parallelism. There are three different versions of the algorithm, and the Argon2id variant should be used, as it provides a balanced approach to resisting both side-channel and GPU-based attacks.

„Argon2id”? Istnieją trzy warianty algorytmu Argon2: Argon2i / Argon2d oraz hybryda dwóch poprzednich Argon2id. Ten ostatni daje on sensowną ochronę zarówno jeśli chodzi o krakowanie na GPU (oraz CPU) jak i tzw. ataki side-channel.

Żeby to jeszcze bardziej skomplikować, algorytm możemy parametryzować za pomocą tzw. work factor (czyli ile mocy obliczeniowej zajmie pojedyncze wyliczenie hasha). W przypadku Argon2 są to parametry -t -m -p poniżej:

Wspominałem wyżej o rekomendowanych przez OWASP parametrach dla Argon2 – tutaj alternatywne rekomendacje dotyczące doboru tych parametrów (strona 15). Ogólnie chodzi o to, że jeśli zwiększymy np. parametr memory – pojedyczne wyliczenie hasha będzie wymagało tyle pamięci. Atakujący będzie chciał maksymalnie zrównoleglić liczenie hashy, ale zaraz wymagana do całego procesu pamięć będzie gigantyczna, więc będzie musiał bardzo zwolnić – i o to właśnie chodzi!

Tutaj możecie pobawić się Argonem2 na żywo:

Argon2 jest stosunkowo nowy (na tyle, że np. dopiero niedługo będzie dostępny w OpenSSL czy Hashcacie), jakie mamy więc inne możliwości?

2. Historycznie do przechowywania haseł używane były algorytmy typu: MD5(), SHA1(), SHA2() czy SHA3(). Ale też wszystkie dość szybko można łamać. Jak szybko? Na jednej karcie graficznej RTX 4900 wygląda to następująco:

  • MD5 – 164.1 GH/s (164 miliardy sprawdzeń na sekundę).
  • SHA1 – 50638.7 MH/s
  • SHA2-256 – 21975.5 MH/s
  • SHA3-256 – 5058.7 MH/s

Obecnie nie jest zalecanie bezpośrednie wykorzystanie przechowywanie ww. hashy do przechowywania haseł w bazie.

3. PBKDF2

W nowych systemach jednym z również zalecanych algorytmów jest między innymi ten właśnie PBKDF2. Chociaż nie wszyscy go kochają:

If you’re forced to use PBKDF2 for whatever dumb reason, the parameters you want are at least:

  • SHA-512 with 210,000 rounds (preferred)
  • SHA-256 with 600,000 rounds
  • SHA-1 (if you must) with 1,300,000 rounds

Mamy tu od razu sporo zamieszania. Skąd te „rundy” (zwane czasem iteracjami)? Skąd nagle pojawiają się algorytmy z rodziny SHA?

PBKDF2 to – w pewnym uproszczeniu – iteracyjne uruchomienie funkcji pseudolosowej na haśle. Czym jest funkcja pseudolosowa? Np. HMAC-SHA-256() – ta ostatnia wiąże się z dwukrotnym wyliczeniem hasha SHA-256() – wg pewnego specyficznego schematu.

Od strony łamania PDKDF2() musimy wiedzieć tyle:

  • da się to zrobić, ale proces jest dość wolny. Przykładowo hashcat ma wsparcie do łamania takich wariantów:

PBKDF2-HMAC-MD5
PBKDF2-HMAC-SHA1
PBKDF2-HMAC-SHA256
PBKDF2-HMAC-SHA512

  • Jeśli użyjemy SHA-512 + 210,000 iteracji, to przy pojedynczej próbie musimy wykonać 210000 * 2 (bo HMAC) = 420 000 wyliczeń SHA-512. Więc łamanie takiego algorytmu jest około 420 000 razy wolniejsze niż SHA-512

Warto również zaznaczyć, że jeśli źle ustawimy liczbę iteracji (rund), to hasło będzie bardzo szybko łamane – pisaliśmy o takim przypadku przy okazji menadżera haseł LastPass, gdzie niektórzy użytkownicy mieli ustawioną liczbę iteracji na 1 (jeden).

4. bcrypt (również dobra alternatywa)

Zaleca się ustawienie tzw. work factora na minimum 12 (OWASP zaleca minimum 10).

5. scrypt (również dobra alternatywa)

Stosunkowo rzadziej spotykany w rzeczywistych systemach. Również wymaga parametryzacji (patrz tutaj)

Na koniec – pamiętajcie, że jeśli użytkownicy będą ustawiali proste hasła – np. takie jak na tweecie poniżej – to żadne hashowanie nie pomoże (atakujący zastosują prosty atak słownikowy z lekkimi wariacjami):

Obecna złota zasada mówi, że hasło powinno być po prostu długie (> 15 znaków, mogą to być same litery i hasło złożone z 3 a najlepiej 4+ nieoczywistych słów).

~Michał Sajdak

Spodobał Ci się wpis? Podziel się nim ze znajomymi:



Komentarze

  1. Piotr

    Można liczyć na wpis o PAKE?

    Odpowiedz
  2. SeeM

    A jeżeli mamy już hasło w formie MD5, to jak je skonwertować na argona? Wystarczy poczekać, aż użytkownik aię zaloguje i przechwycić na chwilę hasło z aplikacji.

    Odpowiedz
  3. Adam

    A nie powinien być używany Agon2i?

    Argon2i instead uses data-independent memory access, which is preferred for password hashing and password-based key derivation, but it is slower as it makes more passes over the memory to protect from tradeoff attacks.

    Odpowiedz
    • Zależy w jakich zastosowaniach. Tak uniwersalnie to:

      * Use Argon2id with a minimum configuration of 15 MiB of memory, an iteration count of 2, and 1 degree of parallelism. [OWASP]
      * Argon2id with a memory cost of 64 MiB, ops cost of 3, and parallelism of 1 [https://soatok.blog/2022/12/29/what-we-do-in-the-etc-shadow-cryptography-with-passwords/]

      Odpowiedz
  4. _admin

    A nasz WordPress sobie używa MD5.

    Warto też dodać, że ciężki algorytm haszujący może spowodować Denial of Service, jeśli się nie ograniczy czasowo ilości prób logowania.

    Odpowiedz
  5. bcrypt ignoruje wszystko powyżej 72 *bajta* hasła kodowanego UTF-8. Szansa natrafienia na takie jest minimalna, ale wystarczy ktoś z 10-wyrazowym diceware i niechcący można mu osłabić hasło. Ewentualnie hashowanie hasła i loginu razem też może dać nieoczekiwanie długą wartość. Można sprawdzić, czy dane nie są za długie i ścisnąć je np. SHA256 (64 bajty w zapisie szesnastkowym) lub SHA384 (64 bajty w zapisie base64).

    Odpowiedz
  6. Bart

    A co gdy użyjemy md5 ale dołożymy duuuuzo soli (losowego ciągu znaków) i interakcję powtórzymy i znów dodając inną sól?

    Odpowiedz
    • Na biedę najpewniej ujdzie ;-)

      Odpowiedz
      • MaDness

        Hashe mają kolizje jak nawet jak dasz bardzo długie i skomplikowane dane wejściowe, to istnieje dużo krótszych, które dają ten sam hash. A w MD5 ich wyznaczenie jest możliwe.

        Odpowiedz
        • Tylko że w MD5 można szybko generować zwykłe kolizje – czyli wygenerować x,y takie że md5(x)=md5(y). Ale niezbyt proste (niezbyt szybkie) jest zrobienie kolizji preimage https://en.wikipedia.org/wiki/Preimage_attack o którą tutaj chodzi. Czyli mając md5(x) szukamy dowolnego x (na tym polega łamanie haseł).

          Odpowiedz
    • Ciekawostka

      Właśnie 2 tyg temu rozmawiałem z osobą, która zna kogoś, kto oblicza md5 w pamięci patrząc na hasha.
      Po za tym możesz sypać soli ile chcesz, to algorytm i tak jest ten sam.

      Odpowiedz
      • zgrywus

        a ja znowu słyszałem o gościu, który zna facetkę, której psiapsiółka ma chłopaka, którego siorka jak walnie 3 driny to z zamkniętymi oczami gra w szachy symultanicznie z 8 graczami.
        Ta to ma wyobraźnię
        ;)

        Odpowiedz
    • Jeżeli dla każdego wpisu w bazie będziemy przechowywać po kilkadziesiąt megabajtów soli i ładować to przy każdej próbie logowania, to może i coś z tego wyjdzie. Nie wiem, czy wyjdzie ochrona hasła, ale na pewno wyjdzie piękny DoS własnej usługi. ;)

      „Don’t roll your own crypto”.

      Odpowiedz
  7. Bart

    Interacje miało być

    Odpowiedz
  8. Яна Мазур

    26 znaków po rosyjsku. Małe litery, tylko to co widać na klawiaturze, bez zmieniaczy (np. shift). Inny alfabet istotnie zwiększa entropię.

    Odpowiedz
    • Jeżeli atakujący zna metodę generowania (w tym przypadku: użycie alfabetu rosyjskiego), to nie różni się niczym od użycia 26 liter łacińskich. A ten warunek jest spełniony z założenia, bo bezpieczeństwo rozwiązania kryptograficzne nie może zależeć od wiedzy na temat jego działania.

      Nawet pomijając to, rozwiązanie takie zwiększałoby entropię o nie więcej niż jeden bit na znak. Czyli więcej pożytku będzie z dodania zaledwie jednego dodatkowego znaku do 8-literowego hasła niż nie tylko użycia samej cyrylicy, ale mieszanki cyrylicy, alfabetu łacińskiego i cyfr.

      W zamian przy powszechnie stosowanym UTF-8 otrzymujesz o połowę niższą gęstość entropii w przeliczeniu na oktet (nie znak), co w połączeniu z limitowaniem długości haseł przez wiele serwisów daje osłabienie hasła. Nie wspominając o problemach z kodowaniem i obsługą znaków.

      Odpowiedz
      • GABS

        Jak to jest z używaniem hashcata i znaków w haśle z innych alfabetów niż łaciński?

        Chodzi mi o to czy domyślne hashcat używa wyłącznie alfabetu łacińskiego a pozostałe musimy dodać jako atrybut, doinstalować, dodać bibliotekę, utworzyć własny zestaw?

        Odpowiedz
        • asdsad

          Też mi się wydaje, że domyślnie jest łaciński. Chyba, że słownik – wtedy ładujemy jaki chcemy. Ale jeśli miałby przelecieć brute-forcem całą cyrylice, to chyba tylko jako zadeklarowany zestaw.

          Odpowiedz
  9. bckqs

    Link do artykułu o LastPass się nie otwiera.

    Odpowiedz
  10. Najbezpieczniej?

    Poza bazą, na „blachę”, długie, z polskimi znakami.

    Dziękuję, dobranoc xD

    Odpowiedz
  11. Sernik

    Sekuraku a pieprz? W waszej książce na stronach 199 i 200 jest co nieco opisane o pieprzu. Czy implementowanie pieprzu nie jest popularną praktyką?

    Odpowiedz
    • Przede wszystkim sól. Pieprz również.

      Odpowiedz
  12. laik

    Jestem laikiem, ale gdybym miał się włamać i pozyskać jakąś bazę do „odszyfrowania” gdzie ktoś zastosował sól i pieprz to pewnie wcześniej bym się tam „zarejestrował” (zakładam, że mając dostęp do sieci wew. mogę to zrobić) podając znane mi hasła i użył ich jako „wzorów/próbek”.
    Taki pomysł laika.

    Odpowiedz
  13. asdsad

    Przydatny artykuł, dzięki!

    Odpowiedz
  14. aaa

    „Odpowiedź zapewne zaskoczy większość z Was…” – z tym o co chodzi – tzn co nas miało zaskoczyć? Na oko to nic nie zaskakuje.

    Odpowiedz
  15. Joel

    Żeby hasło w bazie danych było bezpieczniejsze w razie włamania do bazy danych, można stworzyć kilka kolumn z adresem email i hasłem (adres email wg. mnie też powinien być szyfrowany) np: kolumny od email_1 do email_7 i kolumny od password_1 do password_7. Hasło można przechowywać w jednej z kolumn email a email w jednej z kolumn password. W pozostałych sześciu możemy przechowywać zaszyfrowany losowy ciąg znaków. Po drugie, podczas hashowania możemy użyć różnych funkcji hashujących np: nasze hasło $password,

    $password = hash(’sha384′, $password);
    $password = hash(’sha256′, $password);
    $password = hash(’sha512′, $password);
    $password = hash(’gost’, $password);
    $h1 = hash(’md5′, $password);
    $h2 = hash(’md5′, $h1);
    $h3 = hash(’md5′, $h2);
    $h4 = hash(’md5′, $h3);
    $hash_db = $h1 . $h2 . $h3 . $h4;
    W bazie przechowujemy $hash_db. Hash ma 128 znaków, wygląda jakby używany był algorytm sha512.

    Odpowiedz

Odpowiedz