password-hash против argon2

Хранение паролей в открытом виде — критическая ошибка проектирования любой системы аутентификации. Даже при использовании HTTPS пароль неизбежно оказывается в базе данных, а значит при компрометации БД злоумышленник получает доступ ко всем учетным записям.

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

В экосистеме JavaScript существует множество решений для этой задачи. Одним из высокоуровневых инструментов является библиотека password-hash, а современным промышленным стандартом — алгоритм Argon2.

Несмотря на схожую цель, подходы у них радикально различаются.


Библиотека password-hash

Установка

npm install password-hash

Базовое использование

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

const password = 'myStrongPassword';

const hashedPassword = passwordHash.generate(password);

console.log(hashedPassword);

Пример результата:

sha1$3$5d41402abc4b2a76b9719d911017c592

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

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

const password = 'myStrongPassword';

const hashedPassword = passwordHash.generate(password);

const isMatch = passwordHash.verify(password, hashedPassword);

console.log(isMatch);

Результат:

true

Как работает password-hash

Библиотека является надстройкой над стандартными криптографическими хеш-функциями:

  • SHA1
  • SHA256
  • SHA512
  • MD5

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

  1. соль;
  2. хеш пароля;
  3. строка с параметрами.

Типичная структура:

алгоритм$итерации$соль$хеш

Пример:

sha256$1000$saltValue$hashValue

Настройка алгоритма

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

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

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

console.log(hash);

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

const hash = passwordHash.generate('secret', {
    algorithm: 'sha512'
});

Настройка количества итераций

Количество итераций увеличивает стоимость перебора.

const hash = passwordHash.generate('secret', {
    algorithm: 'sha512',
    iterations: 5000
});

Чем больше итераций:

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

Генерация собственной соли

const hash = passwordHash.generate('secret', {
    saltLength: 32
});

Полный пример регистрации пользователя

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

function registerUser(login, password) {
    const hash = passwordHash.generate(password, {
        algorithm: 'sha512',
        iterations: 10000,
        saltLength: 32
    });

    const user = {
        login,
        passwordHash: hash
    };

    return user;
}

const user = registerUser('admin', 'superPassword');

console.log(user);

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

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

const dbUser = {
    login: 'admin',
    passwordHash: 'sha512$10000$saltsalt$hashhash'
};

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

Основные проблемы password-hash

Несмотря на простоту, библиотека считается устаревшей для современных систем безопасности.

Главная причина — использование обычных криптографических хешей вместо специализированных password hashing алгоритмов.


Почему SHA256 и SHA512 недостаточны

SHA256 и SHA512 создавались не для хранения паролей.

Их задача:

  • контроль целостности;
  • цифровые подписи;
  • криптографические протоколы.

Они чрезвычайно быстрые.

Высокая скорость опасна при хранении паролей.


Опасность быстрых алгоритмов

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

Это делает возможными:

  • brute force;
  • dictionary attack;
  • rainbow tables;
  • GPU cracking.

Даже большое число итераций не решает проблему полностью.


Пример GPU-атаки

Допустим, пароль хранится так:

sha256(password)

Видеокарта может проверить огромный словарь паролей почти мгновенно.

Если используется Argon2, ситуация меняется:

  • требуется большой объем памяти;
  • GPU теряют преимущество;
  • стоимость атаки резко возрастает.

Что такое Argon2

Argon2 — современный алгоритм хеширования паролей, победитель конкурса Password Hashing Competition (PHC).

Существует три варианта:

Вариант Назначение
Argon2d Защита от GPU
Argon2i Защита от side-channel атак
Argon2id Комбинированный режим

На практике почти всегда используется Argon2id.


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

Memory-hard архитектура

Argon2 требует значительного объема памяти.

Это делает GPU и ASIC менее эффективными.


Настраиваемая стоимость

Можно отдельно регулировать:

  • время вычисления;
  • объем памяти;
  • количество потоков.

Защита от параллельных атак

Большой объем памяти резко ограничивает масштабируемость brute force.


Современный стандарт

Argon2 рекомендуется:

  • OWASP;
  • PHC;
  • многими криптографическими аудиторами.

Установка argon2

Node.js библиотека

npm install argon2

Базовое использование Argon2

const argon2 = require('argon2');

async function run() {
    const password = 'superSecret';

    const hash = await argon2.hash(password);

    console.log(hash);
}

run();

Пример результата:

$argon2id$v=19$m=65536,t=3,p=4$...

Проверка пароля через Argon2

const argon2 = require('argon2');

async function login(password, hash) {
    return await argon2.verify(hash, password);
}

Настройка Argon2

Параметр memoryCost

Количество используемой памяти.

const hash = await argon2.hash(password, {
    memoryCost: 65536
});

Значение указывается в килобайтах.


Параметр timeCost

Количество проходов вычислений.

const hash = await argon2.hash(password, {
    timeCost: 4
});

Параметр parallelism

Количество потоков.

const hash = await argon2.hash(password, {
    parallelism: 2
});

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

const argon2 = require('argon2');

const hash = await argon2.hash(password, {
    type: argon2.argon2id
});

Это рекомендуемый режим.


Сравнение password-hash и Argon2

Характеристика password-hash Argon2
Основа SHA/MD5 Специализированный алгоритм
Защита от GPU Слабая Высокая
Memory-hard Нет Да
Современный стандарт Нет Да
Устойчивость к brute force Ограниченная Высокая
Гибкость настройки Низкая Высокая
Рекомендуется OWASP Нет Да

Почему memory-hard важен

Обычные алгоритмы ограничиваются вычислительной мощностью.

Argon2 дополнительно ограничивает:

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

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

Из-за этого преимущества GPU уменьшаются.


Side-channel атаки

Side-channel attack — атака через побочные каналы:

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

Argon2i и Argon2id уменьшают риск таких атак.

Обычные SHA-алгоритмы подобной защиты не имеют.


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

Подход постепенной миграции

Полная немедленная миграция невозможна без знания исходных паролей.

Используется схема:

  1. пользователь входит;
  2. пароль проверяется старым алгоритмом;
  3. создается новый Argon2-хеш;
  4. старый хеш заменяется.

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

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

async function login(password, user) {

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

    if (!oldValid) {
        return false;
    }

    const newHash = await argon2.hash(password, {
        type: argon2.argon2id
    });

    user.passwordHash = newHash;

    return true;
}

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

Иногда библиотека встречается:

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

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


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

Argon2 намеренно медленнее.

Это не недостаток, а часть модели безопасности.

Пример:

Алгоритм Среднее время
SHA256 микросекунды
bcrypt миллисекунды
Argon2 десятки миллисекунд

Выбор параметров Argon2

Параметры зависят от:

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

Типичные настройки

{
    type: argon2.argon2id,
    memoryCost: 65536,
    timeCost: 3,
    parallelism: 1
}

Ошибки при хранении паролей

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

Критически небезопасно.

md5(password)

Подбирается практически мгновенно.


Отсутствие соли

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

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

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

Самостоятельная реализация криптографии

Опасный подход:

sha256(password + 'mySecret')

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


bcrypt против Argon2

До появления Argon2 стандартом считался bcrypt.

bcrypt по-прежнему безопасен, но имеет ограничения:

  • слабая memory-hard модель;
  • ограничение длины пароля;
  • менее гибкая настройка.

Argon2 считается более современным решением.


Пример безопасной схемы аутентификации

Регистрация

const hash = await argon2.hash(password, {
    type: argon2.argon2id,
    memoryCost: 65536,
    timeCost: 3
});

Авторизация

const valid = await argon2.verify(
    user.hash,
    password
);

Дополнительная защита

Даже использование Argon2 не отменяет необходимости:

  • HTTPS;
  • rate limiting;
  • двухфакторной аутентификации;
  • защиты от credential stuffing;
  • мониторинга аномальной активности;
  • блокировки brute force.

Проверка времени выполнения

Слишком слабые параметры Argon2 опасны.

Слишком сильные — перегружают сервер.

Практически обычно выбирают время вычисления:

50–300 мс

на один пароль.


OWASP рекомендации

OWASP рекомендует:

  • Argon2id;
  • уникальную соль;
  • достаточный memoryCost;
  • регулярное обновление параметров;
  • отказ от SHA256/SHA512 как password hashing алгоритмов.

Итоговая архитектурная разница

password-hash — это удобная оболочка над обычными хеш-функциями.

Argon2 — специализированный алгоритм, проектировавшийся именно для защиты паролей от современных атак:

  • GPU cracking;
  • ASIC cracking;
  • massive parallel brute force;
  • memory tradeoff attacks.

Поэтому в современных Node.js-приложениях Argon2 является предпочтительным решением практически для всех сценариев хранения паролей.