Хеширование ассоциируется прежде всего с паролями, однако тот же подход применим к другим чувствительным данным: токенам доступа, кодам подтверждения, одноразовым ключам, ссылкам для сброса пароля. В этих случаях цель аналогична — исключить возможность восстановления исходного значения даже при компрометации базы данных.
Ключевое отличие от паролей заключается в жизненном цикле и энтропии данных:
Это влияет на выбор параметров хеширования и стратегию хранения.
Хеширование оправдано в следующих сценариях:
Принцип: если значение даёт доступ — его нельзя хранить в открытом виде.
Перед хешированием необходимо создать криптографически стойкое
значение. В Node.js это делается через модуль crypto:
const crypto = require('crypto');
function generateToken() {
return crypto.randomBytes(32).toString('hex');
}
Особенности:
Библиотека bcrypt.js подходит не только для паролей, но
и для любых строковых данных.
const bcrypt = require('bcryptjs');
async function hashToken(token) {
const saltRounds = 10;
return await bcrypt.hash(token, saltRounds);
}
Результат:
Вместо сравнения строк используется функция compare:
async function verifyToken(token, hash) {
return await bcrypt.compare(token, hash);
}
Это важно, поскольку:
Пароли вводятся пользователями и могут быть слабыми. Токены генерируются системой и обычно уже обладают высокой случайностью.
Для токенов иногда требуется:
Поэтому:
Токены часто:
Это снижает требования к “вечной” стойкости хеша.
| Тип данных | Рекомендуемые saltRounds |
|---|---|
| Пароли | 10–12 |
| API-токены | 10–12 |
| Коды подтверждения | 6–8 |
| Одноразовые ссылки | 8–10 |
Причина:
Коды подтверждения (например, 6 цифр) имеют низкую энтропию. Это создаёт риск перебора.
Пример:
const code = "123456";
const hash = await bcrypt.hash(code, 6);
Проблема:
Меры защиты:
Пример записи в базе:
{
"userId": "123",
"tokenHash": "$2a$10$...",
"expiresAt": "2026-05-01T12:00:00Z"
}
Ключевые элементы:
После успешной проверки токен должен:
if (await verifyToken(input, storedHash)) {
// удалить токен
}
Причина:
API-токены часто используются многократно, поэтому:
Проверка:
const isValid = await bcrypt.compare(apiToken, storedHash);
Дополнительно:
bcrypt — медленный алгоритм по дизайну. При большом количестве операций возможны:
Решения:
Для некоторых задач вместо bcrypt применяют HMAC:
const crypto = require('crypto');
function hashWithHmac(token, secret) {
return crypto.createHmac('sha256', secret)
.update(token)
.digest('hex');
}
Отличия:
| bcrypt | HMAC |
|---|---|
| Медленный | Быстрый |
| Встроенная соль | Требует секретного ключа |
| Защита от brute-force | Зависит от длины токена |
| Подходит для паролей | Часто используется для API |
bcrypt предпочтителен, если:
В некоторых системах используется схема:
Это даёт:
Хеширование не защищает от:
Поэтому:
Хранение токена в открытом виде
// Неправильно
db.save({ token: rawToken });
Использование слабых токенов
Math.random().toString()
Сравнение через ===
if (token === storedHash) // ошибка
Отсутствие срока действия
const bcrypt = require('bcryptjs');
async function createCode() {
const code = Math.floor(100000 + Math.random() * 900000).toString();
const hash = await bcrypt.hash(code, 6);
return { code, hash };
}
async function verifyCode(input, hash) {
return await bcrypt.compare(input, hash);
}
Дополнительно:
const crypto = require('crypto');
const bcrypt = require('bcryptjs');
async function createResetToken() {
const token = crypto.randomBytes(32).toString('hex');
const hash = await bcrypt.hash(token, 10);
return { token, hash };
}
Поток:
function isExpired(expiresAt) {
return new Date() > new Date(expiresAt);
}
Без этого:
bcrypt остаётся универсальным инструментом для защиты любых чувствительных строковых данных. При работе с токенами и кодами важен баланс между:
Правильная комбинация генерации, хеширования и контроля жизненного цикла обеспечивает высокий уровень безопасности даже при компрометации хранилища данных.