При использовании библиотеки для хеширования паролей в JavaScript, например password-hash, исходный пароль никогда не сохраняется в открытом виде. Вместо этого применяется односторонняя функция преобразования:
Система хранит только результат преобразования, а при проверке выполняет аналогичное преобразование введённого значения и сравнивает результаты.
Ключевая особенность: одинаковый вход всегда даёт одинаковый хеш (при одинаковых параметрах соли и алгоритма).
В корректной модели аутентификации сравнение выглядит так:
Сравнение происходит между:
а не между паролем и хешем.
Это важно, потому что хеш — результат необратимого преобразования, и прямое сравнение с паролем невозможно по определению.
Иногда возникает архитектурная ошибка или альтернативный подход:
Это приводит к модели:
hash(password) → H1
hash(H1) → H2
или попытке сравнения:
H1 (входящий) == H1 (сохранённый)
или даже:
hash(hash(password))
Такой подход часто называют «двойным хешированием», хотя корректнее говорить о каскадном хешировании.
Современные библиотеки, включая password-hash, используют соль:
Если сравниваются «хеш с хешем», часто возникает попытка унифицировать данные, что приводит к:
В обоих случаях резко снижается устойчивость к радужным таблицам.
Правильная модель:
password + salt → hash → compare
Ошибочная:
hash(password) → hash(password) → compare
или
hash(password) == hash(password)
Во втором случае система фактически проверяет не пароль, а совпадение производных значений, что может быть некорректно при изменении алгоритма или параметров.
Если хранится «хеш хеша», возникают сложности при переходе:
Двойное хеширование делает невозможным прозрачную миграцию без пересоздания всех данных.
Библиотека password-hash обычно предоставляет две ключевые операции:
Пример логики:
const passwordHash = require('password-hash');
// регистрация
const hash = passwordHash.generate('userPassword123');
// проверка
const isValid = passwordHash.verify('userPassword123', hash);
Внутри verify выполняется:
Даже если визуально хеши совпадают, необходимо учитывать:
Два одинаковых хеша могут означать разные вещи в разных системах.
В микросервисной архитектуре один сервис может передавать уже хешированное значение другому. Тогда сравнение происходит между:
Но даже в этом случае важно, что это один и тот же алгоритм и формат.
Иногда промежуточный хеш используется как ключ:
Однако конечная проверка всё равно сводится к стандартной модели verify.
Часто встречается анти-паттерн:
if (hash(inputPassword) === storedHash) {
// логин
}
Он может работать, но:
Проблемы:
Даже при корректной архитектуре сравнение строк имеет нюансы:
==Некоторые реализации внутри библиотек используют защитные механизмы, чтобы избежать утечек через время сравнения.
Модель проверки всегда должна оставаться на уровне:
ввод → хеширование → сравнение с эталонным хешем
Любые попытки заменить её схемой «хеш с хешем» меняют не только реализацию, но и семантику системы аутентификации, что приводит к неоднозначным и потенциально уязвимым решениям.