В Web Crypto API ключи, используемые в алгоритмах ECDH и ECDSA,
всегда существуют в строго определённой форме внутри криптографического
контекста браузера. Они не могут быть напрямую сериализованы через
JSON.stringify или переданы как обычные объекты JavaScript. Для этого
используется механизм экспорта и импорта через
SubtleCrypto.
Ключевая особенность: любой ключ создаётся как объект
CryptoKey, а его переносимость контролируется флагом
extractable.
const keyPair = await crypto.subtle.generateKey(
{
name: "ECDSA",
namedCurve: "P-256"
},
true,
["sign", "verify"]
);
Параметр true означает, что ключи можно экспортировать.
Если он установлен в false, любые попытки экспорта
завершатся ошибкой.
Web Crypto API поддерживает несколько стандартных форматов:
ECDH и ECDSA используют разные комбинации этих форматов:
| Тип ключа | Экспорт | Импорт |
|---|---|---|
| ECDSA public | SPKI, JWK | SPKI, JWK |
| ECDSA private | PKCS8, JWK | PKCS8, JWK |
| ECDH public | SPKI, JWK | SPKI, JWK |
| ECDH private | PKCS8, JWK | PKCS8, JWK |
ECDSA применяется для цифровой подписи. Обычно используется пара ключей: приватный для подписи и публичный для проверки.
const exportedPublicKey = await crypto.subtle.exportKey(
"spki",
keyPair.publicKey
);
console.log(new Uint8Array(exportedPublicKey));
SPKI-формат представляет ключ в бинарном виде. Он часто используется для передачи между системами или сохранения на сервере.
const jwkPublic = await crypto.subtle.exportKey(
"jwk",
keyPair.publicKey
);
console.log(jwkPublic);
JWK удобен тем, что представляет ключ как JSON:
{
"kty": "EC",
"crv": "P-256",
"x": "...",
"y": "..."
}
Приватный ключ используется для подписи данных и должен храниться особенно осторожно.
const exportedPrivateKey = await crypto.subtle.exportKey(
"pkcs8",
keyPair.privateKey
);
В бинарной форме PKCS8 ключ может быть сохранён, например, в IndexedDB или передан в зашифрованном виде.
JWK-экспорт приватного ключа:
const jwkPrivate = await crypto.subtle.exportKey(
"jwk",
keyPair.privateKey
);
Импорт позволяет восстановить ключ из ранее экспортированного представления.
const publicKey = await crypto.subtle.importKey(
"spki",
exportedPublicKey,
{
name: "ECDSA",
namedCurve: "P-256"
},
true,
["verify"]
);
const privateKey = await crypto.subtle.importKey(
"pkcs8",
exportedPrivateKey,
{
name: "ECDSA",
namedCurve: "P-256"
},
true,
["sign"]
);
const key = await crypto.subtle.importKey(
"jwk",
jwkPrivate,
{
name: "ECDSA",
namedCurve: "P-256"
},
true,
["sign"]
);
ECDH используется для установления общего секрета между двумя сторонами. Ключи ECDH почти всегда применяются в сценариях обмена ключами.
Генерация пары:
const ecdhKeys = await crypto.subtle.generateKey(
{
name: "ECDH",
namedCurve: "P-256"
},
true,
["deriveKey", "deriveBits"]
);
Публичный ключ отправляется другой стороне для вычисления общего секрета.
const exportedECDHPublic = await crypto.subtle.exportKey(
"spki",
ecdhKeys.publicKey
);
или в JSON-формате:
const jwkECDHPublic = await crypto.subtle.exportKey(
"jwk",
ecdhKeys.publicKey
);
const importedECDHPublic = await crypto.subtle.importKey(
"spki",
exportedECDHPublic,
{
name: "ECDH",
namedCurve: "P-256"
},
true,
[]
);
Обратите внимание: публичный ключ ECDH обычно не имеет usages, потому
что он используется только как вход для deriveKey или
deriveBits на стороне другой пары.
const exportedECDHPrivate = await crypto.subtle.exportKey(
"pkcs8",
ecdhKeys.privateKey
);
const importedECDHPrivate = await crypto.subtle.importKey(
"pkcs8",
exportedECDHPrivate,
{
name: "ECDH",
namedCurve: "P-256"
},
true,
["deriveKey", "deriveBits"]
);
После импорта ключей они используются для получения общего секрета:
const sharedSecret = await crypto.subtle.deriveBits(
{
name: "ECDH",
public: importedECDHPublic
},
importedECDHPrivate,
256
);
Результатом является буфер с байтами общего секрета, который далее используется как основа для симметричного шифрования (AES-GCM и др.).
JWK-формат часто оказывается наиболее удобным для веб-приложений, так как позволяет:
Пример ECDSA JWK:
{
"kty": "EC",
"crv": "P-256",
"x": "base64url...",
"y": "base64url...",
"d": "base64url..."
}
Поле d присутствует только у приватного ключа.
Флаг extractable определяет возможность экспорта:
true — ключ можно сериализоватьfalse — ключ остаётся внутри браузераПрактика безопасности:
ECDSA и ECDH используют одну математическую основу (эллиптические кривые), но Web Crypto различает их контекст использования:
name и
namedCurveЧастые причины ошибок:
P-256 vs
P-384)Пример ошибки:
DataError: algorithm is not recognized or key format is invalid
В реальных протоколах часто комбинируются оба алгоритма:
Типичный поток: