Общие ключи между сервисами

В распределённых системах, где несколько сервисов взаимодействуют друг с другом, возникает необходимость безопасной передачи данных и проверки их подлинности. Общие ключи (shared secrets) — это фундаментальный механизм, позволяющий сервисам:

  • шифровать и расшифровывать данные;
  • подписывать сообщения;
  • проверять целостность и источник данных;
  • реализовывать аутентификацию без хранения состояния.

Библиотека Iron в экосистеме JavaScript предоставляет удобный способ работы с такими ключами, реализуя симметричное шифрование и защиту данных.


Принцип работы Iron

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

  • seal — упаковка (шифрование + подпись)
  • unseal — распаковка (расшифровка + проверка)

Пример:

const Iron = require('@hapi/iron');

const password = 'super-secret-key';

const data = { userId: 123 };

async function run() {
    const sealed = await Iron.seal(data, password, Iron.defaults);
    const unsealed = await Iron.unseal(sealed, password, Iron.defaults);

    console.log(unsealed);
}

run();

Архитектура использования между сервисами

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

  1. Все сервисы знают один и тот же секрет.
  2. Один сервис шифрует данные.
  3. Другой сервис расшифровывает и проверяет их.

Пример сценария:

  • API Gateway генерирует токен
  • Backend-сервис проверяет токен без обращения к базе

Формирование общего секрета

Общий ключ должен соответствовать строгим требованиям безопасности:

  • длина не менее 32 байт;
  • высокая энтропия (не использовать простые строки);
  • хранение вне кода (например, в переменных окружения).

Пример:

IRON_SECRET=3f9c2e7b8a4d... (длинная случайная строка)

Использование:

const password = process.env.IRON_SECRET;

Конфигурация параметров шифрования

Iron предоставляет набор настроек, которые можно кастомизировать:

const options = {
    encryption: {
        saltBits: 256,
        algorithm: 'aes-256-cbc',
        iterations: 100000,
        minPasswordlength: 32
    },
    integrity: {
        saltBits: 256,
        algorithm: 'sha256',
        iterations: 100000
    },
    ttl: 0
};

Ключевые параметры:

  • algorithm — алгоритм шифрования
  • iterations — число итераций для PBKDF2
  • ttl — время жизни зашифрованных данных

Синхронизация ключей между сервисами

Для корректной работы необходимо обеспечить:

  • одинаковый секрет на всех сервисах;
  • одинаковые настройки Iron;
  • синхронное обновление ключей при ротации.

Способы распространения ключа:

  • переменные окружения;
  • системы управления секретами (Vault, AWS Secrets Manager);
  • конфигурационные серверы.

Ротация ключей

Использование одного ключа длительное время увеличивает риск компрометации. Практика ротации:

  1. Ввод нового ключа (primary)
  2. Поддержка старого ключа (secondary)
  3. Постепенное обновление данных

Iron напрямую не поддерживает multiple keys, но можно реализовать вручную:

const keys = ['new-secret', 'old-secret'];

async function unsealWithFallback(sealed) {
    for (const key of keys) {
        try {
            return await Iron.unseal(sealed, key, Iron.defaults);
        } catch (err) {}
    }
    throw new Error('Invalid token');
}

Передача зашифрованных данных

Типичные способы передачи:

  • HTTP-заголовки
  • Cookies
  • Query-параметры
  • Тело запроса

Пример cookie:

const sealed = await Iron.seal(session, password, Iron.defaults);

res.setHeader('Set-Cookie', `session=${sealed}; HttpOnly; Secure`);

Проверка целостности данных

Iron автоматически добавляет подпись (HMAC), которая позволяет:

  • обнаружить изменение данных;
  • защититься от атак подмены;
  • гарантировать, что данные пришли от доверенного источника.

Если данные были изменены, unseal выбросит ошибку.


Ограничение времени жизни (TTL)

Можно задать срок действия данных:

const options = {
    ...Iron.defaults,
    ttl: 60 * 60 * 1000 // 1 час
};

После истечения TTL расшифровка завершится ошибкой.


Использование в микросервисной архитектуре

Общие ключи особенно полезны в:

  • stateless-аутентификации;
  • обмене внутренними токенами;
  • защите межсервисных сообщений;
  • кэшировании данных.

Пример: токен между сервисами

Сервис A:

const token = await Iron.seal({ service: 'A' }, password, Iron.defaults);

Сервис B:

const payload = await Iron.unseal(token, password, Iron.defaults);

Безопасность и риски

Использование общего ключа имеет ограничения:

  • компрометация одного сервиса = компрометация всех;
  • сложность ротации;
  • невозможность разграничения доступа между сервисами.

Меры защиты:

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

Разделение ключей по назначению

Рекомендуется не использовать один ключ для всех задач:

  • отдельный ключ для сессий;
  • отдельный ключ для межсервисных токенов;
  • отдельный ключ для cookies.
const sessionKey = process.env.SESSION_SECRET;
const apiKey = process.env.API_SECRET;

Формат зашифрованной строки

Результат seal — это строка, содержащая:

  • зашифрованные данные;
  • salt;
  • IV (инициализационный вектор);
  • подпись.

Пример (сокращённый):

Fe26.2**...encrypted_data...**...hmac...

Этот формат самодостаточен — дополнительных данных для расшифровки не требуется (кроме ключа).


Производительность

Основные факторы влияния:

  • количество итераций (PBKDF2);
  • размер данных;
  • частота операций.

Оптимизация:

  • уменьшение iterations (с осторожностью);
  • кэширование результатов;
  • избегание частого повторного шифрования одних и тех же данных.

Сравнение с альтернативами

Подход Особенности
Iron Простота, встроенная защита
JWT Подпись без шифрования (по умолчанию)
AES напрямую Требует ручной реализации
OAuth / OAuth2 Сложнее, но более гибко

Iron особенно удобен, когда:

  • требуется простая симметричная защита;
  • нет необходимости в асимметричной криптографии;
  • важна минимальная инфраструктура.

Практические рекомендации

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

Типичные ошибки

Использование короткого ключа

const password = '12345'; // небезопасно

Несогласованные настройки

// разные алгоритмы на сервисах → ошибки

Отсутствие TTL

ttl: 0 // данные живут бесконечно

Расширенные сценарии

Подпись без шифрования

Iron не предназначен только для подписи, но можно хранить минимальные данные и использовать его как защищённый контейнер.

Защита cookies

Iron часто используется в связке с HTTP cookies для хранения сессий без сервера.

Внутренние API-токены

Позволяет отказаться от централизованного хранилища токенов.


Масштабирование

При росте системы:

  • вводятся разные ключи для разных доменов;
  • используется централизованное управление секретами;
  • внедряется автоматическая ротация.

Итоговая схема взаимодействия

  1. Сервисы получают общий ключ из безопасного источника
  2. Один сервис вызывает seal
  3. Данные передаются через сеть
  4. Другой сервис вызывает unseal
  5. Проверяется подпись и TTL
  6. Используются исходные данные

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