В реальных системах редко применяется одиночный криптографический примитив. Даже стойкие алгоритмы становятся уязвимыми при неправильном использовании или недостаточном наборе гарантий. Поэтому практическая криптография строится на композиции нескольких механизмов: шифрования, обмена ключами, аутентификации, деривации ключей и защиты целостности.
Web Crypto API предоставляет низкоуровневые операции, из которых
собираются составные схемы. Библиотека не навязывает архитектуру, но
предоставляет строительные блоки: SubtleCrypto.encrypt,
decrypt, sign, verify,
deriveKey, importKey, wrapKey,
unwrapKey.
Составная схема объединяет несколько задач:
Один алгоритм редко покрывает всё сразу. Например:
Композиция позволяет строить безопасные системы из ограниченных компонентов.
Классическая модель составных схем — гибридное шифрование:
В Web Crypto API это реализуется через комбинацию ECDH или RSA + AES-GCM.
Один из наиболее распространённых паттернов — согласование общего секрета через ECDH и дальнейшее шифрование симметричным ключом.
const aliceKeys = await crypto.subtle.generateKey(
{
name: "ECDH",
namedCurve: "P-256"
},
true,
["deriveKey"]
);
const bobKeys = await crypto.subtle.generateKey(
{
name: "ECDH",
namedCurve: "P-256"
},
true,
["deriveKey"]
);
const sharedKey = await crypto.subtle.deriveKey(
{
name: "ECDH",
public: bobKeys.publicKey
},
aliceKeys.privateKey,
{
name: "AES-GCM",
length: 256
},
false,
["encrypt", "decrypt"]
);
const iv = crypto.getRandomValues(new Uint8Array(12));
const ciphertext = await crypto.subtle.encrypt(
{
name: "AES-GCM",
iv
},
sharedKey,
new TextEncoder().encode("секретное сообщение")
);
Такая схема обеспечивает:
Альтернативный подход — использование RSA для защиты симметричного ключа.
const rsaKeys = await crypto.subtle.generateKey(
{
name: "RSA-OAEP",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256"
},
true,
["encrypt", "decrypt"]
);
const aesKey = await crypto.subtle.generateKey(
{
name: "AES-GCM",
length: 256
},
true,
["encrypt", "decrypt"]
);
const wrappedKey = await crypto.subtle.wrapKey(
"raw",
aesKey,
rsaKeys.publicKey,
{
name: "RSA-OAEP"
}
);
const unwrappedKey = await crypto.subtle.unwrapKey(
"raw",
wrappedKey,
rsaKeys.privateKey,
{
name: "RSA-OAEP"
},
{
name: "AES-GCM",
length: 256
},
true,
["encrypt", "decrypt"]
);
Такая схема часто используется в:
Современные схемы стремятся к минимизации количества примитивов. AES-GCM объединяет:
const encrypted = await crypto.subtle.encrypt(
{
name: "AES-GCM",
iv,
additionalData: new TextEncoder().encode("header")
},
key,
data
);
Параметр additionalData позволяет включать несекретные,
но защищённые данные (AAD), которые участвуют в проверке
целостности.
До появления AEAD-режимов часто применялась схема:
const ciphertext = await crypto.subtle.encrypt(
{ name: "AES-CBC", iv },
encKey,
data
);
const mac = await crypto.subtle.sign(
{ name: "HMAC" },
macKey,
ciphertext
);
Проверка:
const valid = await crypto.subtle.verify(
{ name: "HMAC" },
macKey,
mac,
ciphertext
);
Web Crypto API поддерживает HMAC отдельно, что позволяет строить такие композиции, хотя AES-GCM делает их избыточными в новых системах.
Ключи редко используются напрямую. Обычно они выводятся из мастер-секрета через HKDF.
const derivedKey = await crypto.subtle.deriveKey(
{
name: "HKDF",
salt: crypto.getRandomValues(new Uint8Array(16)),
info: new TextEncoder().encode("encryption context"),
hash: "SHA-256"
},
masterKey,
{
name: "AES-GCM",
length: 256
},
false,
["encrypt", "decrypt"]
);
HKDF используется для:
Составные схемы часто строятся как иерархия:
DEK шифрует данные, KEK защищает DEK.
const wrappedDEK = await crypto.subtle.wrapKey(
"raw",
dek,
kek,
{ name: "AES-KW" }
);
AES-KW (Key Wrap) используется для безопасного хранения ключей без необходимости RSA.
Композиция подписи и шифрования требует строгого порядка операций.
const signature = await crypto.subtle.sign(
{ name: "ECDSA", hash: "SHA-256" },
privateKey,
data
);
const payload = new Uint8Array([...data, ...new Uint8Array(signature)]);
const encrypted = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv },
encKey,
data
);
const signature = await crypto.subtle.sign(
{ name: "ECDSA", hash: "SHA-256" },
signingKey,
encrypted
);
В современных архитектурах предпочтение отдается encrypt-then-sign, так как подпись применяется к защищённым данным.
Составные криптографические схемы часто включают несколько уровней защиты:
Каждый слой использует собственные ключи и контекст.
Пример изоляции:
Типовые проблемы возникают не в алгоритмах, а в композиции:
Web Crypto API снижает вероятность ошибок, но не устраняет архитектурные риски.
Наиболее устойчивый подход включает:
Такая комбинация формирует типовую современную криптографическую архитектуру в браузерных приложениях без зависимости от внешних библиотек.