Многофакторная аутентификация и роль хеширования паролей в ней

Многофакторная аутентификация (MFA) строится на сочетании независимых факторов, относящихся к разным категориям:

  • Знание — пароль, PIN-код
  • Обладание — устройство (смартфон, аппаратный токен)
  • Биометрия — отпечаток пальца, распознавание лица

Классическая схема включает пароль как базовый фактор и дополнительный механизм подтверждения (одноразовый код, push-уведомление, аппаратный ключ). Несмотря на добавление новых уровней защиты, пароль остаётся фундаментом, а значит, его хранение и обработка должны быть максимально безопасными.

Уязвимости паролей и требования к хранению

Пароли подвержены множеству атак:

  • Brute-force — перебор всех возможных комбинаций
  • Dictionary attack — использование словарей популярных паролей
  • Credential stuffing — использование утекших паролей с других сервисов

Хранение паролей в открытом виде или с использованием быстрых хеш-функций (например, SHA-256 без соли) делает систему уязвимой при утечке базы данных.

Требования к безопасному хранению:

  • Использование адаптивных хеш-функций
  • Добавление соли (salt) к каждому паролю
  • Возможность регулировки стоимости вычисления

bcrypt.js как инструмент безопасного хеширования

Библиотека bcrypt.js реализует алгоритм bcrypt на чистом JavaScript и применяется для:

  • Хеширования паролей перед сохранением
  • Проверки введённого пароля при аутентификации

Ключевые свойства bcrypt:

  • Встроенная генерация соли
  • Регулируемая сложность через параметр cost factor
  • Устойчивость к атакам с использованием GPU/ASIC благодаря медленной работе

Пример хеширования:

const bcrypt = require('bcryptjs');

const password = 'user_password';
const saltRounds = 12;

const hash = bcrypt.hashSync(password, saltRounds);

Пример проверки:

const isMatch = bcrypt.compareSync('user_password', hash);

Роль хеширования в MFA

В контексте MFA хеширование паролей выполняет несколько критически важных функций:

1. Защита базового фактора

Даже при наличии второго фактора пароль остаётся первой линией защиты. Его компрометация снижает общую безопасность системы. bcrypt минимизирует ущерб от утечки базы:

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

2. Независимость факторов

Факторы MFA должны быть независимыми. Хеширование обеспечивает, что:

  • сервер не хранит пароль в явном виде
  • компрометация сервера не даёт прямого доступа к фактору знания

3. Сопротивление массовым атакам

Даже если атакующий получает доступ к хешам:

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

Интеграция bcrypt.js в MFA-процесс

Типичный процесс аутентификации с MFA:

  1. Пользователь вводит логин и пароль

  2. Сервер:

    • извлекает хеш пароля
    • проверяет его через bcrypt
  3. При успешной проверке:

    • инициируется второй фактор (OTP, push, WebAuthn)
  4. Доступ предоставляется только после подтверждения второго фактора

Кодовая схема:

const bcrypt = require('bcryptjs');

function authenticate(passwordInput, storedHash) {
    if (!bcrypt.compareSync(passwordInput, storedHash)) {
        return false;
    }

    return true; // переход ко второму фактору
}

Выбор параметра cost factor

Параметр saltRounds определяет сложность хеширования:

  • 10–12 — стандартный уровень
  • 12–14 — повышенная безопасность
  • выше 14 — может замедлить систему

Баланс достигается между:

  • временем отклика сервера
  • устойчивостью к перебору

Рекомендация: выбирать значение, при котором хеширование занимает 100–500 мс на сервере.

Атаки и ограничения MFA

Наличие MFA не устраняет необходимость защищённого хеширования:

  • Phishing может перехватить пароль и одноразовый код
  • Man-in-the-middle может обойти второй фактор
  • SIM swapping компрометирует SMS-коды

При этом слабое хеширование делает систему ещё более уязвимой:

  • утечка базы + слабый хеш → быстрый взлом паролей
  • даже с MFA атакующий может подготовить targeted-атаку

Усиление безопасности паролей в MFA

Дополнительные меры:

  • Ограничение количества попыток входа
  • Использование rate limiting
  • Хранение истории паролей
  • Проверка на утечки (например, через базы скомпрометированных паролей)

bcrypt остаётся ядром этих механизмов, обеспечивая безопасное хранение.

Асинхронное использование bcrypt.js

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

bcrypt.hash(password, saltRounds, (err, hash) => {
    // сохранение хеша
});

bcrypt.compare(passwordInput, storedHash, (err, result) => {
    // проверка
});

Или через Promises:

const hash = await bcrypt.hash(password, 12);
const match = await bcrypt.compare(passwordInput, hash);

Это предотвращает блокировку event loop в Node.js.

Связь с современными методами MFA

Даже при использовании современных подходов:

  • WebAuthn
  • FIDO2
  • Passkeys

пароли продолжают использоваться как fallback-механизм. bcrypt обеспечивает:

  • безопасную деградацию системы
  • защиту legacy-аутентификации

Практические ошибки

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

  • Использование слишком малого saltRounds
  • Повторное хеширование уже захешированного пароля
  • Сравнение строк вместо bcrypt.compare
  • Хранение пароля до хеширования в логах

Корректная реализация требует строгого соблюдения модели:

  • пароль → bcrypt → хеш → хранение
  • ввод → bcrypt.compare → результат

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

bcrypt — намеренно медленный алгоритм. При масштабировании:

  • используется очередь задач
  • вынос хеширования в worker threads
  • кэширование сессий после успешной MFA

Это позволяет сохранить безопасность без деградации UX.

Итеративное усиление безопасности

bcrypt позволяет увеличивать cost factor со временем:

  • при следующем входе пароль можно перехешировать
  • система постепенно повышает уровень защиты

Пример:

if (bcrypt.getRounds(storedHash) < 12) {
    const newHash = await bcrypt.hash(passwordInput, 12);
    // обновить в базе
}

Такой подход особенно важен в долгоживущих системах с MFA.