Автоматическая ротация без инвалидации старых токенов

В Iron механизм ротации ключей строится вокруг идеи множественных паролей, где активный ключ используется для шифрования новых данных, а все предыдущие сохраняются для расшифровки уже выданных токенов. Такой подход позволяет обновлять криптографические секреты без принудительного разрыва сессий и без необходимости отзывать уже выданные токены.


Модель работы с несколькими ключами

В основе Iron лежит список секретов (password array). Поведение библиотеки при этом предсказуемо:

  • первый элемент массива используется для создания новых зашифрованных данных
  • все элементы массива участвуют в попытке расшифровки
  • порядок элементов критичен для производительности и безопасности

При изменении ключей старая информация не становится недоступной, пока её ключ присутствует в списке.

import Iron from '@hapi/iron';

const passwords = [
  'newest-secret-key',
  'previous-secret-key',
  'legacy-secret-key'
];

Любой токен, созданный с использованием одного из этих ключей, остаётся валидным до тех пор, пока соответствующий секрет присутствует в массиве.


Механика шифрования и дешифрования

При создании токена Iron использует первый пароль:

const sealed = await Iron.seal(
  { id: 123, role: 'admin' },
  passwords[0],
  Iron.defaults
);

При расшифровке происходит последовательная проверка:

const unsealed = await Iron.unseal(
  sealed,
  passwords,
  Iron.defaults
);

Алгоритм:

  1. Попытка расшифровать первым ключом
  2. При неудаче — переход к следующему
  3. Успешная проверка завершает процесс
  4. При отсутствии совпадений выбрасывается ошибка

Стратегия ротации без инвалидации токенов

Ротация строится как управляемый сдвиг массива ключей, а не замена:

Добавление нового ключа

Новый секрет всегда добавляется в начало списка:

passwords.unshift('brand-new-secret-key');

Теперь все новые токены будут шифроваться им.

Сохранение старых ключей

Старые ключи не удаляются сразу, а остаются в массиве до полного истечения срока жизни всех токенов, созданных с их использованием.

const passwords = [
  process.env.IRON_KEY_V3,
  process.env.IRON_KEY_V2,
  process.env.IRON_KEY_V1
];

Управление жизненным циклом ключей

Ротация становится безопасной только при наличии стратегии удаления старых секретов.

Этап 1: введение нового ключа

  • добавление нового секрета в начало массива
  • начало шифрования новых токенов новым ключом

Этап 2: переходный период

  • старые ключи продолжают участвовать в дешифровке
  • новые токены уже не используют старые ключи

Этап 3: вывод ключа из эксплуатации

  • удаление ключа возможно только после гарантированного истечения TTL всех токенов

Пример полной конфигурации

import Iron from '@hapi/iron';

const getPasswords = () => {
  return [
    process.env.IRON_KEY_CURRENT,
    process.env.IRON_KEY_PREVIOUS,
    process.env.IRON_KEY_OLD
  ].filter(Boolean);
};

export const sealData = async (data) => {
  const passwords = getPasswords();

  return Iron.seal(data, passwords[0], Iron.defaults);
};

export const unsealData = async (token) => {
  const passwords = getPasswords();

  return Iron.unseal(token, passwords, Iron.defaults);
};

Ротация в распределённых системах

При использовании нескольких инстансов приложения ключи должны синхронизироваться:

  • единый источник конфигурации (Vault, AWS SSM, Kubernetes Secrets)
  • атомарное обновление набора ключей
  • отсутствие частично обновлённых инстансов

Если один сервер использует новый ключ, а другой нет, система всё равно остаётся работоспособной, пока старые ключи не удалены.


Особенности поведения при ошибках

Если ключ удалён преждевременно:

  • все токены, зашифрованные им, становятся недействительными
  • пользователи теряют сессии
  • возникает массовая инвалидaция без механизма восстановления

Если ключи расположены в неправильном порядке:

  • деградация производительности (частые попытки дешифровки)
  • увеличение времени проверки токена

Оптимизация порядка ключей

Порядок массива влияет на скорость проверки:

  • новый ключ всегда первым
  • наиболее часто используемые старые ключи — выше в списке
  • устаревшие ключи — в конце массива

Типичная структура:

[
  'current',
  'previous',
  'older',
  'legacy'
]

Связь с TTL и сессиями

Ротация ключей должна учитывать срок жизни токенов:

  • короткий TTL упрощает удаление старых ключей
  • длинный TTL требует длительного периода поддержки старых секретов

Комбинация Iron + TTL позволяет безопасно обновлять ключи без механизма централизованного отзыва токенов.


Частичная ротация и перекрытие поколений

В устойчивых системах часто применяется перекрывающая модель:

  • ключ V1 активен для чтения
  • ключ V2 активен для записи
  • ключ V3 готовится к внедрению

Каждый следующий этап не ломает предыдущий, а накладывается поверх него, формируя непрерывную цепочку совместимости.


Поведение при масштабировании нагрузки

При большом количестве проверок токенов важны нюансы:

  • дешифровка выполняется линейным перебором ключей
  • увеличение числа ключей увеличивает worst-case latency
  • рекомендуется ограничивать количество активных ключей (обычно 2–4)

Iron часто используется через Hapi для защищённых cookie:

  • cookie содержит sealed payload
  • сервер расшифровывает его при каждом запросе
  • ротация ключей не требует сброса cookie у клиента

Пример конфигурации:

server.state('session', {
  isSecure: true,
  isHttpOnly: true,
  encoding: 'iron',
  password: passwords[0]
});

При ротации достаточно обновить массив паролей, используемый сервером.


Безопасное обновление ключей без прерывания сессий

Корректная схема обновления:

  • новый ключ добавляется в начало массива
  • старые остаются для расшифровки
  • генерация новых токенов происходит только с новым ключом
  • удаление старых ключей происходит после полного цикла жизни токенов

Такой подход исключает необходимость принудительной инвалидaции активных сессий и позволяет проводить ротацию незаметно для пользователей и без потери состояния системы