Przejdź do treści

~/cyberbezpieczenstwo cat algorytm-hashowania-z-sola.md

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.

CZCzarek Zawolski--aktualizacja=--czas=8 min--dział=Cyberbezpieczeństwo
Ilustracja zabezpieczania haseł funkcją skrótu z dodaną losową solą
tldr.txt — W skrócie

~ xad tldr algorytm-hashowania-z-sola

  • 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.
$ tree --spis-tresci

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:

  1. System generuje losową sól, np. 16 bajtów z kryptograficznie bezpiecznego generatora (CSPRNG), osobno dla każdego hasła.
  2. 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.
  3. Zapisuje w bazie sól i skrót, zwykle w jednym polu tekstowym.

Przy logowaniu:

  1. System odczytuje z bazy sól i skrót danego użytkownika.
  2. Liczy skrót z zapisanej soli i hasła wpisanego przez użytkownika.
  3. 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ć.

Schemat hashowania hasła z losową solą

Przed czym chroni sól, a przed czym nie

ZagrożenieCzy sól pomaga?Dlaczego
Tęczowe tablice, bazy gotowych skrótówTakTablice musiałyby być policzone osobno dla każdej soli
Identyczne hasła widoczne w bazieTakKażde hasło ma inną sól, więc inny skrót
Łamanie wielu kont jednym obliczeniemTakKażdy kandydat trzeba liczyć osobno dla każdego konta
Atak słownikowy na jedno kontoNieSól jest znana, więc atakujący po prostu ją dokleja
Słabe hasło typu „123456”NieZostanie zgadnięte w pierwszych sekundach
Kradzież hasła przez phishing lub keyloggerNieHasł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:

FunkcjaStatusMinimalne parametry wg OWASP
Argon2idPierwszy wybór dla nowych systemów19 MiB pamięci, 2 iteracje, 1 wątek
scryptDobra alternatywa, gdy Argon2 niedostępnyN=2^17, r=8, p=1
bcryptSprawdzony standard w starszych systemachkoszt co najmniej 10
PBKDF2-HMAC-SHA256Gdy wymagana zgodność z FIPS600 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. Sól i PBKDF2 spotkasz także w protokołach logowania — na nich opiera się uwierzytelnianie SCRAM, używane m.in. przez PostgreSQL i MongoDB.

Ważne: Nie twórz własnej konstrukcji typu sha256(sól + hasło) ani md5(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.

CechaSólPieprz
UnikalnośćInna dla każdego hasłaJedna dla aplikacji
TajnośćJawna, zapisana obok skrótuTajna, poza bazą danych
Chroni przedTęczowymi tablicami, masowym łamaniemŁamaniem po wycieku samej bazy
ObowiązkowaTakOpcjonalna 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 z rand() czy Math.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.

Porównanie skrótów tego samego hasła z różnymi solami

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.

~/narzedzia/hash

$ ./hash --interaktywnie

Generator skrótów SHA

Policz SHA-1, SHA-256, SHA-384 i SHA-512 dowolnego tekstu, opcjonalnie z solą. Zmień jedną literę i zobacz efekt lawinowy.

Sól jest doklejana przed tekstem (sól + tekst). Do przechowywania haseł nie używa się samych funkcji SHA — tylko wolnych algorytmów, takich jak Argon2id czy bcrypt. MD5 nie jest dostępne w Web Crypto i nie powinno być już używane do celów bezpieczeństwa.

~ man faq

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.

Ten artykuł jest częścią tematów

CZ

$ whoami

Czarek Zawolski

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ń.

~ ls ../podobne