Шифрование с использованием схемы envelope encryption (конвертного шифрования) в Web Crypto API основано на разделении ролей ключей: данные шифруются симметричным ключом, а сам симметричный ключ защищается асимметричной или мастер-ключевой системой. Такой подход позволяет безопасно масштабировать хранение и передачу зашифрованных данных, не теряя производительности и сохраняя удобство управления ключами.
В основе модели лежат два типа ключей:
Data Encryption Key (DEK) — ключ, который непосредственно шифрует данные. Key Encryption Key (KEK) — ключ, который защищает DEK.
Схема работы выглядит следующим образом:
Ключевой смысл подхода — минимизация риска: компрометация KEK не раскрывает данные напрямую, а компрометация DEK ограничена конкретным набором данных.
Web Crypto API предоставляет низкоуровневые примитивы через
window.crypto.subtle.
Основные операции, используемые в envelope encryption:
generateKeyencryptdecryptwrapKeyunwrapKeyimportKeyexportKeyНа практике чаще всего применяются:
AES-GCM — для симметричного шифрования данных RSA-OAEP — для защиты симметричных ключей ECDH + HKDF — как альтернатива для построения KEK
const kekPair = await crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256"
},
true,
["encrypt", "decrypt"]
);
KEK состоит из публичной и приватной части:
const dek = await crypto.subtle.generateKey(
{
name: "AES-GCM",
length: 256
},
true,
["encrypt", "decrypt"]
);
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:
Web Crypto API предоставляет два подхода: ручной encrypt
или специализированный wrapKey.
const wrappedDEK = await crypto.subtle.wrapKey(
"jwk",
dek,
kekPair.publicKey,
{
name: "RSA-OAEP"
}
);
Важные моменты:
"jwk" означает экспортируемое представление
ключаconst unwrappedDEK = await crypto.subtle.unwrapKey(
"jwk",
wrappedDEK,
kekPair.privateKey,
{
name: "RSA-OAEP"
},
{
name: "AES-GCM",
length: 256
},
true,
["encrypt", "decrypt"]
);
Процесс включает:
Объединённый процесс выглядит следующим образом:
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"]
);
const iv = crypto.getRandomValues(new Uint8Array(12));
const data = new TextEncoder().encode("секрет");
const encryptedData = await crypto.subtle.encrypt(
{
name: "AES-GCM",
iv
},
dek,
data
);
const wrappedKey = await crypto.subtle.wrapKey(
"jwk",
dek,
kek.publicKey,
{
name: "RSA-OAEP"
}
);
Типичная структура хранения:
{
"iv": "...",
"ciphertext": "...",
"wrappedKey": "..."
}
IV обычно хранится в открытом виде, так как не является секретом.
const dek = await crypto.subtle.unwrapKey(
"jwk",
wrappedKey,
kek.privateKey,
{
name: "RSA-OAEP"
},
{
name: "AES-GCM",
length: 256
},
true,
["decrypt"]
);
const decrypted = await crypto.subtle.decrypt(
{
name: "AES-GCM",
iv
},
dek,
encryptedData
);
const plaintext = new TextDecoder().decode(decrypted);
Один KEK может защищать множество DEK, что снижает:
KEK можно менять без повторного шифрования всех данных:
AES-GCM значительно быстрее RSA:
RSA может быть заменён на:
Это снижает размер ключей и повышает эффективность в некоторых сценариях, особенно в браузере.
CryptoKey нельзя напрямую прочитать — только экспортировать:
await crypto.subtle.exportKey("jwk", key);
Ключи создаются с набором разрешений:
["encrypt", "decrypt"]
Неправильная конфигурация приводит к ошибкам выполнения.
Web Crypto API работает только:
Особенно критично для AES-GCM:
Это одна из наиболее опасных ошибок:
Если DEK хранится без wrapping:
RSA-1024 или некорректные параметры делают систему уязвимой к факторизации.
Часто используется комбинированный формат:
wrappedKey — Base64 или ArrayBufferiv — случайный nonceciphertext — бинарные данные AES-GCMalg — описание алгоритмовПример:
{
"alg": "RSA-OAEP + AES-GCM",
"iv": "base64...",
"wrappedKey": "base64...",
"ciphertext": "base64..."
}
Envelope encryption применяется в: