Проверка пароля в процессе аутентификации с использованием bcrypt.js основана на сравнении введённого пользователем пароля с ранее сохранённым хешем. Важно понимать, что прямое сравнение строк здесь невозможно, поскольку в базе данных хранится не сам пароль, а его криптографическое представление.
Алгоритм bcrypt устроен таким образом, что каждый хеш содержит внутри себя параметры соли и стоимость вычислений (cost factor). Это позволяет выполнять проверку без необходимости отдельно хранить соль.
При аутентификации происходит следующая логика:
Ключевой момент заключается в том, что повторное хеширование введённого пароля не требуется вручную — библиотека самостоятельно извлекает параметры из сохранённого хеша.
Основной метод проверки в bcrypt.js — compare. Он выполняет безопасное сравнение в асинхронном режиме и защищает от атак, связанных с утечкой времени выполнения.
Пример базового использования:
import bcrypt from 'bcryptjs';
const plainPassword = 'user_password_input';
const hashedPassword = '$2a$10$EIXhG3...'; // значение из базы данных
bcrypt.compare(plainPassword, hashedPassword, (err, result) => {
if (err) {
// обработка ошибки
return;
}
if (result) {
// пароль совпадает
} else {
// неверный пароль
}
});
Функция принимает три аргумента:
Результат true означает совпадение, false —
отказ в аутентификации.
Современные приложения чаще используют промисы вместо callback-стиля. bcrypt.js поддерживает такую форму через обёртку:
import bcrypt from 'bcryptjs';
const verifyPassword = async (password, hash) => {
const match = await bcrypt.compare(password, hash);
return match;
};
const isValid = await verifyPassword('user_password_input', storedHash);
Использование async/await делает код более линейным и удобным для интеграции в middleware и сервисные слои.
Библиотека также предоставляет синхронный метод сравнения:
import bcrypt from 'bcryptjs';
const result = bcrypt.compareSync('user_password_input', storedHash);
if (result) {
// доступ разрешён
}
Синхронный вариант блокирует поток выполнения, поэтому его использование оправдано только в скриптах или простых утилитах, но не в серверных приложениях с высокой нагрузкой.
Хеш, создаваемый bcrypt, имеет строго определённую структуру:
$2a$10$..........................................
Разделение компонентов:
При сравнении библиотека:
Обычное строковое сравнение может раскрыть информацию о совпадении символов через разницу во времени выполнения. bcrypt.js решает эту проблему за счёт:
Это делает невозможным определение правильности пароля через измерение задержек.
Типичный поток проверки пароля в серверном приложении выглядит следующим образом:
import bcrypt from 'bcryptjs';
import User from './models/User.js';
const login = async (email, password) => {
const user = await User.findOne({ email });
if (!user) {
return false;
}
const passwordMatch = await bcrypt.compare(password, user.passwordHash);
if (!passwordMatch) {
return false;
}
return true;
};
Логика проверки обычно является частью более крупного процесса:
При работе с bcrypt.compare возможны ошибки, связанные с:
Корректная обработка:
try {
const match = await bcrypt.compare(password, hash);
} catch (error) {
// логирование ошибки
// возврат отказа в доступе
}
Игнорирование ошибок недопустимо, так как может привести к нестабильному поведению системы аутентификации.
bcrypt является алгоритмом с высокой вычислительной сложностью. Это специально сделано для защиты от brute-force атак, но влияет на производительность.
Факторы, влияющие на скорость проверки:
Важно учитывать, что compare всегда быстрее, чем hash, поскольку не требует генерации новой соли и использует уже готовые параметры.
Распространённые проблемы:
1. Попытка сравнивать строки напрямую
if (password === hash) // ошибка логики
2. Повторное хеширование перед сравнением
const newHash = bcrypt.hashSync(password, 10);
bcrypt.compareSync(newHash, storedHash); // некорректный подход
3. Использование разных алгоритмов bcrypt-хеш нельзя сравнивать с SHA, MD5 и другими алгоритмами.
При построении системы аутентификации важно разделять ответственность:
bcrypt.compare должен находиться в сервисном слое, а не в контроллерах, чтобы избежать дублирования логики.
Часто сравнение пароля выполняется даже если пользователь не найден, чтобы снизить риск enumeration атак. В таких случаях используется фиктивный хеш:
const fakeHash = '$2a$10$invalidsaltsaltsaltsalt....';
await bcrypt.compare(password, user?.passwordHash || fakeHash);
Это делает поведение системы одинаковым по времени ответа независимо от существования пользователя.
bcrypt.js поддерживает несколько версий формата:
При сравнении библиотека автоматически определяет формат и корректно обрабатывает его без дополнительной настройки.
В высоконагруженных системах предпочтение отдаётся асинхронной версии compare. Это позволяет:
Синхронные вызовы допустимы только в ограниченных сценариях, например при миграциях или тестировании.
Результат compare не должен сохраняться в логах в явном виде. Логирование должно ограничиваться фактами:
Сохранение паролей, хешей или промежуточных данных недопустимо даже в отладочных режимах.