password-hash против crypto модуля Node.js напрямую

Архитектурная разница

Библиотека password-hash представляет собой высокоуровневую абстракцию для хранения паролей. Модуль crypto в Node.js — низкоуровневый криптографический инструмент общего назначения.

Главное различие заключается в уровне ответственности:

Подход Назначение
password-hash Готовое решение для безопасного хранения паролей
crypto Набор криптографических примитивов

При использовании password-hash разработчик работает с API, ориентированным именно на пароли:

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

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

const isValid = passwordHash.verify(
    'secret-password',
    hash
);

При использовании crypto необходимо самостоятельно проектировать:

  • алгоритм хеширования;
  • генерацию соли;
  • формат хранения;
  • кодирование;
  • защиту от ошибок;
  • параметры безопасности.

Пример через crypto:

const crypto = require('crypto');

function hashPassword(password) {
    const salt = crypto.randomBytes(32).toString('hex');

    const hash = crypto
        .pbkdf2Sync(password, salt, 100000, 64, 'sha512')
        .toString('hex');

    return `${salt}:${hash}`;
}

Разница в объёме инфраструктурного кода становится особенно заметной в крупных проектах.


Что делает password-hash

Библиотека автоматизирует несколько критически важных задач:

  • генерацию соли;
  • выбор безопасного алгоритма;
  • формирование итоговой строки;
  • проверку пароля;
  • управление параметрами хеширования.

Результат выглядит примерно так:

sha1$3f6a9f$1$8f7d...

Внутри строки уже содержатся:

  • алгоритм;
  • соль;
  • параметры;
  • итоговый хеш.

Разработчику не требуется самостоятельно проектировать формат хранения.


Проблема прямого использования crypto

Ошибки проектирования

Большинство уязвимостей возникает не из-за плохой криптографии, а из-за неправильного использования примитивов.

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

Использование SHA256 напрямую

Неправильно:

const hash = crypto
    .createHash('sha256')
    .update(password)
    .digest('hex');

Причины небезопасности:

  • отсутствие соли;
  • высокая скорость вычислений;
  • уязвимость к brute-force;
  • уязвимость к rainbow tables.

Использование одинаковой соли

const salt = 'static-salt';

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

Слишком маленькое число итераций

crypto.pbkdf2Sync(password, salt, 1000, 64, 'sha512');

Современные значения обычно начинаются от десятков или сотен тысяч итераций.

Хранение соли отдельно без стандарта

{
    passwordHash: '...',
    salt: '...'
}

Подобная схема быстро превращается в источник несовместимостей между сервисами.


Почему password-hash безопаснее по умолчанию

Высокоуровневая библиотека минимизирует вероятность критических ошибок.

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

const hash = passwordHash.generate(password);

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

Встроенная структура

Строка уже содержит:

  • тип алгоритма;
  • параметры;
  • соль;
  • хеш.

Не требуется создавать собственный формат.

Проверка через API

passwordHash.verify(password, hash);

Разработчик не сравнивает хеши вручную.


Что происходит внутри crypto

Модуль Node.js предоставляет доступ к:

  • SHA;
  • HMAC;
  • PBKDF2;
  • RSA;
  • AES;
  • ECDH;
  • случайным числам;
  • сертификатам;
  • потоковому шифрованию.

Это мощный фундаментальный инструмент.

Пример генерации случайной соли:

const salt = crypto.randomBytes(16);

Пример PBKDF2:

crypto.pbkdf2Sync(
    password,
    salt,
    100000,
    64,
    'sha512'
);

Пример HMAC:

crypto.createHmac('sha256', key);

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

Поэтому безопасность полностью зависит от архитектуры приложения.


Скорость разработки

password-hash

Минимальный код:

const hash = passwordHash.generate(password);

Проверка:

passwordHash.verify(password, hash);

Подходит для:

  • CRUD-приложений;
  • админ-панелей;
  • REST API;
  • MVP;
  • внутренних сервисов.

crypto

Требуется самостоятельно реализовать:

  • соль;
  • формат хранения;
  • алгоритм;
  • миграции;
  • параметры;
  • совместимость.

Пример полноценной схемы:

const crypto = require('crypto');

function hashPassword(password) {
    const iterations = 120000;
    const keyLength = 64;
    const digest = 'sha512';

    const salt = crypto
        .randomBytes(32)
        .toString('hex');

    const hash = crypto
        .pbkdf2Sync(
            password,
            salt,
            iterations,
            keyLength,
            digest
        )
        .toString('hex');

    return [
        digest,
        iterations,
        salt,
        hash
    ].join('$');
}

Гибкость crypto

Несмотря на сложность, низкоуровневый подход обладает огромным преимуществом — полной управляемостью.

Можно:

  • менять алгоритмы;
  • настраивать параметры;
  • внедрять собственный формат;
  • использовать аппаратное ускорение;
  • подключать внешние HSM;
  • реализовывать миграции;
  • строить корпоративные схемы безопасности.

Когда password-hash становится ограничением

Высокоуровневая библиотека подходит не всегда.

Ограниченный контроль

Нельзя гибко управлять:

  • внутренним форматом;
  • миграцией алгоритмов;
  • стратегией обновления параметров;
  • нестандартными требованиями безопасности.

Зависимость от реализации

Если библиотека перестаёт поддерживаться:

  • обновления прекращаются;
  • алгоритмы устаревают;
  • возникают проблемы совместимости.

Ограниченность алгоритмов

Современные системы чаще используют:

  • bcrypt;
  • scrypt;
  • Argon2.

Некоторые старые библиотеки ориентированы на SHA1/PBKDF2 и уже не соответствуют современным требованиям.


Современный подход через crypto.scrypt

Node.js поддерживает scrypt напрямую.

Пример:

const crypto = require('crypto');

function hashPassword(password) {
    const salt = crypto.randomBytes(16).toString('hex');

    const hash = crypto
        .scryptSync(password, salt, 64)
        .toString('hex');

    return `${salt}:${hash}`;
}

scrypt существенно устойчивее к GPU-атакам по сравнению с SHA256.


Проблема самодельных решений

При прямом использовании crypto разработчики часто создают собственные схемы хранения:

salt:hash

или:

hash:salt:iterations

или даже:

{
    "salt": "...",
    "hash": "...",
    "algo": "sha512"
}

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

  • несовместимость версий;
  • ошибки миграций;
  • сложности обновления;
  • путаница параметров;
  • дублирование логики.

password-hash стандартизирует эти процессы.


Сравнение объёма кода

password-hash

const hash = passwordHash.generate(password);

const ok = passwordHash.verify(
    password,
    hash
);

crypto

const crypto = require('crypto');

function createHash(password) {
    const salt = crypto.randomBytes(16);

    const hash = crypto.pbkdf2Sync(
        password,
        salt,
        120000,
        64,
        'sha512'
    );

    return {
        salt: salt.toString('hex'),
        hash: hash.toString('hex')
    };
}

function verify(password, stored) {
    const hash = crypto.pbkdf2Sync(
        password,
        Buffer.from(stored.salt, 'hex'),
        120000,
        64,
        'sha512'
    );

    return hash.toString('hex') === stored.hash;
}

Контроль над параметрами безопасности

В password-hash

Настройки ограничены API библиотеки.

Например:

passwordHash.generate(password, {
    algorithm: 'sha512',
    saltLength: 32,
    iterations: 15000
});

Но доступный набор возможностей зависит от реализации.


В crypto

Полный контроль:

crypto.pbkdf2Sync(
    password,
    salt,
    310000,
    128,
    'sha512'
);

Можно изменять:

  • длину ключа;
  • алгоритм;
  • число итераций;
  • размер соли;
  • бинарный формат;
  • кодировку.

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

password-hash

Добавляет:

  • уровень абстракции;
  • парсинг;
  • внутреннюю обработку.

Накладные расходы обычно незначительны.


crypto

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

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

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

Особенно важно для:

  • высоконагруженных API;
  • authentication gateways;
  • enterprise-сервисов.

Прозрачность реализации

password-hash

Часть логики скрыта внутри библиотеки.

Иногда сложно понять:

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

crypto

Весь процесс полностью прозрачен:

const salt = crypto.randomBytes(32);

const hash = crypto.pbkdf2Sync(
    password,
    salt,
    200000,
    64,
    'sha512'
);

Каждый этап контролируется напрямую.


Сценарии использования

Когда подходит password-hash

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

Когда лучше crypto

  • enterprise-разработка;
  • банковские системы;
  • high-load API;
  • требования аудита;
  • собственная криптографическая политика;
  • поддержка нескольких алгоритмов;
  • миграции между форматами;
  • интеграция с внешними системами безопасности.

Безопасность сравнения хешей

Обычное сравнение строк:

hash1 === hash2

может быть уязвимо к timing attack.

В crypto существует безопасное сравнение:

crypto.timingSafeEqual(
    Buffer.from(hash1),
    Buffer.from(hash2)
);

Высокоуровневые библиотеки обычно скрывают этот механизм внутри себя.


Масштабируемость архитектуры

password-hash

Подходит для простых схем:

password → hash → verify

crypto

Позволяет строить сложные пайплайны:

password
    ↓
pre-hash normalization
    ↓
pepper
    ↓
salt
    ↓
PBKDF2/scrypt
    ↓
custom encoding
    ↓
versioned storage

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


Поддержка миграций

При использовании crypto можно хранить версии схем:

v1$sha256$...
v2$pbkdf2$...
v3$scrypt$...

Это позволяет:

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

У password-hash подобные возможности обычно ограничены внутренней реализацией библиотеки.


Подход к ответственности

password-hash

Библиотека берёт ответственность на себя:

  • выбор формата;
  • генерация соли;
  • верификация;
  • параметры.

Это уменьшает число ошибок.


crypto

Ответственность полностью лежит на разработчике:

  • архитектура;
  • безопасность;
  • формат хранения;
  • миграции;
  • защита от атак;
  • обновление алгоритмов.

Взамен предоставляется максимальная гибкость.