Проверка пароля при аутентификации

Проверка пароля в процессе аутентификации с использованием bcrypt.js основана на сравнении введённого пользователем пароля с ранее сохранённым хешем. Важно понимать, что прямое сравнение строк здесь невозможно, поскольку в базе данных хранится не сам пароль, а его криптографическое представление.

Алгоритм bcrypt устроен таким образом, что каждый хеш содержит внутри себя параметры соли и стоимость вычислений (cost factor). Это позволяет выполнять проверку без необходимости отдельно хранить соль.

При аутентификации происходит следующая логика:

  • пользователь вводит пароль
  • из базы данных извлекается bcrypt-хеш
  • выполняется сравнение через специальную функцию библиотеки
  • результатом становится булево значение

Ключевой момент заключается в том, что повторное хеширование введённого пароля не требуется вручную — библиотека самостоятельно извлекает параметры из сохранённого хеша.

Использование bcrypt.compare

Основной метод проверки в 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 {
        // неверный пароль
    }
});

Функция принимает три аргумента:

  • строка с введённым паролем
  • сохранённый хеш
  • callback-функция для обработки результата

Результат true означает совпадение, false — отказ в аутентификации.

Promise-ориентированный подход

Современные приложения чаще используют промисы вместо 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-хеша и её роль в проверке

Хеш, создаваемый bcrypt, имеет строго определённую структуру:

$2a$10$..........................................

Разделение компонентов:

  • версия алгоритма
  • cost factor (например, 10)
  • соль
  • итоговый хеш

При сравнении библиотека:

  1. извлекает соль из хеша
  2. повторно вычисляет хеш введённого пароля
  3. сравнивает оба результата через защищённое сравнение

Защита от timing attacks

Обычное строковое сравнение может раскрыть информацию о совпадении символов через разницу во времени выполнения. 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 атак, но влияет на производительность.

Факторы, влияющие на скорость проверки:

  • cost factor при создании хеша
  • нагрузка на процессор
  • количество одновременных запросов

Важно учитывать, что compare всегда быстрее, чем hash, поскольку не требует генерации новой соли и использует уже готовые параметры.

Частые ошибки при использовании сравнения

Распространённые проблемы:

1. Попытка сравнивать строки напрямую

if (password === hash) // ошибка логики

2. Повторное хеширование перед сравнением

const newHash = bcrypt.hashSync(password, 10);
bcrypt.compareSync(newHash, storedHash); // некорректный подход

3. Использование разных алгоритмов bcrypt-хеш нельзя сравнивать с SHA, MD5 и другими алгоритмами.

Рекомендации по архитектуре

При построении системы аутентификации важно разделять ответственность:

  • слой хранения данных (база данных)
  • сервис проверки пароля
  • слой аутентификации (JWT, session)
  • контроллеры HTTP-запросов

bcrypt.compare должен находиться в сервисном слое, а не в контроллерах, чтобы избежать дублирования логики.

Поведение при отсутствии пользователя

Часто сравнение пароля выполняется даже если пользователь не найден, чтобы снизить риск enumeration атак. В таких случаях используется фиктивный хеш:

const fakeHash = '$2a$10$invalidsaltsaltsaltsalt....';

await bcrypt.compare(password, user?.passwordHash || fakeHash);

Это делает поведение системы одинаковым по времени ответа независимо от существования пользователя.

Работа с различными форматами хешей

bcrypt.js поддерживает несколько версий формата:

  • 2a
  • 2b
  • 2y

При сравнении библиотека автоматически определяет формат и корректно обрабатывает его без дополнительной настройки.

Асинхронная модель в серверных приложениях

В высоконагруженных системах предпочтение отдаётся асинхронной версии compare. Это позволяет:

  • не блокировать event loop
  • обрабатывать параллельные запросы
  • масштабировать систему без деградации производительности

Синхронные вызовы допустимы только в ограниченных сценариях, например при миграциях или тестировании.

Безопасное хранение результата проверки

Результат compare не должен сохраняться в логах в явном виде. Логирование должно ограничиваться фактами:

  • успешная аутентификация
  • ошибка входа
  • технические сбои

Сохранение паролей, хешей или промежуточных данных недопустимо даже в отладочных режимах.