Асинхронная проверка пароля: bcrypt.compare

Общий принцип проверки пароля

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

Функция bcrypt.compare реализует этот процесс асинхронно, что особенно важно для серверных приложений на Node.js, где блокировка event loop приводит к деградации производительности.


Сигнатура bcrypt.compare

Основной вариант использования:

bcrypt.compare(data, encrypted, callback)

Где:

  • data — пароль в открытом виде (строка, введённая пользователем)
  • encrypted — сохранённый bcrypt-хэш из базы данных
  • callback — функция обратного вызова (err, result)

Возвращаемое значение при отсутствии callback — Promise<boolean>.


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

Асинхронная версия bcrypt.compare использует неблокирующие операции, выполняемые в отдельном потоке (thread pool). Это критично, так как сам процесс проверки включает вычислительно тяжёлую операцию повторного хэширования с учётом salt и cost factor.

Пример с callback

bcrypt.compare(password, hash, (err, result) => {
  if (err) {
    console.error('Ошибка при проверке пароля:', err);
    return;
  }

  if (result) {
    console.log('Пароль корректный');
  } else {
    console.log('Пароль неверный');
  }
});

Пример с Promise

bcrypt.compare(password, hash)
  .then(result => {
    if (result) {
      console.log('Доступ разрешён');
    } else {
      console.log('Неверный пароль');
    }
  })
  .catch(err => {
    console.error('Ошибка проверки:', err);
  });

Пример с async/await

async function checkPassword(password, hash) {
  try {
    const match = await bcrypt.compare(password, hash);

    if (match) {
      console.log('Авторизация успешна');
    } else {
      console.log('Неверный пароль');
    }
  } catch (err) {
    console.error('Ошибка при сравнении:', err);
  }
}

Внутренний механизм сравнения

Алгоритм проверки включает несколько этапов:

  1. Извлечение salt и cost factor из хэша
  2. Повторное хэширование входного пароля с использованием этих параметров
  3. Побайтовое сравнение полученного результата с сохранённым хэшем

Важно, что сравнение реализовано через безопасную функцию, предотвращающую утечки через timing attack.


Защита от атак по времени

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

Это означает, что:

  • одинаковые и разные пароли обрабатываются за одинаковое время
  • невозможно определить частичное совпадение хэша через измерение задержек

Promise против callback

Асинхронная версия bcrypt.compare поддерживает обе модели.

Callback-стиль

  • используется в старом коде
  • сложнее масштабируется при цепочках операций

Promise / async-await

  • предпочтительный современный подход
  • упрощает обработку ошибок
  • улучшает читаемость кода

Производительность и нагрузка

Асинхронная проверка не блокирует основной поток, однако сама операция остаётся ресурсоёмкой. Влияние определяется параметром cost factor, заданным при генерации хэша.

Чем выше cost factor:

  • тем медленнее сравнение
  • тем выше устойчивость к перебору
  • тем больше нагрузка на CPU

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


Типичные ошибки при использовании

Несовпадение типов данных

bcrypt.compare(123456, hash)

Пароль должен быть строкой. Передача чисел или null может приводить к некорректным результатам.


Сравнение с неправильным хэшем

bcrypt.compare(password, 'plain-text')

Второй аргумент должен быть валидным bcrypt-хэшем. В противном случае результат всегда будет false или возникнет ошибка.


Игнорирование ошибок

bcrypt.compare(password, hash, (err, result) => {
  if (result) {
    // ошибка не обработана
  }
});

Ошибки криптографического уровня (повреждённый хэш, некорректный формат) должны обрабатываться отдельно от результата сравнения.


Безопасная интеграция в авторизацию

При использовании bcrypt.compare в системах логина важно учитывать:

  • не раскрывать разницу между «неверный пароль» и «пользователь не найден»
  • избегать логирования введённых паролей
  • использовать единый ответ для всех неуспешных попыток

Пример типового поведения:

const user = await db.findUser(username);

if (!user) {
  return false;
}

const isValid = await bcrypt.compare(password, user.passwordHash);

return isValid;

Особенности работы с базами данных

Хэш bcrypt обычно хранится в поле фиксированной длины (60 символов). При извлечении из базы важно:

  • не обрезать строку
  • сохранять полный хэш
  • учитывать возможные кодировки (UTF-8)

Сравнение с синхронной версией

Существует bcrypt.compareSync, однако её использование в серверной среде приводит к блокировке event loop.

Асинхронная версия предпочтительна, так как:

  • не блокирует обработку запросов
  • масштабируется под нагрузкой
  • соответствует архитектуре Node.js

Поведение при высокой нагрузке

При большом количестве одновременных вызовов bcrypt.compare:

  • используется пул потоков libuv
  • возможны очереди выполнения
  • увеличивается latency ответов

Оптимизация обычно включает:

  • снижение cost factor
  • кэширование сессий после успешной проверки
  • ограничение частоты попыток входа

Криптографическая устойчивость сравнения

bcrypt.combine:

  • соль уникальна для каждого хэша
  • результат сравнения зависит от полного набора параметров
  • невозможна атака с предвычислением rainbow tables

Асинхронная проверка сохраняет все эти свойства, так как использует тот же алгоритм, что и генерация хэша.