Ленивая миграция (lazy migration) — это стратегия постепенного обновления хешей паролей пользователей без принудительного сброса паролей и без массовой фоновой переработки базы данных. Обновление происходит только в момент успешной аутентификации пользователя.
Основная идея заключается в том, что устаревшие алгоритмы хеширования заменяются на более современные по мере естественных входов пользователей в систему. Это позволяет одновременно поддерживать совместимость со старыми данными и повышать криптографическую стойкость без дорогостоящих миграционных операций.
Ключевой принцип:
если пользователь успешно вошёл в систему с устаревшим хешем — пароль пересчитывается и сохраняется в новом формате
Ленивая миграция используется в следующих ситуациях:
Типичный поток аутентификации включает несколько этапов:
В контексте библиотеки password-hash логика обычно
строится вокруг функции проверки и функции генерации нового хеша.
Пример упрощённой схемы:
const passwordHash = require('password-hash');
function authenticate(user, plainPassword) {
const storedHash = user.passwordHash;
const isValid = passwordHash.verify(plainPassword, storedHash);
if (!isValid) {
return { success: false };
}
if (passwordHash.isDeprecated(storedHash)) {
const newHash = passwordHash.generate(plainPassword);
updateUserPasswordHash(user.id, newHash);
}
return { success: true };
}
Ключевым элементом ленивой миграции является механизм определения необходимости обновления хеша.
Устаревание может определяться по нескольким критериям:
Если хеш создан с использованием алгоритма, который признан небезопасным или устаревшим.
Например, слишком низкий cost-фактор у bcrypt-подобных схем.
Различные поколения системы могут использовать разные префиксы или структуры хеша.
Пример логики:
function isDeprecated(hash) {
if (hash.startsWith('sha256$')) return true;
if (hash.includes('rounds=5')) return true;
return false;
}
Обновление должно выполняться только после успешной проверки пароля. Это критически важно: нельзя менять хеш до подтверждения корректности пароля.
Процесс выглядит следующим образом:
пользователь вводит пароль;
система проверяет пароль старым методом;
если пароль верный:
пользователь получает доступ.
Библиотека password-hash обычно предоставляет три
ключевых операции:
При реализации ленивой миграции важно учитывать, что библиотека может не хранить метаданные о “силе” хеша, поэтому дополнительная логика должна быть вынесена на уровень приложения.
Пример генерации:
const hash = passwordHash.generate('user-password');
Пример проверки:
const valid = passwordHash.verify('user-password', hash);
В реальных системах часто присутствует сразу несколько поколений хешей:
Для поддержки такой модели вводится слой абстракции:
function verifyPassword(password, hash) {
if (hash.startsWith('sha')) {
return legacyShaVerify(password, hash);
}
return passwordHash.verify(password, hash);
}
И дополнительная проверка на необходимость обновления:
function needsUpgrade(hash) {
if (hash.startsWith('sha')) return true;
if (hash.includes('rounds=10')) return true;
return false;
}
Несмотря на удобство, стратегия требует строгого соблюдения правил безопасности:
При одновременной аутентификации одного пользователя возможна ситуация гонки обновления хеша. Для предотвращения проблем применяются:
Пример защиты:
async function updateHashIfUnchanged(userId, oldHash, newHash) {
const user = await getUser(userId);
if (user.passwordHash !== oldHash) {
return;
}
await saveNewHash(userId, newHash);
}
Ленивая миграция позволяет внедрять новые алгоритмы хеширования без массового пересчёта базы:
Таким образом, система естественным образом “перетекает” на новый стандарт безопасности.
Несмотря на эффективность, стратегия имеет особенности:
class AuthService {
async login(email, password) {
const user = await findUserByEmail(email);
if (!user) return false;
const valid = verifyPassword(password, user.passwordHash);
if (!valid) return false;
if (needsUpgrade(user.passwordHash)) {
const newHash = passwordHash.generate(password);
await updateUserPasswordHash(user.id, newHash);
}
return true;
}
}
Ленивая миграция стала стандартным подходом в системах с большим количеством пользователей. Она используется в:
Её ценность заключается в том, что безопасность повышается постепенно, без влияния на пользовательский опыт и без массовых операций над базой данных.