Пошук уроків, статей та іншого контенту
Навчимося безпечно зберігати паролі за допомогою хешування, salt і сучасних алгоритмів на кшталт Argon2 та bcrypt.
Пароль — це секрет користувача. Якщо зберігати його безпосередньо в базі даних, то витік бази одразу відкриє всі облікові записи.
Небезпечний приклад:
email: olena@example.com
password: MySecretPassword123Навіть адміністратор бази даних не повинен бачити паролі користувачів. Для цього пароль перед збереженням перетворюють на хеш.
Хешування — це одностороннє перетворення даних у рядок фіксованого формату.
пароль → хешНа відміну від шифрування, хеш не призначений для зворотного перетворення. Під час входу в систему сервер:
отримує пароль від користувача;
хешує його за допомогою того самого алгоритму;
порівнює результат із хешем у базі даних.
Насправді спеціальні алгоритми для паролів порівнюють пароль без необхідності вручну відтворювати хеш. Наприклад, bcrypt.compare() або argon2.verify() самостійно використовують параметри, збережені в хеші.
Криптографічні хеші на кшталт SHA-256 корисні для перевірки цілісності даних, але не є найкращим вибором для паролів.
Вони дуже швидкі. Це добре для звичайних обчислень, але небезпечно для паролів: зловмисник може швидко перевіряти мільйони можливих паролів.
Для паролів використовують спеціальні повільні алгоритми:
Argon2id;
bcrypt;
scrypt;
PBKDF2 за правильних параметрів.
Повільність тут є перевагою: вона ускладнює масовий підбір паролів.
Salt — це випадковий рядок, який додається до пароля перед хешуванням.
Без salt однакові паролі мали б однакові хеші:
користувач 1: password123 → однаковий хеш
користувач 2: password123 → однаковий хешЗавдяки salt хеші відрізняються:
password123 + salt_1 → хеш_1
password123 + salt_2 → хеш_2Salt має такі властивості:
генерується випадково;
повинен бути унікальним для кожного пароля;
не є секретом;
зазвичай зберігається разом із хешем;
не потрібно зберігати в окремому секретному сховищі.
Сучасні бібліотеки генерують salt автоматично та записують його до результату хешування. Тому не потрібно створювати salt власноруч.
Результат роботи bcrypt або Argon2 — це не просто набір випадкових символів. У ньому зазвичай містяться:
назва алгоритму;
параметри складності;
salt;
власне значення хешу.
Наприклад, хеш bcrypt може мати такий вигляд:
$2b$12$...Завдяки цьому під час перевірки бібліотека знає, який алгоритм і параметри потрібно використати.
Хеш можна зберігати в базі даних як звичайний текстовий рядок.
Для Node.js доступна бібліотека bcrypt.
Встановлення:
npm install bcryptПриклад файлу passwords.js:
const bcrypt = require('bcrypt');
const PASSWORD_COST = 12;
async function main() {
const password = 'MyStrongPassword123!';
// Хешуємо пароль. Salt генерується бібліотекою автоматично.
const passwordHash = await bcrypt.hash(password, PASSWORD_COST);
console.log('Хеш для збереження в базі даних:');
console.log(passwordHash);
// Перевіряємо пароль, який ввів користувач під час входу.
const isPasswordCorrect = await bcrypt.compare(
'MyStrongPassword123!',
passwordHash
);
console.log('Правильний пароль:', isPasswordCorrect);
const isWrongPassword = await bcrypt.compare(
'WrongPassword',
passwordHash
);
console.log('Неправильний пароль:', isWrongPassword);
}
main().catch((error) => {
console.error('Сталася помилка:', error);
process.exitCode = 1;
});Запуск:
node passwords.jsОчікуваний результат матиме приблизно такий вигляд:
Хеш для збереження в базі даних:
$2b$12$...
Правильний пароль: true
Неправильний пароль: falseУ реальному застосунку в базу даних потрібно зберігати значення passwordHash, а не змінну password.
Другий аргумент bcrypt.hash() — це параметр складності, який часто називають cost або salt rounds:
const passwordHash = await bcrypt.hash(password, 12);Більше значення означає більше обчислень і повільніше хешування. Це підвищує стійкість до підбору, але збільшує навантаження на сервер.
Параметр потрібно підбирати з урахуванням продуктивності серверів. Не слід бездумно встановлювати надто мале або надто велике значення.
Для кожного входу не потрібно повторно створювати хеш і порівнювати рядки вручну. Використовуйте:
const isValid = await bcrypt.compare(plainPassword, savedHash);Argon2id — сучасний алгоритм для хешування паролів. Він використовує не лише час процесора, а й пам’ять, що ускладнює підбір за допомогою спеціалізованого обладнання.
Встановлення пакета:
npm install argon2Приклад:
const argon2 = require('argon2');
async function main() {
const password = 'MyStrongPassword123!';
// Argon2id автоматично створює випадковий salt.
const passwordHash = await argon2.hash(password, {
type: argon2.argon2id,
});
console.log('Хеш для збереження в базі даних:');
console.log(passwordHash);
// Перевіряємо пароль за параметрами, які містяться в хеші.
const isPasswordCorrect = await argon2.verify(
passwordHash,
'MyStrongPassword123!'
);
console.log('Правильний пароль:', isPasswordCorrect);
}
main().catch((error) => {
console.error('Сталася помилка:', error);
process.exitCode = 1;
});У результаті буде рядок, що починається приблизно так:
$argon2id$...Пакет argon2 автоматично:
генерує salt;
хешує пароль;
записує параметри до результату;
використовує ці параметри під час перевірки.
Під час реєстрації:
const passwordHash = await bcrypt.hash(password, 12);
// У базу даних зберігаємо:
// email і passwordHashПід час входу:
const user = await findUserByEmail(email);
if (!user) {
throw new Error('Неправильний email або пароль');
}
const isValid = await bcrypt.compare(password, user.passwordHash);
if (!isValid) {
throw new Error('Неправильний email або пароль');
}Не повертайте окреме повідомлення на кшталт «такого email не існує». Однакове загальне повідомлення зменшує можливість визначити, які email зареєстровані в системі.
Ніколи не зберігайте паролі у відкритому вигляді.
Не використовуйте для паролів звичайний SHA-256, MD5 або подібні швидкі хеші.
Використовуйте Argon2id або bcrypt.
Не створюйте salt вручну, якщо бібліотека робить це автоматично.
Не зберігайте пароль у логах, URL або повідомленнях про помилки.
Не порівнюйте паролі після самостійного хешування рядків — використовуйте compare() або verify().
Зберігайте хеш у базі даних як текст.
Не показуйте хеш пароля користувачеві.
Вибирайте параметри складності, які відповідають продуктивності вашого сервера.
Небезпечно:
await users.insert({
email,
password,
});Безпечно:
const passwordHash = await bcrypt.hash(password, 12);
await users.insert({
email,
passwordHash,
});Не потрібно створювати один глобальний salt. Кожен виклик bcrypt.hash() або argon2.hash() має генерувати власний salt.
===Такий підхід неправильний:
const hash = await bcrypt.hash(password, 12);
if (hash === savedHash) {
// ...
}Навіть для однакового пароля нове хешування може створити інший результат через новий salt.
Правильно:
const isValid = await bcrypt.compare(password, savedHash);Після реєстрації або входу сервер не повинен повертати пароль чи його хеш у відповіді API.
Результат хешування має зберігатися повністю. Якщо поле бази даних надто коротке і обрізає рядок, перевірка пароля перестане працювати.
Хешування — одностороннє перетворення, яке підходить для зберігання паролів.
Паролі не можна зберігати у відкритому вигляді або шифрувати саморобним способом.
Salt робить хеш кожного пароля унікальним.
Salt не потрібно приховувати або зберігати окремо.
Для паролів використовуйте повільні алгоритми Argon2id або bcrypt.
Для перевірки застосовуйте argon2.verify() або bcrypt.compare().
У базі даних має зберігатися лише хеш пароля.