Приложение шифрует чувствительные поля (номера документов, телефоны, токены) через API Stronghold и сохраняет в базе данных только шифртекст. Ключ шифрования не покидает Stronghold, поэтому утечка дампа базы данных не раскрывает данные.

Цель

Настроить ключ Transit для приложения, зашифровать и расшифровать поле, применить конвертное шифрование для больших объектов, выполнить ротацию ключа, перешифровать данные (rewrap) и запретить расшифровку старыми версиями ключа.

Предварительные требования

Для выполнения примера нужны:

  • Токен Stronghold с правами на включение механизмов секретов и создание политик.
  • Утилиты base64, jq, openssl и xxd для примеров.

Шаг 1. Создайте ключ

Включите механизм секретов Transit и создайте ключ:

  1. Включите механизм секретов:

    d8 stronghold secrets enable transit
  2. Создайте именованный ключ для приложения:

    d8 stronghold write -f transit/keys/orders type=aes256-gcm96

    Используйте отдельный ключ для каждого приложения или набора данных.

Шаг 2. Разделите права

Создайте отдельные политики для приложения и для задания перешифрования:

  1. Политика приложения — только шифрование и расшифровка:

    d8 stronghold policy write orders-app - <<'POLICY'
    path "transit/encrypt/orders" {
      capabilities = ["update"]
    }
    path "transit/decrypt/orders" {
      capabilities = ["update"]
    }
    path "transit/datakey/plaintext/orders" {
      capabilities = ["update"]
    }
    POLICY
  2. Политика задачи перешифрования — только rewrap, без доступа к открытым данным:

    d8 stronghold policy write orders-rewrap - <<'POLICY'
    path "transit/rewrap/orders" {
      capabilities = ["update"]
    }
    path "transit/keys/orders" {
      capabilities = ["read"]
    }
    POLICY

Управление ключом (transit/keys/orders/*) оставьте администраторам.

Шаг 3. Зашифруйте и расшифруйте поле

Данные передаются в Stronghold в кодировке base64.

  1. Зашифруйте значение:

    d8 stronghold write -field=ciphertext transit/encrypt/orders plaintext=$(echo -n "4509 123456" | base64)

    Результат вида vault:v1:... сохраните в столбце таблицы. Префикс v1 — версия ключа, которой выполнено шифрование.

  2. Расшифруйте значение:

    d8 stronghold write -field=plaintext transit/decrypt/orders ciphertext="vault:v1:..." | base64 --decode

Для обработки множества записей за один запрос используйте параметр batch_input эндпоинтов encrypt, decrypt и rewrap.

Шаг 4. Используйте конвертное шифрование для больших данных

Передавать через API файлы и большие объекты неэффективно. Вместо этого запросите ключ данных (data key): Stronghold возвращает его в открытом виде для локального шифрования и в зашифрованном виде для хранения.

  1. Получите ключ данных:

    d8 stronghold write -format=json -f transit/datakey/plaintext/orders > datakey.json
    jq -r '.data.plaintext' datakey.json | base64 --decode > dek.bin
    jq -r '.data.ciphertext' datakey.json > dek.enc
  2. Зашифруйте файл локально и удалите открытый ключ:

    openssl rand -hex 16 > report.pdf.iv
    openssl enc -aes-256-cbc -K "$(xxd -p -c 64 dek.bin)" -iv "$(cat report.pdf.iv)" -in report.pdf -out report.pdf.enc
    shred -u dek.bin

    Ключ данных имеет длину 256 бит и передаётся в openssl как AES-ключ в шестнадцатеричном виде через параметр -K. Файл report.pdf.iv содержит случайный вектор инициализации (IV). Генерируйте новый IV для каждого файла. Сохраните report.pdf.enc вместе с dek.enc и report.pdf.iv.

  3. Для расшифровки восстановите ключ данных через Transit и расшифруйте файл с тем же IV:

    d8 stronghold write -field=plaintext transit/decrypt/orders ciphertext="$(cat dek.enc)" | base64 --decode > dek.bin
    openssl enc -d -aes-256-cbc -K "$(xxd -p -c 64 dek.bin)" -iv "$(cat report.pdf.iv)" -in report.pdf.enc -out report.pdf
    shred -u dek.bin

Если ключ данных нужен только для хранения (например, его расшифрует другой сервис), используйте transit/datakey/wrapped/orders — ответ не содержит открытого ключа.

Шаг 5. Выполните ротацию ключа

Выполните ротацию ключа вручную или настройте автоматическую ротацию:

  1. Создайте новую версию ключа:

    d8 stronghold write -f transit/keys/orders/rotate

    Новые данные шифруются последней версией, старые по-прежнему расшифровываются.

  2. Чтобы ротация выполнялась автоматически, задайте период:

    d8 stronghold write transit/keys/orders/config auto_rotate_period=720h

Шаг 6. Перешифруйте данные (rewrap)

Rewrap перешифровывает шифртекст последней версией ключа без раскрытия открытых данных. Выполните его для всех записей таблицы, например фоновой задачей с политикой orders-rewrap:

d8 stronghold write -field=ciphertext transit/rewrap/orders ciphertext="vault:v1:..."

Результат vault:v2:... запишите обратно в столбец.

Шаг 7. Запретите расшифровку старыми версиями

Когда в базе данных не осталось шифртекста со старыми версиями, поднимите минимальную версию для расшифровки:

d8 stronghold write transit/keys/orders/config min_decryption_version=2

Версии ниже min_decryption_version переносятся в архив и не используются для расшифровки. В экстренном случае значение можно вернуть назад.

Если старые версии нужно удалить окончательно, используйте transit/keys/orders/trim с параметром min_available_version. Это действие необратимо.

Проверка

Проверьте результат:

  1. Просмотрите состояние ключа:

    d8 stronghold read transit/keys/orders

    Проверьте значения latest_version, min_decryption_version и auto_rotate_period.

  2. Попробуйте расшифровать шифртекст vault:v1:... после шага 7 — запрос должен завершиться ошибкой.

  3. Убедитесь, что токен с политикой orders-rewrap не может выполнить transit/decrypt/orders.

Очистка

Удалите объекты, созданные в примере:

  1. Удалите политики: d8 stronghold policy delete orders-app и d8 stronghold policy delete orders-rewrap.

  2. Удалите локальные файлы datakey.json, dek.enc, report.pdf.iv, report.pdf.enc.

  3. Для удаления тестового ключа разрешите удаление и удалите ключ:

    d8 stronghold write transit/keys/orders/config deletion_allowed=true
    d8 stronghold delete transit/keys/orders

    Данные, зашифрованные этим ключом, станут недоступны.

Дополнительные ресурсы