Web Crypto API проектирует работу с ключами так, чтобы минимизировать
возможность их утечки из безопасного контекста. В отличие от «сырых»
криптографических библиотек, где ключи часто представлены в виде обычных
байтовых массивов, в браузере ключи существуют как объект
CryptoKey, управляемый рантаймом.
Каждый ключ в Web Crypto API характеризуется набором свойств:
keyUsages)extractable)Ключевым элементом этой модели является флаг
extractable, который определяет, можно ли получить «сырой»
вид ключа через экспорт.
extractableФлаг extractable задаётся при создании или импорте
ключа:
crypto.subtle.generateKeycrypto.subtle.importKeyОн принимает булево значение:
true — ключ можно экспортироватьfalse — ключ нельзя экспортировать никаким
способомПример генерации:
const key = await crypto.subtle.generateKey(
{
name: "AES-GCM",
length: 256
},
false, // extractable
["encrypt", "decrypt"]
);
Если значение установлено в false, браузер гарантирует,
что ключ нельзя будет получить в виде массива байтов или
JSON-представления.
Web Crypto API поддерживает экспорт через:
crypto.subtle.exportKeycrypto.subtle.wrapKeyПоддерживаемые форматы зависят от типа ключа:
"raw" — байтовый массив (ArrayBuffer)"jwk" — JSON Web Key"spki" — публичный ключ"pkcs8" — приватный ключ"jwk" — JSON Web KeyПример экспорта:
const exported = await crypto.subtle.exportKey("raw", key);
extractable = falseЕсли ключ создан с параметром:
extractable: false
то любая попытка экспорта приводит к ошибке:
const keyPair = await crypto.subtle.generateKey(
{
name: "RSA-PSS",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256"
},
false,
["sign"]
);
await crypto.subtle.exportKey("pkcs8", keyPair.privateKey);
// InvalidAccessError
jwk становится недоступнымФлаг extractable является одним из ключевых механизмов
защиты:
Даже если злоумышленник получил доступ к выполнению скрипта на странице (XSS), он не сможет извлечь ключ, если он неэкстрагируемый.
extractable = true ключ можно:
extractable = false ключ можно только:
crypto.subtleНаиболее критичный сценарий — приватные ключи RSA/ECDSA:
crypto.subtle.generateKey(
{
name: "ECDSA",
namedCurve: "P-256"
},
false,
["sign"]
);
В этом случае приватный ключ нельзя извлечь даже в виде
pkcs8.
extractable нельзя изменитьПосле создания ключа значение extractable становится
неизменяемым.
Невозможно:
Единственный способ получить другой режим — создать новый ключ.
При импорте ключа флаг также фиксируется:
const key = await crypto.subtle.importKey(
"jwk",
jwkData,
{ name: "AES-GCM" },
true,
["encrypt"]
);
Даже если исходный ключ был экспортируемым, после импорта можно принудительно задать:
extractable: false
Это позволяет «заблокировать» ключ сразу после загрузки.
Некоторые ограничения связаны не только с флагом, но и с алгоритмом:
Например:
| Тип ключа | Возможный экспорт |
|---|---|
| AES | raw, jwk |
| RSA public | spki, jwk |
| RSA private | pkcs8, jwk |
| ECDSA | spki/pkcs8, jwk |
Даже если ключ неэкстрагируемый, он может быть обёрнут:
const wrapped = await crypto.subtle.wrapKey(
"raw",
key,
wrappingKey,
"AES-GCM"
);
Но важно:
wrapKey не раскрывает сам ключЧасто ключи сохраняются через IndexedDB:
CryptoKey
объектПример:
const db = indexedDB.open("keys", 1);
// CryptoKey сохраняется напрямую
store.put(key, "my-key");
Браузер сериализует ключ в защищённом формате, недоступном для JavaScript.
Если ключ создан как non-extractable:
Такие ключи фактически привязаны к:
Если ключ удалён или профиль повреждён:
const keyPair = await crypto.subtle.generateKey(
{
name: "RSASSA-PKCS1-v1_5",
modulusLength: 2048,
publicExponent: new Uint8Array([1, 0, 1]),
hash: "SHA-256"
},
false,
["sign", "verify"]
);
Приватный ключ используется только внутри браузера.
AES ключи для локального шифрования:
await crypto.subtle.generateKey(
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"]
);
Флаг extractable — это не просто ограничение API, а
часть модели доверия:
Это приближает Web Crypto API к аппаратным модулям безопасности (HSM-подобная модель), но на уровне программной среды браузера.