Сравнение хеша с хешем вместо пароля с хешем

Принцип хранения паролей в виде хеша

При использовании библиотеки для хеширования паролей в JavaScript, например password-hash, исходный пароль никогда не сохраняется в открытом виде. Вместо этого применяется односторонняя функция преобразования:

  • вход: пароль пользователя
  • выход: строка фиксированного формата (хеш)

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

Ключевая особенность: одинаковый вход всегда даёт одинаковый хеш (при одинаковых параметрах соли и алгоритма).


Суть сравнения: почему не «пароль vs хеш»

В корректной модели аутентификации сравнение выглядит так:

  1. Пользователь вводит пароль
  2. Пароль преобразуется в хеш
  3. Полученный хеш сравнивается с сохранённым

Сравнение происходит между:

  • вычисленным хешем
  • сохранённым хешем

а не между паролем и хешем.

Это важно, потому что хеш — результат необратимого преобразования, и прямое сравнение с паролем невозможно по определению.


Идея «хеш с хешем»: когда она появляется

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

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

Это приводит к модели:

hash(password) → H1
hash(H1) → H2

или попытке сравнения:

H1 (входящий) == H1 (сохранённый)

или даже:

hash(hash(password))

Такой подход часто называют «двойным хешированием», хотя корректнее говорить о каскадном хешировании.


Почему сравнение хешей с хешами считается ошибочной моделью

1. Потеря смысла соли

Современные библиотеки, включая password-hash, используют соль:

  • случайное значение
  • добавляется к паролю перед хешированием

Если сравниваются «хеш с хешем», часто возникает попытка унифицировать данные, что приводит к:

  • либо повторному хешированию соли
  • либо её потере

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


2. Нарушение модели проверки подлинности

Правильная модель:

password + salt → hash → compare

Ошибочная:

hash(password) → hash(password) → compare

или

hash(password) == hash(password)

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


3. Проблема миграции алгоритмов

Если хранится «хеш хеша», возникают сложности при переходе:

  • SHA-1 → SHA-256
  • bcrypt → argon2-подобные схемы

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


Корректная схема проверки в password-hash

Библиотека password-hash обычно предоставляет две ключевые операции:

  • генерация хеша
  • проверка пароля

Пример логики:

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

// регистрация
const hash = passwordHash.generate('userPassword123');

// проверка
const isValid = passwordHash.verify('userPassword123', hash);

Внутри verify выполняется:

  1. извлечение параметров хеша (алгоритм, соль)
  2. повторное вычисление хеша входного пароля
  3. сравнение строк результата

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

Даже если визуально хеши совпадают, необходимо учитывать:

  • алгоритм генерации
  • версию схемы хеширования
  • наличие соли
  • параметры итераций

Два одинаковых хеша могут означать разные вещи в разных системах.


Сценарии, где сравнение «хеш с хешем» встречается

1. Распределённые системы

В микросервисной архитектуре один сервис может передавать уже хешированное значение другому. Тогда сравнение происходит между:

  • хешем, созданным в одном сервисе
  • хешем, сохранённым в другом

Но даже в этом случае важно, что это один и тот же алгоритм и формат.


2. Кэширование результатов

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

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

Однако конечная проверка всё равно сводится к стандартной модели verify.


3. Ошибочная реализация авторизации

Часто встречается анти-паттерн:

if (hash(inputPassword) === storedHash) {
   // логин
}

Он может работать, но:

  • не учитывает соль внутри структуры
  • игнорирует встроенные механизмы verify
  • создаёт риск несовместимости с библиотекой

Устойчивость к атакам при разных подходах

Прямое сравнение пароля и хеша (не используется)

  • невозможно
  • противоречит модели хеширования

Хеш с хешем (каскадное хеширование)

Проблемы:

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

Использование verify из password-hash

  • корректная проверка
  • учёт всех параметров
  • защита от типичных ошибок реализации

Особенности строкового сравнения хешей

Даже при корректной архитектуре сравнение строк имеет нюансы:

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

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


Итоговая логика понимания модели

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

ввод → хеширование → сравнение с эталонным хешем

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