Envelope encryption: концепция и реализация

Шифрование с использованием схемы envelope encryption (конвертного шифрования) в Web Crypto API основано на разделении ролей ключей: данные шифруются симметричным ключом, а сам симметричный ключ защищается асимметричной или мастер-ключевой системой. Такой подход позволяет безопасно масштабировать хранение и передачу зашифрованных данных, не теряя производительности и сохраняя удобство управления ключами.


В основе модели лежат два типа ключей:

Data Encryption Key (DEK) — ключ, который непосредственно шифрует данные. Key Encryption Key (KEK) — ключ, который защищает DEK.

Схема работы выглядит следующим образом:

  • генерируется случайный DEK
  • DEK используется для шифрования данных (обычно AES-GCM)
  • DEK шифруется KEK (RSA-OAEP, ECDH-derived key или другой механизм)
  • зашифрованные данные + зашифрованный DEK сохраняются вместе

Ключевой смысл подхода — минимизация риска: компрометация KEK не раскрывает данные напрямую, а компрометация DEK ограничена конкретным набором данных.


Криптографические примитивы Web Crypto API

Web Crypto API предоставляет низкоуровневые примитивы через window.crypto.subtle.

Основные операции, используемые в envelope encryption:

  • generateKey
  • encrypt
  • decrypt
  • wrapKey
  • unwrapKey
  • importKey
  • exportKey

На практике чаще всего применяются:

AES-GCM — для симметричного шифрования данных RSA-OAEP — для защиты симметричных ключей ECDH + HKDF — как альтернатива для построения KEK


Генерация ключевой иерархии

Генерация KEK (RSA-OAEP)

const kekPair = await crypto.subtle.generateKey(
  {
    name: "RSA-OAEP",
    modulusLength: 2048,
    publicExponent: new Uint8Array([1, 0, 1]),
    hash: "SHA-256"
  },
  true,
  ["encrypt", "decrypt"]
);

KEK состоит из публичной и приватной части:

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

Генерация DEK (AES-GCM)

const dek = await crypto.subtle.generateKey(
  {
    name: "AES-GCM",
    length: 256
  },
  true,
  ["encrypt", "decrypt"]
);

DEK — временный ключ, используемый только для шифрования данных.


Шифрование данных с помощью DEK

const iv = crypto.getRandomValues(new Uint8Array(12));

const encodedData = new TextEncoder().encode("секретные данные");

const ciphertext = await crypto.subtle.encrypt(
  {
    name: "AES-GCM",
    iv
  },
  dek,
  encodedData
);

Особенности AES-GCM:

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

Упаковка DEK с помощью KEK

Web Crypto API предоставляет два подхода: ручной encrypt или специализированный wrapKey.

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

const wrappedDEK = await crypto.subtle.wrapKey(
  "jwk",
  dek,
  kekPair.publicKey,
  {
    name: "RSA-OAEP"
  }
);

Важные моменты:

  • формат "jwk" означает экспортируемое представление ключа
  • результат — бинарный буфер зашифрованного DEK
  • используется публичный KEK

Распаковка DEK

const unwrappedDEK = await crypto.subtle.unwrapKey(
  "jwk",
  wrappedDEK,
  kekPair.privateKey,
  {
    name: "RSA-OAEP"
  },
  {
    name: "AES-GCM",
    length: 256
  },
  true,
  ["encrypt", "decrypt"]
);

Процесс включает:

  • дешифрование DEK приватным ключом
  • восстановление CryptoKey объекта
  • восстановление прав доступа (encrypt/decrypt)

Полный цикл envelope encryption

Объединённый процесс выглядит следующим образом:

1. Генерация ключей

const kek = await crypto.subtle.generateKey(
  {
    name: "RSA-OAEP",
    modulusLength: 2048,
    publicExponent: new Uint8Array([1, 0, 1]),
    hash: "SHA-256"
  },
  true,
  ["encrypt", "decrypt"]
);

const dek = await crypto.subtle.generateKey(
  {
    name: "AES-GCM",
    length: 256
  },
  true,
  ["encrypt", "decrypt"]
);

2. Шифрование данных

const iv = crypto.getRandomValues(new Uint8Array(12));
const data = new TextEncoder().encode("секрет");

const encryptedData = await crypto.subtle.encrypt(
  {
    name: "AES-GCM",
    iv
  },
  dek,
  data
);

3. Упаковка ключа

const wrappedKey = await crypto.subtle.wrapKey(
  "jwk",
  dek,
  kek.publicKey,
  {
    name: "RSA-OAEP"
  }
);

4. Хранение структуры

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

{
  "iv": "...",
  "ciphertext": "...",
  "wrappedKey": "..."
}

IV обычно хранится в открытом виде, так как не является секретом.


Восстановление данных

1. Распаковка DEK

const dek = await crypto.subtle.unwrapKey(
  "jwk",
  wrappedKey,
  kek.privateKey,
  {
    name: "RSA-OAEP"
  },
  {
    name: "AES-GCM",
    length: 256
  },
  true,
  ["decrypt"]
);

2. Дешифрование данных

const decrypted = await crypto.subtle.decrypt(
  {
    name: "AES-GCM",
    iv
  },
  dek,
  encryptedData
);

const plaintext = new TextDecoder().decode(decrypted);

Причины использования envelope encryption

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

Один KEK может защищать множество DEK, что снижает:

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

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

KEK можно менять без повторного шифрования всех данных:

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

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

AES-GCM значительно быстрее RSA:

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

Альтернативная схема: ECDH + HKDF

RSA может быть заменён на:

  • ECDH для обмена ключами
  • HKDF для деривации KEK

Это снижает размер ключей и повышает эффективность в некоторых сценариях, особенно в браузере.


Особенности Web Crypto API

1. Непрозрачность ключей

CryptoKey нельзя напрямую прочитать — только экспортировать:

await crypto.subtle.exportKey("jwk", key);

2. Ограничения использования

Ключи создаются с набором разрешений:

["encrypt", "decrypt"]

Неправильная конфигурация приводит к ошибкам выполнения.


3. Security Context

Web Crypto API работает только:

  • в HTTPS контексте
  • в secure origins (localhost допускается)

4. Невозможность повторного использования IV

Особенно критично для AES-GCM:

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

Типичные ошибки реализации

Повторное использование IV

Это одна из наиболее опасных ошибок:

  • приводит к раскрытию XOR-связей между сообщениями
  • делает AES-GCM уязвимым

Хранение DEK без защиты

Если DEK хранится без wrapping:

  • envelope encryption теряет смысл
  • вся модель безопасности рушится

Использование слабого KEK

RSA-1024 или некорректные параметры делают систему уязвимой к факторизации.


Практическая структура хранения в приложениях

Часто используется комбинированный формат:

  • wrappedKey — Base64 или ArrayBuffer
  • iv — случайный nonce
  • ciphertext — бинарные данные AES-GCM
  • alg — описание алгоритмов

Пример:

{
  "alg": "RSA-OAEP + AES-GCM",
  "iv": "base64...",
  "wrappedKey": "base64...",
  "ciphertext": "base64..."
}

Масштабируемые сценарии применения

Envelope encryption применяется в:

  • облачных хранилищах
  • клиентском end-to-end шифровании
  • защищённых браузерных хранилищах
  • криптографических SDK

Итоговая архитектурная модель

  • KEK защищает DEK
  • DEK защищает данные
  • Web Crypto API выполняет все операции без доступа к сырому ключевому материалу
  • безопасность строится на разделении ответственности и минимизации зоны компрометации