Хеширование паролей — критически важный механизм защиты пользовательских данных. Вместо хранения пароля в открытом виде приложение сохраняет результат криптографического преобразования — хеш. При авторизации введённый пароль снова хешируется и сравнивается с сохранённым значением.
В экосистеме JavaScript существуют разные подходы к хешированию:
Библиотеки password-hash и bcryptjs решают
одну задачу — безопасное хранение паролей, но делают это принципиально
разными способами.
Библиотека password-hash представляет собой лёгкую
обёртку над алгоритмом SHA. Она генерирует соль и объединяет её с
паролем перед хешированием.
Типичный результат:
sha1$3$5d41402abc4b2a76b9719d911017c592
Особенности:
Главный недостаток — слишком высокая скорость вычисления. Современные видеокарты способны перебирать миллиарды SHA-хешей в секунду.
bcryptjs — JavaScript-реализация алгоритма bcrypt.
bcrypt был создан специально для хранения паролей и обладает встроенной защитой от:
Пример bcrypt-хеша:
$2a$10$eImiTXuWVxfM37uY4JANjQ==...
Особенности:
npm install password-hash
npm install bcryptjs
const passwordHash = require('password-hash');
const hashedPassword = passwordHash.generate('myPassword');
console.log(hashedPassword);
Результат:
sha1$3$1d0f3e...
const bcrypt = require('bcryptjs');
const password = 'myPassword';
const salt = bcrypt.genSaltSync(10);
const hash = bcrypt.hashSync(password, salt);
console.log(hash);
Результат:
$2b$10$zL9x7...
algorithm$iterations$salt$hash
Пример:
sha256$1000$mysalt$hashvalue
Компоненты:
| Часть | Назначение |
|---|---|
| algorithm | Используемый SHA-алгоритм |
| iterations | Количество итераций |
| salt | Соль |
| hash | Итоговый хеш |
$2b$10$KYVbZ5JFVfqu0oV98LnF5eTk4QTe2e4PQG7QNYfhumEpGdi/867AO
Компоненты:
| Часть | Назначение |
|---|---|
| 2b | Версия bcrypt |
| 10 | Cost factor |
| Остальная часть | Соль и хеш |
bcrypt хранит все параметры внутри строки хеша.
const passwordHash = require('password-hash');
const hashedPassword = passwordHash.generate('secret');
const isValid = passwordHash.verify('secret', hashedPassword);
console.log(isValid);
const bcrypt = require('bcryptjs');
const hash = bcrypt.hashSync('secret', 10);
const result = bcrypt.compareSync('secret', hash);
console.log(result);
bcrypt-операции могут быть ресурсоёмкими, поэтому библиотека поддерживает async API.
const bcrypt = require('bcryptjs');
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash('password', salt, (err, hash) => {
console.log(hash);
});
});
Современный вариант:
const bcrypt = require('bcryptjs');
async function createHash() {
const salt = await bcrypt.genSalt(10);
return await bcrypt.hash('password', salt);
}
Библиотека в основном ориентирована на синхронную работу.
const passwordHash = require('password-hash');
const hash = passwordHash.generate('password');
Из-за высокой скорости SHA необходимость в асинхронности практически отсутствует.
Ключевая особенность bcrypt — регулируемая вычислительная сложность.
const hash = bcrypt.hashSync('password', 12);
Число 12 означает количество раундов.
Рост сложности:
| Cost | Скорость |
|---|---|
| 8 | Быстро |
| 10 | Стандарт |
| 12 | Медленно |
| 14+ | Очень медленно |
Каждое увеличение на 1 удваивает время вычисления.
const hash = passwordHash.generate('password', {
algorithm: 'sha256',
iterations: 10000
});
Проблема заключается в том, что даже большое количество SHA-итераций остаётся значительно менее безопасным, чем bcrypt.
password-hash использует быстрые криптографические
функции.
Это приводит к нескольким проблемам:
Видеокарты эффективно вычисляют SHA-хеши.
Современные GPU способны выполнять:
Даже при использовании соли быстрые алгоритмы остаются уязвимыми к предвычисленным атакам.
При компрометации БД атакующий может очень быстро проверить огромные словари паролей.
bcrypt проектировался именно для защиты паролей.
bcrypt намеренно использует тяжёлые операции.
Это делает:
Рост мощности оборудования компенсируется увеличением
cost factor.
Например:
bcrypt.hashSync(password, 14);
bcrypt всегда использует случайную соль.
Разработчику не требуется реализовывать её вручную.
SHA-хеширование чрезвычайно быстрое.
Преимущества:
Недостаток:
bcrypt существенно медленнее.
Примерные значения:
| Cost | Время |
|---|---|
| 8 | ~20 мс |
| 10 | ~80 мс |
| 12 | ~300 мс |
| 14 | ~1 сек |
Точные показатели зависят от оборудования.
const hash = passwordHash.generate('password', {
saltLength: 16
});
Можно вручную управлять:
bcrypt скрывает детали реализации.
const salt = bcrypt.genSaltSync(10);
Соль автоматически встраивается в итоговый хеш.
const passwordHash = require('password-hash');
function register(password) {
return passwordHash.generate(password);
}
function login(password, savedHash) {
return passwordHash.verify(password, savedHash);
}
const bcrypt = require('bcryptjs');
async function register(password) {
return await bcrypt.hash(password, 12);
}
async function login(password, savedHash) {
return await bcrypt.compare(password, savedHash);
}
| Возможность | password-hash | bcryptjs |
|---|---|---|
| Простота | Очень высокая | Высокая |
| Скорость | Очень высокая | Средняя |
| Защита от brute-force | Слабая | Сильная |
| Адаптивность | Ограниченная | Полная |
| GPU-защита | Плохая | Хорошая |
| Асинхронность | Ограниченная | Полная |
| Современная актуальность | Низкая | Высокая |
Даже SHA512 с большим числом итераций считается недостаточно надёжным для современных систем хранения паролей.
bcrypt использует более сложные внутренние структуры, тогда как SHA-алгоритмы ориентированы только на скорость.
Библиотека редко используется в новых production-проектах.
Использование возможно:
Для production-аутентификации предпочтительнее:
bcryptjs реализован полностью на JavaScript.
Нативная библиотека bcrypt обычно быстрее.
Синхронные методы блокируют поток Node.js.
Плохо:
bcrypt.hashSync(password, 14);
Лучше:
await bcrypt.hash(password, 14);
Типичная стратегия:
const passwordHash = require('password-hash');
const bcrypt = require('bcryptjs');
async function login(password, user) {
const oldValid = passwordHash.verify(password, user.password);
if (oldValid) {
const newHash = await bcrypt.hash(password, 12);
user.password = newHash;
return true;
}
return await bcrypt.compare(password, user.password);
}
| Тип атаки | password-hash | bcryptjs |
|---|---|---|
| Dictionary attack | Слабая защита | Хорошая защита |
| GPU brute-force | Очень слабая | Умеренная |
| Rainbow tables | Частичная | Хорошая |
| Массовый перебор | Слабая | Сильная |
Предпочтительный вариант:
bcrypt.hash(password, 12);
Оптимальный баланс:
bcrypt.hash(password, 10);
Рекомендуется:
Проблемный код:
app.post('/login', (req, res) => {
const hash = bcrypt.hashSync(req.body.password, 14);
});
При большом количестве запросов сервер начинает блокироваться.
Небезопасно:
bcrypt.hash(password, 4);
Критическая ошибка:
crypto.createHash('sha256')
Без соли одинаковые пароли создают одинаковые хеши.
| Критерий | password-hash | bcryptjs |
|---|---|---|
| Современность | Низкая | Высокая |
| Безопасность | Ограниченная | Высокая |
| Производительность | Очень высокая | Контролируемая |
| Простота интеграции | Высокая | Высокая |
| Рекомендуется для production | Нет | Да |
| Защита от современных атак | Слабая | Хорошая |
| Поддержка индустрией | Снижается | Широкая |