Повторное хеширование пароля при входе пользователя представляет собой механизм обновления уже существующего хеша с учётом изменившихся параметров безопасности, прежде всего — увеличения стоимости вычисления (cost factor) в bcrypt.
Алгоритм bcrypt изначально проектировался как адаптивный: его вычислительная сложность регулируется параметром cost (также называемым rounds). Чем выше значение cost, тем больше времени требуется на вычисление хеша.
Со временем вычислительные мощности увеличиваются, и ранее выбранный уровень сложности перестаёт соответствовать актуальным требованиям безопасности. Например, хеши, созданные с cost = 10 в прошлом, сегодня могут вычисляться значительно быстрее, чем это было рассчитано при их выборе. Это снижает устойчивость к атакам перебора.
Повторное хеширование при входе пользователя позволяет постепенно обновлять базу паролей без принудительного сброса паролей и без массовой миграции данных.
Хеш bcrypt содержит встроенную информацию о параметрах его создания. Типичный формат:
$2b$12$..............................
Разбор структуры:
$2b$ — версия алгоритма12$ — cost factor (в данном случае 12)Таким образом, хеш самодостаточен: по нему можно определить, с какой сложностью он был создан.
Повторное хеширование требуется, если:
В bcrypt.js можно извлечь cost из хеша:
import bcrypt from 'bcryptjs';
const hash = '$2b$10$EIXh...';
const currentRounds = bcrypt.getRounds(hash);
console.log(currentRounds); // 10
Сравнение с актуальной конфигурацией системы:
const TARGET_ROUNDS = 12;
function needsRehash(hash) {
const currentRounds = bcrypt.getRounds(hash);
return currentRounds < TARGET_ROUNDS;
}
Процесс аутентификации включает два этапа:
const isPasswordValid = await bcrypt.compare(password, user.passwordHash);
if (!isPasswordValid) {
throw new Error('Invalid credentials');
}
После успешной аутентификации выполняется проверка актуальности хеша:
const TARGET_ROUNDS = 12;
const currentRounds = bcrypt.getRounds(user.passwordHash);
if (currentRounds < TARGET_ROUNDS) {
const newHash = await bcrypt.hash(password, TARGET_ROUNDS);
await updateUserPasswordHash(user.id, newHash);
}
Повторное хеширование выполняется только после успешной проверки пароля. Это важно, поскольку:
Полный поток входа пользователя:
async function login(username, password) {
const user = await getUserByUsername(username);
if (!user) {
throw new Error('Invalid credentials');
}
const isValid = await bcrypt.compare(password, user.passwordHash);
if (!isValid) {
throw new Error('Invalid credentials');
}
const TARGET_ROUNDS = 12;
if (bcrypt.getRounds(user.passwordHash) < TARGET_ROUNDS) {
const newHash = await bcrypt.hash(password, TARGET_ROUNDS);
await updateUserPasswordHash(user.id, newHash);
}
return createSession(user);
}
Модель “lazy migration” (ленивая миграция) используется для постепенного обновления базы:
Фактически база паролей эволюционирует естественным образом.
Повторное хеширование увеличивает нагрузку на CPU, поскольку bcrypt — алгоритм с высокой вычислительной стоимостью.
Поэтому:
При реализации повторного хеширования учитываются следующие свойства:
Некорректные подходы включают:
Изменение параметра сложности происходит централизованно:
const BCRYPT_COST = parseInt(process.env.BCRYPT_COST || '12', 10);
После увеличения значения система автоматически начинает обновлять хеши пользователей при входе:
На практике одновременно могут существовать хеши с разными cost:
Логика повторного хеширования обеспечивает постепенное выравнивание уровня безопасности без резких изменений в инфраструктуре.