Hashowanie z solą — jak działa sól kryptograficzna i jak bezpiecznie przechowywać hasła
Czym jest hashowanie z solą, przed czym chroni sól, a przed czym nie, i dlaczego do haseł używa się Argon2id lub bcrypt zamiast SHA-256. Przykłady w PHP i Pythonie.

W skrócie
- Sól to losowa wartość dodawana do hasła przed hashowaniem, unikalna dla każdego hasła i zapisywana jawnie obok skrótu.
- Dzięki soli te same hasła mają różne skróty, a gotowe tęczowe tablice przestają działać — atakujący musi łamać każde konto osobno.
- Sól nie spowalnia ataku słownikowego na pojedyncze hasło, dlatego do haseł używa się wolnych funkcji: Argon2id, scrypt, bcrypt lub PBKDF2, a nie samego SHA-256.
- W praktyce nie implementujesz soli ręcznie: password_hash() w PHP czy argon2-cffi w Pythonie generują ją same i zapisują w wynikowym ciągu.
- Pieprz (pepper) to dodatkowy sekret trzymany poza bazą danych — uzupełnienie soli, nie jej zamiennik.
Spis treści
Hashowanie z solą polega na tym, że przed obliczeniem skrótu hasła system dokleja do niego losowy ciąg znaków — sól — wygenerowany osobno dla każdego hasła. Dzięki temu dwa identyczne hasła dają zupełnie różne skróty, a atakujący, który wykradnie bazę, nie może użyć gotowych tablic skrótów i musi łamać każde konto osobno.
Sól to jednak tylko połowa sukcesu. Sama nie spowalnia zgadywania pojedynczego hasła, dlatego w nowoczesnych systemach łączy się ją z funkcjami celowo wolnymi, takimi jak Argon2id czy bcrypt. Poniżej wyjaśniam, jak to działa, przed czym chroni, a przed czym nie, i jak wdrożyć to poprawnie w kilku linijkach kodu.
Czym jest hashowanie haseł i dlaczego samo nie wystarcza
Funkcja skrótu kryptograficznego zamienia dowolne dane na ciąg o stałej długości. Jest jednokierunkowa: ze skrótu nie da się wyliczyć danych wejściowych. Dlatego serwisy nie przechowują haseł, tylko ich skróty. Przy logowaniu liczą skrót z tego, co wpisałeś, i porównują z zapisanym.
Problem polega na tym, że ta sama funkcja dla tego samego hasła zawsze zwraca ten sam wynik. Łatwo to sprawdzić w terminalu:
echo -n "Wiosna2026" | sha256sum
echo -n "Wiosna2026" | sha256sum
Oba polecenia wypiszą identyczny skrót. Z tej przewidywalności wynikają trzy ataki:
- Tęczowe tablice i bazy gotowych skrótów — atakujący raz liczy skróty milionów popularnych haseł, a potem tylko wyszukuje je w wykradzionej bazie.
- Wykrywanie identycznych haseł — jeśli tysiąc użytkowników ma ten sam skrót, wiadomo, że mają to samo (pewnie słabe) hasło.
- Masowe łamanie — jedno obliczenie skrótu kandydata „sprawdza” go jednocześnie dla wszystkich kont w bazie.
Jak działa sól: rejestracja i logowanie krok po kroku
Sól rozwiązuje wszystkie trzy problemy jednocześnie. Proces wygląda tak.
Przy ustawianiu hasła:
- System generuje losową sól, np. 16 bajtów z kryptograficznie bezpiecznego generatora (CSPRNG), osobno dla każdego hasła.
- Liczy skrót z połączenia soli i hasła:
hash(sól + hasło)— w praktyce przez wolną funkcję, o której niżej. - Zapisuje w bazie sól i skrót, zwykle w jednym polu tekstowym.
Przy logowaniu:
- System odczytuje z bazy sól i skrót danego użytkownika.
- Liczy skrót z zapisanej soli i hasła wpisanego przez użytkownika.
- Porównuje wynik z zapisanym skrótem w czasie stałym (tak, aby czas porównania nie zdradzał, ile znaków się zgadza).
Efekt widać na przykładzie z Linuksa. Polecenie openssl passwd z jawnie podaną solą tworzy skrót w formacie używanym w /etc/shadow:
openssl passwd -6 -salt Ab3kQ9 Wiosna2026
openssl passwd -6 -salt Zx81Lm Wiosna2026
To samo hasło z dwiema różnymi solami daje dwa zupełnie różne wyniki w formacie $6$sól$skrót. Widać też, że sól jest zapisana jawnie — i tak ma być.

Przed czym chroni sól, a przed czym nie
| Zagrożenie | Czy sól pomaga? | Dlaczego |
|---|---|---|
| Tęczowe tablice, bazy gotowych skrótów | Tak | Tablice musiałyby być policzone osobno dla każdej soli |
| Identyczne hasła widoczne w bazie | Tak | Każde hasło ma inną sól, więc inny skrót |
| Łamanie wielu kont jednym obliczeniem | Tak | Każdy kandydat trzeba liczyć osobno dla każdego konta |
| Atak słownikowy na jedno konto | Nie | Sól jest znana, więc atakujący po prostu ją dokleja |
| Słabe hasło typu „123456” | Nie | Zostanie zgadnięte w pierwszych sekundach |
| Kradzież hasła przez phishing lub keylogger | Nie | Hasło przechwycono przed hashowaniem |
Najważniejszy wiersz to atak słownikowy na pojedyncze konto. Narzędzia takie jak Hashcat na jednej współczesnej karcie graficznej liczą miliardy skrótów SHA-256 na sekundę. Sól w niczym tu nie przeszkadza, bo atakujący ma ją w wykradzionej bazie. Dlatego potrzebny jest drugi element: funkcja, która jest wolna z założenia.
Sól to za mało: wybierz wolną funkcję (Argon2id, scrypt, bcrypt, PBKDF2)
Funkcje SHA-256 czy SHA-512 zaprojektowano do szybkiego liczenia sum kontrolnych dużych plików. Przy hasłach szybkość jest wadą. Funkcje do przechowywania haseł mają regulowany koszt — liczbę iteracji, a w nowszych także ilość wymaganej pamięci RAM — co sprawia, że jedna próba trwa np. kilkadziesiąt milisekund. Dla użytkownika to niezauważalne, dla atakującego oznacza tysiące razy mniej prób na sekundę.
Wszystkie poniższe funkcje same generują i zapisują sól. Zalecenia według OWASP Password Storage Cheat Sheet:
| Funkcja | Status | Minimalne parametry wg OWASP |
|---|---|---|
| Argon2id | Pierwszy wybór dla nowych systemów | 19 MiB pamięci, 2 iteracje, 1 wątek |
| scrypt | Dobra alternatywa, gdy Argon2 niedostępny | N=2^17, r=8, p=1 |
| bcrypt | Sprawdzony standard w starszych systemach | koszt co najmniej 10 |
| PBKDF2-HMAC-SHA256 | Gdy wymagana zgodność z FIPS | 600 000 iteracji |
Argon2id i scrypt są „memory-hard”: wymagają dużo pamięci, co ogranicza równoległe łamanie na kartach graficznych i specjalizowanych układach. bcrypt ma ograniczenie długości hasła do 72 bajtów — dłuższe hasła są obcinane, o czym trzeba pamiętać przy bardzo długich frazach.
Ważne: Nie twórz własnej konstrukcji typu
sha256(sól + hasło)animd5(md5(hasło) + sól). To wciąż szybkie funkcje. Użyj gotowej, przetestowanej biblioteki z jedną z funkcji z tabeli.
Jak wdrożyć hashowanie z solą w PHP, Pythonie i Node.js
W praktyce nie musisz ręcznie generować soli ani jej osobno przechowywać. Biblioteki zwracają jeden ciąg zawierający identyfikator algorytmu, parametry, sól i skrót.
PHP: password_hash() i password_verify()
<?php
// Rejestracja: sól generowana automatycznie
$hash = password_hash($haslo, PASSWORD_DEFAULT);
// albo jawnie Argon2id (jeśli PHP ma wkompilowaną obsługę)
$hash = password_hash($haslo, PASSWORD_ARGON2ID);
// Logowanie
if (password_verify($haslo, $hashZBazy)) {
// Aktualizacja skrótu, gdy zmieniono algorytm lub koszt
if (password_needs_rehash($hashZBazy, PASSWORD_DEFAULT)) {
$nowyHash = password_hash($haslo, PASSWORD_DEFAULT);
// zapisz $nowyHash w bazie
}
}
PASSWORD_DEFAULT oznacza obecnie bcrypt (od PHP 8.4 z domyślnym kosztem 12). Wynik ma postać $2y$12$..., gdzie po parametrach następuje 22 znaki soli i właściwy skrót. Kolumna w bazie powinna mieć co najmniej 255 znaków, bo domyślny algorytm może się w przyszłości zmienić. Więcej o samym języku przeczytasz we wprowadzeniu do PHP.
Python: argon2-cffi
pip install argon2-cffi
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
ph = PasswordHasher() # Argon2id, sól 16 bajtów generowana automatycznie
hash_ = ph.hash("Wiosna2026")
# np. $argon2id$v=19$m=65536,t=3,p=4$<sól>$<skrót>
try:
ph.verify(hash_, "Wiosna2026")
if ph.check_needs_rehash(hash_):
hash_ = ph.hash("Wiosna2026") # zapisz nowy skrót
except VerifyMismatchError:
print("Nieprawidłowe hasło")
W samej bibliotece standardowej Pythona masz hashlib.scrypt() i hashlib.pbkdf2_hmac(), ale wtedy sól (os.urandom(16)) i parametry musisz przechowywać samodzielnie, a do porównania użyć hmac.compare_digest().
Node.js: wbudowany scrypt
const { scryptSync, randomBytes, timingSafeEqual } = require('node:crypto');
function hashPassword(password) {
const salt = randomBytes(16);
const key = scryptSync(password, salt, 64, { N: 2 ** 17, r: 8, p: 1, maxmem: 256 * 1024 * 1024 });
return `${salt.toString('hex')}:${key.toString('hex')}`;
}
function verifyPassword(password, stored) {
const [saltHex, keyHex] = stored.split(':');
const key = scryptSync(password, Buffer.from(saltHex, 'hex'), 64, { N: 2 ** 17, r: 8, p: 1, maxmem: 256 * 1024 * 1024 });
return timingSafeEqual(key, Buffer.from(keyHex, 'hex'));
}
W produkcji lepiej użyć asynchronicznej wersji crypto.scrypt() albo popularnych pakietów argon2 lub bcrypt, żeby nie blokować pętli zdarzeń.
Sól a pieprz (pepper): czym się różnią
Pieprz to dodatkowy sekret, wspólny dla całej aplikacji i przechowywany poza bazą danych — w zmiennej środowiskowej, menedżerze sekretów albo module HSM. Typowo stosuje się go jako klucz HMAC przed właściwym hashowaniem albo szyfruje się nim gotowe skróty.
| Cecha | Sól | Pieprz |
|---|---|---|
| Unikalność | Inna dla każdego hasła | Jedna dla aplikacji |
| Tajność | Jawna, zapisana obok skrótu | Tajna, poza bazą danych |
| Chroni przed | Tęczowymi tablicami, masowym łamaniem | Łamaniem po wycieku samej bazy |
| Obowiązkowa | Tak | Opcjonalna warstwa dodatkowa |
Pieprz ma sens, gdy atakujący może wykraść bazę (np. przez SQL injection), ale nie ma dostępu do serwera aplikacji. Wymaga jednak przemyślenia rotacji klucza — jego utrata oznacza, że nikt nie zaloguje się starym hasłem.
Najczęstsze błędy przy soleniu haseł
- Jedna sól dla wszystkich użytkowników. To w praktyce pieprz bez tajności — identyczne hasła znów mają identyczne skróty.
- Sól z nazwy użytkownika lub adresu e-mail. Jest przewidywalna i powtarzalna między serwisami. Sól ma pochodzić z generatora CSPRNG (
random_bytes(),os.urandom(),crypto.randomBytes()), nie zrand()czyMath.random(). - Brak nowej soli przy zmianie hasła. Każde nowe hasło powinno dostać nową sól — biblioteki robią to automatycznie.
- Porównywanie skrótów operatorem
==. W PHP grozi to błędami porównań typów, a ogólnie wyciekiem informacji przez czas odpowiedzi. Używaj funkcji weryfikujących z biblioteki. - Szyfrowanie zamiast hashowania. Szyfrowanie symetryczne jest odwracalne: kto zdobędzie klucz, odczyta wszystkie hasła. Haseł użytkowników nie szyfruje się, tylko hashuje.

Co z tego wynika dla zwykłego użytkownika
Jako użytkownik nie masz wpływu na to, czy serwis soli hasła i jakiej funkcji używa. Masz za to wpływ na to, czy Twoje hasło da się zgadnąć słownikiem: nawet najlepszy Argon2id nie ochroni hasła „Kasia1990”. Używaj długich, unikalnych haseł z menedżera haseł — praktyczne sposoby opisuję w poradniku jak stworzyć i zapamiętać bezpieczne hasło — i włącz uwierzytelnianie dwuskładnikowe tam, gdzie się da.
Jeśli projektujesz system logowania, lista kontrolna jest krótka: Argon2id (lub bcrypt/scrypt) z gotowej biblioteki, automatyczna sól, kolumna na skrót o długości co najmniej 255 znaków, mechanizm „rehash” przy logowaniu i ograniczenie liczby prób logowania. Wtedy nawet wyciek bazy nie oznacza natychmiastowej kompromitacji haseł użytkowników.
Najczęściej zadawane pytania
Co to jest sól w kryptografii?
Sól to losowy ciąg bajtów generowany osobno dla każdego hasła i dołączany do niego przed obliczeniem skrótu. Nie jest tajna — przechowuje się ją razem z hashem, bo jest potrzebna przy weryfikacji logowania.
Czy sól musi być tajna?
Nie. Sól ma zapewnić unikalność skrótów, a nie stanowić sekret. Tajnym dodatkiem jest pieprz (pepper), przechowywany poza bazą danych, np. w konfiguracji serwera lub module HSM.
Czy SHA-256 z solą wystarczy do przechowywania haseł?
Nie. SHA-256 jest bardzo szybki, więc karta graficzna sprawdza miliardy kandydatów na sekundę, nawet z solą. Do haseł używaj funkcji celowo spowolnionych: Argon2id, scrypt, bcrypt albo PBKDF2 z dużą liczbą iteracji.
Jak długa powinna być sól?
NIST wymaga co najmniej 32 bitów, ale w praktyce stosuje się 16 bajtów (128 bitów) z kryptograficznie bezpiecznego generatora liczb losowych. Biblioteki takie jak password_hash() dobierają długość automatycznie.
Czym różni się hashowanie od szyfrowania?
Szyfrowanie jest odwracalne — z kluczem odzyskasz oryginał. Hashowanie jest jednokierunkowe: ze skrótu nie da się wyliczyć hasła, można tylko sprawdzić, czy podane hasło daje ten sam skrót.
Autor
Założyciel i redaktor XAD.pl. Pisze o sieciach, bezpieczeństwie IT, administracji systemami Windows i Linux oraz o sprzęcie, który sprawia ludziom problemy na co dzień.


