Версионирование алгоритма хеширования в схемах хранения паролей
Системы аутентификации, основанные на хешировании паролей, неизбежно сталкиваются с эволюцией криптографических алгоритмов. Алгоритмы, считавшиеся устойчивыми несколько лет назад, со временем могут утратить безопасность из-за роста вычислительных мощностей или появления новых атак. В таких условиях схема хранения паролей должна поддерживать возможность изменения алгоритма без нарушения совместимости с уже сохранёнными данными.
Версионирование алгоритма хеширования решает задачу сосуществования нескольких криптографических схем в одной системе. Оно позволяет:
Современные схемы хранения паролей не ограничиваются сохранением только результата хеширования. Хранится метаданные, определяющие способ вычисления.
Типичная структура включает:
Пример строкового представления:
$argon2id$v=19$m=65536,t=3,p=4$<salt>$<hash>
или
$2b$12$<salt><hash>
Такая форма позволяет системе определить, каким способом проверять пароль, без обращения к внешним таблицам версий.
Наиболее распространённый подход — включение версии алгоритма непосредственно в строку хеша.
Принцип работы:
Преимущества:
Недостатки:
Альтернативный подход — хранение версии алгоритма в отдельном поле:
{
"password_hash": "<hash>",
"hash_version": 2
}
Логика проверки:
Этот подход часто используется в сочетании с библиотеками уровня приложения, включая реализации на JavaScript, где слой сервиса управляет логикой хеширования.
Преимущества:
Недостатки:
Комбинированный подход включает:
Такой вариант используется в системах с высокими требованиями к миграции и аудиту безопасности.
В JavaScript-экосистеме библиотека Password-hash выступает как абстракция над различными алгоритмами хеширования. Основная задача — унификация интерфейса работы с паролями независимо от используемого криптографического метода.
Типичная логика:
Пример использования:
import { hash, verify } from "password-hash";
const hashed = hash("user_password");
// hashed содержит алгоритм, соль и параметры
const isValid = verify("user_password", hashed);
Версионирование в данном контексте реализуется через структуру самого хеша, а не через внешние механизмы.
Изменение алгоритма хеширования требует стратегии постепенного перехода. Полная перекодировка всех паролей невозможна без знания исходных значений, поэтому применяется ленивый механизм миграции.
Принцип:
Псевдологика:
if (verify(password, storedHash)) {
if (needsRehash(storedHash)) {
const newHash = hash(password);
updateUserPassword(newHash);
}
}
Ключевым элементом является функция needsRehash,
определяющая устаревание версии.
Система должна уметь оценивать актуальность параметров хеширования.
Критерии:
Пример проверки:
function needsRehash(hashString) {
const meta = parseHash(hashString);
return (
meta.algorithm !== "argon2id" ||
meta.memory < 65536 ||
meta.iterations < 3
);
}
При наличии нескольких версий важно обеспечить обратную совместимость. Это достигается через слой диспетчеризации:
Архитектурно это реализуется через стратегию (Strategy Pattern), где каждый алгоритм представляет отдельную реализацию.
Распространённые проблемы:
Версионирование включает не только сам алгоритм, но и его конфигурацию. Для современных KDF (Key Derivation Function) критичны параметры:
Изменение любого параметра фактически формирует новую версию схемы хеширования.
Многие алгоритмы используют структурированный формат строки:
$argon2id$ — идентификатор алгоритма;v=19 — версия спецификации;m, t, p —
конфигурация;Такая структура позволяет системе не хранить отдельное поле версии, сохраняя всё в одном поле базы данных.
Грамотно спроектированная схема версионирования допускает добавление новых алгоритмов без изменения существующих записей. Это достигается через:
Пример реестра:
const algorithms = {
bcrypt: bcryptAdapter,
argon2id: argon2Adapter,
pbkdf2: pbkdf2Adapter
};
Каждый алгоритм проходит стадии:
Версионирование позволяет управлять этими стадиями без нарушения работы системы аутентификации.