Паролі ніколи не зберігають у відкритому вигляді чи зашифрованими - лише як хеш спеціальної повільної функції з сіллю.
Чому не шифрування: зашифроване можна розшифрувати. Хто отримав ключ разом з базою, отримав усі паролі. Хеш - односторонній: навіть сервер не знає пароля, лише вміє перевірити.
Чому не md5/sha256: вони створені швидкими. Сучасна відеокарта перебирає мільярди хешів SHA-256 за секунду, тож витеклу базу з простими паролями підбирають за години.
Правильні функції - навмисно повільні й налаштовувані:
- Argon2id - рекомендований вибір;
- bcrypt - перевірений, широко підтримуваний (обмеження: використовує лише перші 72 байти пароля).
$hash = password_hash($password, PASSWORD_ARGON2ID); // або PASSWORD_DEFAULT (bcrypt)
if (password_verify($input, $hash)) {
// вхід
}
Hash::make($password); // Laravel: bcrypt за замовчуванням, налаштовується
Hash::check($input, $hash);
Сіль - випадкове значення, унікальне для кожного пароля. Вона робить однакові паролі різними хешами й унеможливлює готові таблиці (rainbow tables). password_hash генерує сіль сам і зберігає її в рядку хешу - окрема колонка не потрібна.
Ще правила:
- Перехешування при вході (
password_needs_rehash, у Laravel - автоматично), коли збільшується вартість чи змінюється алгоритм. - Ліміт спроб входу, щоб онлайн-перебір був непрактичним.
- Перевірка на злиті паролі (Have I Been Pwned, правило
Password::uncompromised()у Laravel) замість вимог «велика літера + цифра + символ».