Node.jsМодуль 6: Безопасность
Хеширование паролей
bcrypt, argon2, salt
Цель урока
В этом уроке ты научишься:
- Понимать зачем хешировать пароли
- Использовать bcrypt
- Работать с argon2
Зачем хешировать
Никогда не храни пароли в открытом виде!
При утечке базы данных все пароли будут скомпрометированы.
Хеширование — односторонняя функция: из пароля можно получить хеш, но из хеша нельзя получить пароль.
bcrypt
Установка
npm install bcryptХеширование
const bcrypt = require('bcrypt');
// Хеширование
const password = 'mypassword123';
const saltRounds = 10;
const hash = await bcrypt.hash(password, saltRounds);
// $2b$10$N9qo8uLOickgx2ZMRZoMy...
// Проверка
const isMatch = await bcrypt.compare('mypassword123', hash);
// trueВ модели Mongoose
const userSchema = new mongoose.Schema({
email: String,
password: {
type: String,
required: true,
select: false // Не возвращать по умолчанию
}
});
// Хешируем перед сохранением
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 10);
next();
});
// Метод для проверки пароля
userSchema.methods.comparePassword = async function(candidatePassword) {
return bcrypt.compare(candidatePassword, this.password);
};
const User = mongoose.model('User', userSchema);Использование
// Регистрация
app.post('/register', async (req, res) => {
const { email, password } = req.body;
const user = await User.create({ email, password });
// Пароль автоматически хешируется
res.status(201).json({ id: user._id, email: user.email });
});
// Логин
app.post('/login', async (req, res) => {
const { email, password } = req.body;
const user = await User.findOne({ email }).select('+password');
if (!user || !await user.comparePassword(password)) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Генерация токена...
res.json({ token: '...' });
});argon2 (рекомендуется)
Более современный и безопасный алгоритм.
Установка
npm install argon2Использование
const argon2 = require('argon2');
// Хеширование
const hash = await argon2.hash(password, {
type: argon2.argon2id, // Рекомендуемый тип
memoryCost: 2 ** 16, // 64MB
timeCost: 3,
parallelism: 1
});
// Проверка
const isMatch = await argon2.verify(hash, password);В модели
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
this.password = await argon2.hash(this.password);
next();
});
userSchema.methods.comparePassword = async function(candidatePassword) {
return argon2.verify(this.password, candidatePassword);
};Сравнение
| Критерий | bcrypt | argon2 |
|---|---|---|
| Безопасность | Высокая | Очень высокая |
| Скорость | Средняя | Настраиваемая |
| Память | Фиксированная | Настраиваемая |
| Стандарт | Широко используется | Победитель PHC |
Salt
Salt — случайные данные, добавляемые к паролю перед хешированием.
// bcrypt автоматически генерирует salt
const hash = await bcrypt.hash(password, 10);
// 10 — количество раундов (cost factor)
// Salt встроен в хеш
// $2b$10$N9qo8uLOickgx2ZMRZoMy...
// $2b$ — версия
// 10$ — cost factor
// N9qo8uLOickgx2ZMRZoMy — salt (22 символа)
// ... — сам хешПрактика
Задание: Смена пароля
Задача: Реализуй endpoint для смены пароля.
Решение:
app.post('/change-password', authenticate, async (req, res) => {
const { currentPassword, newPassword } = req.body;
const user = await User.findById(req.user.id).select('+password');
// Проверяем текущий пароль
if (!await user.comparePassword(currentPassword)) {
return res.status(400).json({ error: 'Current password is incorrect' });
}
// Устанавливаем новый пароль
user.password = newPassword;
await user.save(); // Хеширование в pre-save hook
res.json({ message: 'Password changed' });
});Проверь себя
- Почему нельзя хранить пароли в открытом виде?
- Что такое salt?
- Чем argon2 лучше bcrypt?