---
title: "Koktajl z bcrypta i wyłuskiwanie haszy"
url: "https://unshade.pl/blog/koktalj-z-bcrypta-i-wyluskiwanie-haszy"
lang: "pl"
date: "17.06.2024"
author: "Unshade"
---

# Koktajl z bcrypta i wyłuskiwanie haszy

*Opublikowano: 17.06.2024*

## Koktajl z bcrypta i wyłuskiwanie haszy

TL;DR - łączenie bcrypta z innymi, niesolonymi funkcjami skrótu (typu MD5 czy SHA-1), może powodować poważne konsekwencje w postaci "wyłuskania" bcrypta. Nadal jest to jednak lepsze, niż po prostu używanie słabego algorytmu. W niektórych przypadkach, możliwe jest znalezenie czegoś w rodzaju "kolizji".

### Wyłuskiwanie haszy aka. password shucking

#### 30ml legacy code

Firmy, które istnieją juz jakiś czas na rynku niejednokrotnie muszą dopasowywać się do nowych okoliczności, wytycznych lub schematów działania. Tak jest również w kwestii przechowywania haseł. Lata temu biznesy radziły sobie z tym na różne sposoby - przechowywały hasła bez jakiegokolwiek zabezpieczenia, zaszyfrowane, czy z użyciem szybkich funkcji skrótu typu MD5 czy SHA-1. Kiedy nadszedł jednak powiew zmian, i chęć lub konieczność dopasowania swojej infrastruktury do obecnych realiów, nastąpił problem. Jak zmienić wszystkie hasze haseł swoich użytkowników w całej bazie danych na nowszy algorytm? Można wyróżnić trzy poniższe pomysły.

Po pierwsze, kazać każdemu nadać nowe hasło. Niesie to za sobą jednak pewne konsekwencje - nie jest to wygodne ani dla nas, ani dla użytkowników. Musimy obsłużyć bardzo dużą, nagłą i natychmistową liczbę operacji oraz narażamy się na podejrzenia o wyciek danych. Klienci mogą chcieć zrezygnować z korzystania z platformy.

Po drugie, można przy logowaniu podmieniać hasze użytkowników w bazie. Pomysł nienajgorszy - nikt się nie dowie, z zewnątrz nic nie widać, ale ile to zajmie czasu? Jakaś część użytkowników jest pewnie naszymi stałymi klientami, ale wymiana wszytstkich to w najlepszym przypadku miesiące, o ile nie lata. Co, jeśli w tym czasie będziemy mieli wyciek? Co z użytkownikami, którzy nigdy się nie zalogują? Opinii publicznej nie będzie interesowało, że 15% użytkowników zdązyliśmy przerzucić na bcrypta przed wyciekiem. Trzeba obsługiwać też wiele mechanizmów logowania na raz.

Po trzecie - a może by tak zahaszować hasza? Weźmy hasza każdego użytkownika i policzmy z niego bcrypta - np. `bcrypt(MD5($pass))`. Mechanizm również niewidoczny z zewnątrz. Co prawda musimy wspierać dwa algorytmy na raz, ale jesteśmy od razu bezpieczni, nie mamy przecież MD5 w bazie. Ale czy na pewno?

#### 90ml danych z wycieków

Problemem, który zaczyna wychodzić nam naprzeciw jest `password shucking`. Metoda, która w języku polskim nie ma ani znanego mi tłumaczenia, ani opisu działnia - roboczo nazwijmy ją zatem `wyłuskiwaniem haszy`. Żeby zrozumieć, dlaczego jest ona problemem, rzućmy okiem na jeden z najpopularniejszych serwisów agregujących dane o wyciekach - `haveibeenpwned`. Obecnie, znajduje się w nim `12,485,202,808` haseł, z 664 stron na dzień 27 marca 2023. Spójrzmy na kilka ostatnich opublikowanych informacji o wyciekach: TheGradCafe, Shopper+, Eye4Fraud, LBB czy iDTech - o wszystkich z nich HIBP poinformował na Twitterze w marcu 2023.

![Screenshot%20from%202023-03-27%2010-09-30](/_emdash/api/media/file/01M5A275H52Y33RRX20TSX8WHB.png)

![Screenshot%20from%202023-03-27%2010-10-22](/_emdash/api/media/file/01M5A275TZ2E5NK76C60H1H5PB.png)

![Screenshot%20from%202023-03-27%2010-10-35](/_emdash/api/media/file/01M5A27652HC9AEC8PKMBPXE76.png)

![Screenshot%20from%202023-03-27%2010-10-46](/_emdash/api/media/file/01M5A276EB61MMRX8QZP0Y1T9Y.png)

![Screenshot%20from%202023-03-27%2010-11-00](/_emdash/api/media/file/01M5A276R4W6HWRVQJEEFWPCSH.png)

Przytłaczająca większość haszy z tych, i wielu innych wycieków o których informuje HIBP była już w ich bazie danych. Co to oznacza? Możnaby pokusić się o stwierdzenie, że przynajmniej część użytkowników używa po prostu tego samego hasła w wielu miejscach.

Inne wycieki ujawnione w tym roku:

![3](/_emdash/api/media/file/01M5A2771MXG62G6Q8XXVR74G5.png)

![md51](/_emdash/api/media/file/01M5A277AVQ8KDA2K1BDYV9E8Z.png)

![md52](/_emdash/api/media/file/01M5A277NYS97SX56QDK3196CN.png)

(Hasła przechowywane w MD5, czy nawet w postaci nieprzetworzonej.)

Czym jest w takim razie właściwie `wyłuskiwanie haszy`? Weźmy na warsztat (nieco zmodyfikowany) przykład Royce Williamsa, członka zespołu `hashcat`. Hipotetyczna sytuacja jest następująca:

1. Atakujący uzyskuje bazę danych z giełdy kryptowalut, w których znajdują się hasze haseł w postaci bcrypta.
1. Atakujący przeprowadza najprostsze, bezpośrednie ataki na bcrypta - niestety nie dają sukcesu - algorytm bcrypt jest bardzo powolny.
1. Atakujący konstruuje słownik do ataku na bcrypta, w którym znajdują się hasze MD5 z innego, niepowiązanego wycieku, np. z portalu randkowego. Sprawdza, czy któraś z MD5 jest prawidłowym "hasłem" do bcrypta. Może też poszukiwać hasza konkretnego użytkownika, jeżeli atak skierowany jest na określoną osobę. Jeśli atakujący trafi chociaż jedną MD5, może wyciągnąć następujące wnioski:
- Po pierwsze, bezpośrednie złamanie bcryptów, których bazą jest wyjście z MD5 jest mało prawdopodobne.
- Po drugie, złamanie MD5 z innego wycieku w połączeniu z popularnością reużywania haseł przez użytkowników w różnych serwisach daje mu spore szanse na sukces.

Łamanie MD5 w praktyce jest dużo szybsze niż bcrypta. Karta graficzna RTX4080 Founders Edition potrafi łamać MD5 z prędkością około `98000 MH/s`, słownie `dziewięćdziesięciu ośmiu miliardów haszy na sekundę`. Bcrypt? `131 kH/s` - słownie `sto trzycieści jeden tysięcy haszy na skeundę`. Jest różnica.

Tym właśnie jest `password shucking` - wyłuskaniem słabszego algorytmu z silniejszej obudowy. Takie "opakowywanie" ma miejsce także w innych sytuacjach, np. kiedy ktoś (dosyć nieumiejętnie) chce obejść ograniczenia bcrypta co do maksymalnej długości hasła, albo uznał, że dzięki temu łatwe hasła zostaną "wzmocnione". Nie dotyczy to tylko MD5, a każdego niesolonego algorytmu opakowywanego w bcrypta. Narzędzie `hashcat` wspiera także bezpośrednie łamanie niesolonych algorytmów opkowywanych w bcrypta:

- `25600 - bcrypt(md5($pass))`
- `25800 - bcrypt(sha1($pass))`
- `30600 - bcrypt(sha256($pass))`
- `28400 - bcrypt(sha512($pass))`

Nie jest to również atak teoretyczny - Royce przyznaje, że:

> This is not a theoretical attack. It is used all the time by advanced password crackers to successfully crack bcrypt hashes that would otherwise be totally out of reach for the attacker.

#### 2 sztuki dobrych rad

Jak w takim razie zrobić to dobrze?

1. Należy użyć pieprzu, czyli zapisanej osobno, długiej i losowej wartości, która będzie dodawana do słabszego algorytmu (np. w postaci `bcrypt(md5($pass).$pepper)`, tak, aby inne wycieki nie dawały przewagi w łamaniu opakowanych haszy. Trzeba jednak mieć na nią oko - utrata pieprzu oznacza powrót do punktu wyjścia.
1. Można połączyć pomysły opakowywania bieżących haszy i wymiany ich na "zwykłego" bcrypta przy logowaniu użytkownika.

### Wstrząśnięte, nie zmieszane - podano koktajl z bcrypta

Innym problemem, który możemy napotkać podczas łączenia bcrypta z innymi funkcjami jest to, w jaki sposób został on zaimplementowany. Weźmy na warsztat język PHP oraz wpis Anthon'ego Ferrara (@ircmaxwell) opiosujący kwestię implementacji bcrypta. Wszystko sprowadza się właściwie do zdania:

> Basically, it ignores everything after the first null byte.

Jeżeli w wejściu do funkcji bcrypt znajdzie się `null byte`, wszystko po nim zostanie zignorowane. Czy to jest problem? Cóż, używanie `null byte` w haśle z pewnością nie należy do najpopularniejszych zabiegów wśród użytkowników. Czy w takim razie, powoduje to jakiś realny problem?

![disperse-lesley-nielsen](/_emdash/api/media/file/01M5A2786DN924FYGMWTYGDZWE.gif)

Jeżeli połączymy wyjście z funkcji typu MD5, SHA, czy HMAC-SHA z bcryptem to duży. W nieprzetowrzonym wyjściu z tych funkcji, `null byte` jest standardowo pojawiającym się znakiem. Co więcej, Anthony podaje statystykę wskazującą na fakt, że 1 na 256 (~0,39%) wygenerowanych "pre-haszy" z użyciem HMAC-SHA256 będzie miało `null byte` jako pierwszy znak. Posłużę się kolejnym jego przykładem, w którym wygenerujemy dwa "hasła" z użyciem HMAC-SHA256, które bedą od siebie różne, a jednak ich weryfikacja jako ten sam bcrypt będzie prawidłowa (dzięki pierwszemu znakowi `null byte`):

```php
$key = "cwuioshc8934f89ch398h34hdfhd3d3d4d343d"; //losowo wybrany klucz, wybrany poprzez uderzenie czołem w klawiaturę
$hash_function = "sha256";
$i = 0;
$found = [];

while (count($found) < 2) {
    $pw = base64_encode(str_repeat($i, 5));
    $hash = hash_hmac($hash_function, $pw, $key, true); //tworzenie HMAC-SHA256 na podstawie klucza oraz licznika pętli
    if ($hash[0] === "\0") {
        $found[] = $pw;
    }
    $i++;
}

var_dump($i, $found);
$hash = password_hash(hash_hmac("sha256", $found[0], $key, true), PASSWORD_BCRYPT);
var_dump(password_verify(hash_hmac("sha256", $found[1], $key, true), $hash));
```

Rezultat jest następujący:

![result](/_emdash/api/media/file/01M5A278JW5QYSR5NWHHTYN7K2.png)

Dwa różne wyjścia z HMAC-SHA256 dają zbieżny wynik bcrypt. Problemem jest nie tylko znak `null byte` na pierwszym miejsu, a właściwie na każdym. Przypadek nie jest także typowo PHPowy - sama zależność `crypt(3)` w języku C posiada ten sam "feature". Nie jest to nawet przypadłość tylko i wyłącznie bcrypta, a różnych rozwiązań z rodziny `crypt()`. Przykład bcrypta jest natomiast dobry, gdzyż bcrypt jest rozwiązaniem bardzo populanrym i obecnym w wielu miejscach - PHP dostępny zarówno bezpośrednio jako `password_hash()` jak i we frameworkach (chociażby `Hash::make` w Laravelu, który pod spodem również używa `password_hash()`).

Jak nie dać się zmiksować? Najlepiej po prostu używać standardowego bcrypta. Jeżeli jednak jesteśmy zmuszeni do użycia takiej konstrukcji, nie używać bezpośredniego wyjścia z "pre-hasha" (ostatni parametr `true` w funkcji `hash_hmac` - tak standardowo łączy się funkcje kryptograficzne, używając wyjścia `raw`, a nie `encoded`), a np. dodatkowo je przetowrzyć - wykorzystywać wyjście w formie ciągu heksydecymalnego, czy np. z użyciem base64: `password_hash(base64_encode(hash_hmac("sha512", $password, $key, true)), PASSWORD_BCRYPT)`.

Źródła:

> https://www.youtube.com/watch?v=OQD3qDYMyYQ

> https://twitter.com/haveibeenpwned

> https://security.stackexchange.com/questions/234794/is-bcryptstrtolowerhexmd5pass-ok-for-storing-passwords

> https://superuser.com/questions/1561434/how-do-i-crack-a-double-encrypted-hash/1561612#1561612

> https://www.scottbrady91.com/authentication/beware-of-password-shucking

> https://gist.github.com/bigpick/cfa22947c884f7a3fc1431475e345427

> https://blog.ircmaxell.com/2015/03/security-issue-combining-bcrypt-with.html+

> https://tenor.com/view/disperse-lesley-nielsen-explosion-gif-13010485

> https://github.com/illuminate/hashing/blob/master/BcryptHasher.php
