Хеширование — это процесс преобразования произвольных данных в строку фиксированной длины при помощи специального алгоритма — хеш-функции.
На вход хеш-функция может получать данные любого размера:
"password"
"123456"
"qwerty123"
На выходе всегда формируется уникальный набор символов фиксированной длины:
5e884898da28047151d0e56f8dc6292773603d0d...
Полученный результат называется:
В JavaScript и Node.js хеширование применяется практически во всех системах аутентификации, авторизации и защиты данных.
Главная особенность хеширования заключается в том, что процесс работает только в одну сторону.
Можно:
Текст → Хеш
Нельзя:
Хеш → Исходный текст
Именно поэтому пароли в безопасных приложениях не хранятся в открытом виде.
Вместо этого сохраняется только хеш:
Пароль пользователя:
mypassword123
Сохранённый хеш:
a8b7c6d5e4...
Даже если база данных будет украдена, злоумышленник не увидит настоящие пароли пользователей.
Очень распространённая ошибка — путать хеширование и шифрование.
Шифрование предназначено для сокрытия данных с возможностью последующего восстановления.
Текст → Шифр → Текст
Для расшифровки используется ключ.
Пример:
"secret"
↓
"8fj2kf9s"
↓
"secret"
Хеширование не предполагает обратного преобразования.
Текст → Хеш
Вернуть исходные данные невозможно.
Пример:
"secret"
↓
"2bb80d537b..."
Безопасная хеш-функция должна обладать несколькими важными свойствами.
Одинаковые данные всегда дают одинаковый хеш.
hash("password")
hash("password")
Результат:
5f4dcc3b5aa765d61d8327deb882cf99
Алгоритм должен быстро создавать хеш даже для больших объёмов данных.
Это важно для:
Из хеша нельзя получить исходное значение.
Даже при наличии:
Коллизия — ситуация, при которой разные данные дают одинаковый хеш.
Пример плохой функции:
"abc" → 123
"xyz" → 123
Криптографическая функция должна минимизировать вероятность подобных совпадений.
Даже минимальное изменение данных полностью меняет хеш.
Пример:
password1
и
password2
дадут совершенно разные результаты.
Хеширование применяется практически во всех областях разработки.
Самая известная область применения.
При регистрации:
Пароль → Хеш → База данных
При авторизации:
Введённый пароль → Новый хеш
После этого система сравнивает:
Хеш из БД
=
Хеш введённого пароля
Если значения совпадают — пароль верный.
Хеш позволяет определить, изменился ли файл.
Пример:
archive.zip
↓
SHA-256:
a4f5b6...
После скачивания пользователь может вычислить хеш самостоятельно.
Если значения совпадают:
Оригинальный хеш = Новый хеш
значит файл не повреждён и не был изменён.
Хеширование используется в:
Вместо подписи всего файла подписывается его хеш.
Это ускоряет работу системы.
В блокчейнах хеши используются для:
Каждый блок содержит хеш предыдущего блока.
Изменение любого блока ломает всю цепочку.
Хеши помогают быстро находить данные.
Например:
URL → Хеш
или:
Объект → Хеш-ключ
Это активно используется в:
В JavaScript хеширование чаще всего используется на сервере через Node.js.
Для этого применяются:
встроенный модуль crypto;
библиотеки:
Многие новички совершают опасную ошибку:
const crypto = require("crypto")
const hash = crypto
.createHash("sha256")
.update(password)
.digest("hex")
На первый взгляд выглядит безопасно.
Но для хранения паролей это плохое решение.
SHA-256 создавался как очень быстрый алгоритм.
Современные видеокарты способны вычислять миллиарды SHA-хешей в секунду.
Это позволяет злоумышленникам массово перебирать пароли.
Brute force — полный перебор всех возможных комбинаций.
Пример:
123456
123457
123458
...
Если алгоритм работает слишком быстро, пароль может быть подобран за секунды.
Злоумышленники используют базы популярных паролей:
Для каждого слова заранее вычисляется хеш.
Затем выполняется сравнение с украденной базой.
Радужная таблица — заранее подготовленная база:
Пароль → Хеш
Пример:
password → 5f4dcc3b5aa765d61d8327deb882cf99
Если пароль хранится без дополнительной защиты, подобрать его становится значительно проще.
Salt — случайная строка, добавляемая к паролю перед хешированием.
Пример:
password + X7aP91Lm
После этого вычисляется хеш.
Salt решает несколько важных проблем:
Даже одинаковые пароли будут иметь разные хеши.
password + salt1
password + salt2
Результат:
a12f...
b98c...
Два пользователя с одинаковым паролем не будут иметь одинаковые записи в базе.
Злоумышленнику приходится подбирать пароль отдельно для каждого пользователя.
Для хранения паролей применяются специальные алгоритмы:
Они специально работают медленно.
Это делает массовый перебор крайне дорогим.
Библиотека password-hash — простой инструмент для:
Пример установки:
npm install password-hash
Библиотека автоматически:
Пример результата:
sha1$3$1d00a113$e7e4dc6c2...
Внутри строки содержатся:
Пример:
const passwordHash = require("password-hash")
const hashedPassword =
passwordHash.generate("mypassword")
console.log(hashedPassword)
Результат:
sha1$3$8f9d1a$9e2c4b...
При авторизации:
const passwordHash = require("password-hash")
const verified =
passwordHash.verify(
"mypassword",
storedHash
)
console.log(verified)
Результат:
true
Нельзя делать так:
if (hash1 === hash2)
Некоторые типы сравнений могут быть уязвимы к timing attack.
Библиотеки проверки паролей используют специальные методы безопасного сравнения.
password-hash поддерживает многократное повторение
вычислений.
Пример:
passwordHash.generate(password, {
iterations: 10
})
Каждая дополнительная итерация:
Библиотека поддерживает:
Пример:
passwordHash.generate(password, {
algorithm: "sha256"
})
Алгоритмы:
имеют известные криптографические уязвимости.
Их нельзя использовать в современных системах безопасности.
Особенно опасно применять их для хранения паролей.
В современных проектах предпочтение обычно отдаётся:
Они обеспечивают:
Преимущества библиотеки:
Несмотря на удобство, библиотека имеет ограничения.
По умолчанию могут использоваться алгоритмы, которые уже не считаются надёжными.
Современные алгоритмы вроде Argon2 используют не только процессор, но и память.
Это значительно усложняет GPU-взлом.
password-hash такой защиты не предоставляет.
Для современных production-систем чаще рекомендуют:
const passwordHash = require("password-hash")
const password = "mypassword"
const hashed =
passwordHash.generate(password)
// сохранить hashed в БД
const passwordHash = require("password-hash")
const enteredPassword = "mypassword"
// получить хеш из БД
const savedHash = user.password
const verified =
passwordHash.verify(
enteredPassword,
savedHash
)
if (verified) {
console.log("Доступ разрешён")
} else {
console.log("Неверный пароль")
}
Крайне опасный подход:
password: qwerty123
При утечке базы все аккаунты оказываются скомпрометированы.
Даже сильный алгоритм становится уязвим без salt.
Нельзя писать собственные алгоритмы:
reverse(password) + "secret"
Подобные решения не являются безопасными.
Salt должен быть случайным и уникальным для каждого пользователя.
Низкая стоимость вычисления делает атаки значительно дешевле.
Этапы проверки:
Современная схема выглядит так:
Пароль
↓
Salt
↓
Медленный алгоритм
↓
Хеш
↓
База данных
При авторизации:
Введённый пароль
↓
Те же параметры
↓
Новый хеш
↓
Сравнение
Без хеширования невозможно представить:
Практически каждая система аутентификации использует хеширование как базовый механизм защиты учётных данных.