password-hash против bcryptjs

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

В экосистеме JavaScript существуют разные подходы к хешированию:

  • простые библиотеки поверх SHA-алгоритмов;
  • специализированные password hashing-решения;
  • адаптивные алгоритмы с защитой от brute-force атак.

Библиотеки password-hash и bcryptjs решают одну задачу — безопасное хранение паролей, но делают это принципиально разными способами.


Архитектурные различия

password-hash

Библиотека password-hash представляет собой лёгкую обёртку над алгоритмом SHA. Она генерирует соль и объединяет её с паролем перед хешированием.

Типичный результат:

sha1$3$5d41402abc4b2a76b9719d911017c592

Особенности:

  • высокая скорость вычислений;
  • минимальные зависимости;
  • простая реализация;
  • поддержка SHA1, SHA256, SHA512.

Главный недостаток — слишком высокая скорость вычисления. Современные видеокарты способны перебирать миллиарды SHA-хешей в секунду.


bcryptjs

bcryptjs — JavaScript-реализация алгоритма bcrypt.

bcrypt был создан специально для хранения паролей и обладает встроенной защитой от:

  • brute-force атак;
  • GPU-ускоренного перебора;
  • массового взлома утекших баз данных.

Пример bcrypt-хеша:

$2a$10$eImiTXuWVxfM37uY4JANjQ==...

Особенности:

  • адаптивная сложность;
  • встроенная соль;
  • намеренно медленные вычисления;
  • устойчивость к современным атакам.

Установка библиотек

Установка password-hash

npm install password-hash

Установка bcryptjs

npm install bcryptjs

Генерация хеша

Хеширование в password-hash

const passwordHash = require('password-hash');

const hashedPassword = passwordHash.generate('myPassword');

console.log(hashedPassword);

Результат:

sha1$3$1d0f3e...

Хеширование в bcryptjs

const bcrypt = require('bcryptjs');

const password = 'myPassword';

const salt = bcrypt.genSaltSync(10);

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

console.log(hash);

Результат:

$2b$10$zL9x7...

Сравнение структуры хеша

Формат password-hash

algorithm$iterations$salt$hash

Пример:

sha256$1000$mysalt$hashvalue

Компоненты:

Часть Назначение
algorithm Используемый SHA-алгоритм
iterations Количество итераций
salt Соль
hash Итоговый хеш

Формат bcrypt

$2b$10$KYVbZ5JFVfqu0oV98LnF5eTk4QTe2e4PQG7QNYfhumEpGdi/867AO

Компоненты:

Часть Назначение
2b Версия bcrypt
10 Cost factor
Остальная часть Соль и хеш

bcrypt хранит все параметры внутри строки хеша.


Проверка пароля

Проверка в password-hash

const passwordHash = require('password-hash');

const hashedPassword = passwordHash.generate('secret');

const isValid = passwordHash.verify('secret', hashedPassword);

console.log(isValid);

Проверка в bcryptjs

const bcrypt = require('bcryptjs');

const hash = bcrypt.hashSync('secret', 10);

const result = bcrypt.compareSync('secret', hash);

console.log(result);

Асинхронная работа

Асинхронный bcryptjs

bcrypt-операции могут быть ресурсоёмкими, поэтому библиотека поддерживает async API.

const bcrypt = require('bcryptjs');

bcrypt.genSalt(10, (err, salt) => {
    bcrypt.hash('password', salt, (err, hash) => {
        console.log(hash);
    });
});

Современный вариант:

const bcrypt = require('bcryptjs');

async function createHash() {
    const salt = await bcrypt.genSalt(10);
    return await bcrypt.hash('password', salt);
}

Асинхронность в password-hash

Библиотека в основном ориентирована на синхронную работу.

const passwordHash = require('password-hash');

const hash = passwordHash.generate('password');

Из-за высокой скорости SHA необходимость в асинхронности практически отсутствует.


Настройка сложности

Cost factor в bcryptjs

Ключевая особенность bcrypt — регулируемая вычислительная сложность.

const hash = bcrypt.hashSync('password', 12);

Число 12 означает количество раундов.

Рост сложности:

Cost Скорость
8 Быстро
10 Стандарт
12 Медленно
14+ Очень медленно

Каждое увеличение на 1 удваивает время вычисления.


Итерации в password-hash

const hash = passwordHash.generate('password', {
    algorithm: 'sha256',
    iterations: 10000
});

Проблема заключается в том, что даже большое количество SHA-итераций остаётся значительно менее безопасным, чем bcrypt.


Безопасность алгоритмов

Уязвимости SHA-подхода

password-hash использует быстрые криптографические функции.

Это приводит к нескольким проблемам:

GPU-атаки

Видеокарты эффективно вычисляют SHA-хеши.

Современные GPU способны выполнять:

  • миллиарды SHA1-хешей в секунду;
  • сотни миллионов SHA256-операций;
  • массовый параллельный перебор.

Радужные таблицы

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


Утечки баз данных

При компрометации БД атакующий может очень быстро проверить огромные словари паролей.


Почему bcrypt безопаснее

bcrypt проектировался именно для защиты паролей.

Медленные вычисления

bcrypt намеренно использует тяжёлые операции.

Это делает:

  • brute-force дорогим;
  • GPU-ускорение менее эффективным;
  • массовые атаки значительно медленнее.

Адаптивность

Рост мощности оборудования компенсируется увеличением cost factor.

Например:

bcrypt.hashSync(password, 14);

Автоматическая соль

bcrypt всегда использует случайную соль.

Разработчику не требуется реализовывать её вручную.


Производительность

Скорость password-hash

SHA-хеширование чрезвычайно быстрое.

Преимущества:

  • низкая нагрузка на CPU;
  • высокая пропускная способность;
  • минимальная задержка.

Недостаток:

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

Скорость bcryptjs

bcrypt существенно медленнее.

Примерные значения:

Cost Время
8 ~20 мс
10 ~80 мс
12 ~300 мс
14 ~1 сек

Точные показатели зависят от оборудования.


Работа с солью

Соль в password-hash

const hash = passwordHash.generate('password', {
    saltLength: 16
});

Можно вручную управлять:

  • длиной соли;
  • алгоритмом;
  • итерациями.

Соль в bcryptjs

bcrypt скрывает детали реализации.

const salt = bcrypt.genSaltSync(10);

Соль автоматически встраивается в итоговый хеш.


Пример авторизации

Реализация через password-hash

const passwordHash = require('password-hash');

function register(password) {
    return passwordHash.generate(password);
}

function login(password, savedHash) {
    return passwordHash.verify(password, savedHash);
}

Реализация через bcryptjs

const bcrypt = require('bcryptjs');

async function register(password) {
    return await bcrypt.hash(password, 12);
}

async function login(password, savedHash) {
    return await bcrypt.compare(password, savedHash);
}

Сравнение API

Возможность password-hash bcryptjs
Простота Очень высокая Высокая
Скорость Очень высокая Средняя
Защита от brute-force Слабая Сильная
Адаптивность Ограниченная Полная
GPU-защита Плохая Хорошая
Асинхронность Ограниченная Полная
Современная актуальность Низкая Высокая

Проблемы password-hash

Устаревшая концепция

Даже SHA512 с большим числом итераций считается недостаточно надёжным для современных систем хранения паролей.


Отсутствие memory-hard механизма

bcrypt использует более сложные внутренние структуры, тогда как SHA-алгоритмы ориентированы только на скорость.


Ограниченная поддержка

Библиотека редко используется в новых production-проектах.


Когда допустим password-hash

Использование возможно:

  • в старых системах;
  • для обратной совместимости;
  • в учебных проектах;
  • для не-критичных данных.

Для production-аутентификации предпочтительнее:

  • bcrypt;
  • argon2;
  • scrypt.

Ограничения bcryptjs

Медленнее нативного bcrypt

bcryptjs реализован полностью на JavaScript.

Нативная библиотека bcrypt обычно быстрее.


Нагрузка на event loop

Синхронные методы блокируют поток Node.js.

Плохо:

bcrypt.hashSync(password, 14);

Лучше:

await bcrypt.hash(password, 14);

Миграция с password-hash на bcryptjs

Типичная стратегия:

  1. Пользователь входит в систему.
  2. Пароль проверяется через старый SHA-хеш.
  3. После успешной проверки пароль немедленно пересчитывается в bcrypt.
  4. Старый хеш заменяется новым.

Пример миграции

const passwordHash = require('password-hash');
const bcrypt = require('bcryptjs');

async function login(password, user) {

    const oldValid = passwordHash.verify(password, user.password);

    if (oldValid) {

        const newHash = await bcrypt.hash(password, 12);

        user.password = newHash;

        return true;
    }

    return await bcrypt.compare(password, user.password);
}

Сравнение устойчивости к атакам

Тип атаки password-hash bcryptjs
Dictionary attack Слабая защита Хорошая защита
GPU brute-force Очень слабая Умеренная
Rainbow tables Частичная Хорошая
Массовый перебор Слабая Сильная

Практические рекомендации

Для новых проектов

Предпочтительный вариант:

bcrypt.hash(password, 12);

Для высоконагруженных систем

Оптимальный баланс:

bcrypt.hash(password, 10);

Для критически важных сервисов

Рекомендуется:

  • bcrypt cost 12–14;
  • rate limiting;
  • MFA;
  • HTTPS;
  • защита от credential stuffing.

Типичные ошибки

Использование hashSync в API

Проблемный код:

app.post('/login', (req, res) => {
    const hash = bcrypt.hashSync(req.body.password, 14);
});

При большом количестве запросов сервер начинает блокироваться.


Слишком маленький cost factor

Небезопасно:

bcrypt.hash(password, 4);

Хранение SHA без соли

Критическая ошибка:

crypto.createHash('sha256')

Без соли одинаковые пароли создают одинаковые хеши.


Итоговое сравнение технологий

Критерий password-hash bcryptjs
Современность Низкая Высокая
Безопасность Ограниченная Высокая
Производительность Очень высокая Контролируемая
Простота интеграции Высокая Высокая
Рекомендуется для production Нет Да
Защита от современных атак Слабая Хорошая
Поддержка индустрией Снижается Широкая