Хеширование паролей: почему SHA-512 недостаточно

Криптографические хеш-функции общего назначения проектируются для скорости и детерминированности. Их цель — быстро преобразовать произвольные данные в фиксированный отпечаток. В контексте паролей это становится проблемой: скорость, которая полезна в большинстве криптографических задач, превращается в уязвимость.

Парольное хеширование требует обратного набора свойств:

  • высокая вычислительная стоимость одной операции;
  • возможность настройки сложности;
  • обязательное использование уникальной соли;
  • устойчивость к массовому перебору (GPU/ASIC);
  • замедление атакующего, а не пользователя.

Алгоритмы вроде SHA-512 изначально не предназначены для этого класса задач.


Что делает SHA-512 в TweetNaCl.js и nacl.js

В экосистеме TweetNaCl.js / nacl.js присутствует реализация хеш-функции, основанной на SHA-512:

nacl.hash(message)

или в более низкоуровневом виде:

nacl.hash = function(m) { ... } // SHA-512 over input

Этот механизм корректно выполняет задачу криптографического хеширования данных, но не содержит:

  • соли (salt);
  • итеративного усиления (key stretching);
  • адаптивной сложности;
  • защиты от перебора паролей.

Фактически это чистая, быстрая функция без контрмер против атак на пароли.


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

Главная проблема SHA-512 в контексте паролей — производительность.

Современные GPU способны выполнять миллиарды SHA-512 операций в секунду. Это означает, что даже относительно сложный пароль становится уязвимым при атаке перебором.

Сценарий атаки выглядит так:

  1. Берётся база хешей.
  2. Каждый хеш сравнивается с миллиардами вариантов паролей.
  3. Используется параллельная обработка (GPU/ASIC).
  4. Время взлома сокращается до минут или часов.

Отсутствие искусственного замедления делает такие атаки экономически выгодными.


Отсутствие соли как критическая проблема

SHA-512 сам по себе не предусматривает соль. В системах вроде TweetNaCl.js разработчик обязан добавлять её вручную.

Без соли возникает классическая уязвимость:

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

Пример небезопасного подхода:

const hash = nacl.hash(nacl.util.decodeUTF8(password));

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

Правильный подход требует уникальной соли на каждый пароль:

hash = SHA512(salt + password)

Но даже это не решает фундаментальную проблему скорости алгоритма.


Ключевая архитектурная проблема TweetNaCl.js / nacl.js

TweetNaCl.js ориентирован на минимализм и реализацию проверенных криптопримитивов:

  • Curve25519 (обмен ключами)
  • Salsa20 / ChaCha-like конструкции (в зависимости от сборки)
  • Poly1305 (аутентификация)
  • SHA-512 (хеширование данных)

Отсутствует целый класс алгоритмов:

  • password-based key derivation functions;
  • memory-hard функции;
  • адаптивные KDF.

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


Почему SHA-512 не является KDF

Функция хеширования:

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

Функция вывода ключа (KDF):

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

SHA-512 не удовлетворяет этим требованиям.


Атаки на пароли через SHA-512

Перебор (brute force)

Каждая проверка стоит дешево → огромная скорость перебора.

Атаки по словарю

Используются базы утекших паролей и их комбинации.

Rainbow tables

Предрасчитанные таблицы соответствий «пароль → хеш», полностью эффективные при отсутствии соли.

GPU-ускорение

Параллельная обработка делает SHA-512 практически неподходящим для защиты паролей.


Правильный подход: KDF вместо SHA-512

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

  • PBKDF2
  • scrypt
  • Argon2

PBKDF2 (через WebCrypto API)

const enc = new TextEncoder();

const key = await crypto.subtle.importKey(
  "raw",
  enc.encode(password),
  "PBKDF2",
  false,
  ["deriveBits"]
);

const derived = await crypto.subtle.deriveBits(
  {
    name: "PBKDF2",
    salt: saltBuffer,
    iterations: 310000,
    hash: "SHA-256"
  },
  key,
  256
);

scrypt / Argon2 (через libsodium)

TweetNaCl.js не содержит этих алгоритмов, но экосистема libsodium.js предоставляет:

  • memory-hard hashing;
  • защиту от GPU атак;
  • параметризуемую сложность.

Сравнение подходов

Метод Скорость Безопасность паролей Устойчивость к GPU
SHA-512 (TweetNaCl.js) Очень высокая Низкая Низкая
PBKDF2 Средняя Средняя Средняя
scrypt Низкая Высокая Высокая
Argon2 Низкая Очень высокая Очень высокая

Типичная ошибка использования TweetNaCl.js

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

const digest = nacl.hash(password);

Проблема заключается в том, что атакующий получает:

  • мгновенную проверку гипотез;
  • возможность масштабирования атаки;
  • отсутствие затрат на перебор.

Даже добавление соли не решает проблему полностью:

nacl.hash(salt + password)

Корректная архитектура хранения паролей

Безопасная схема включает:

  1. Генерацию случайной соли (CSPRNG).
  2. Использование KDF с высокой стоимостью.
  3. Хранение параметров алгоритма вместе с хешем.
  4. Проверку через повторное вычисление KDF.

Пример структуры записи:

{
  salt: "...",
  iterations: 310000,
  hash: "..."
}

Роль TweetNaCl.js в системах с паролями

TweetNaCl.js может использоваться корректно в следующих частях системы:

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

Но не в слое:

  • хранения паролей;
  • проверки паролей;
  • генерации password hashes.

Причина распространённой ошибки в проектах

Использование SHA-512 для паролей часто возникает из-за:

  • наличия функции nacl.hash;
  • иллюзии «криптографичности = безопасности»;
  • отсутствия встроенного KDF в библиотеке;
  • упрощённых учебных примеров.

Однако криптографическая корректность не равна устойчивости к перебору.