Перейти к содержимому
ecdsa.com
WebCrypto SubtleCrypto.exportKey / wrapKeyWebCrypto

Сообщение об ошибке

InvalidAccessError: Failed to execute 'exportKey' on 'SubtleCrypto': key is not extractable

Также выводится как

  • InvalidAccessError: The CryptoKey is nonextractable
  • InvalidAccessError: A parameter or an operation is not supported by the underlying object

Выше приведён текст Chromium; Safari/WebKit: «The CryptoKey is nonextractable»; Firefox: «A parameter or an operation is not supported by the underlying object». Во всех случаях это DOMException `InvalidAccessError`.

WebCrypto SubtleCrypto.exportKey / wrapKey — DOMException InvalidAccessError (Chromium / Firefox / WebKit)

Что это означает

Ключ создан с `extractable: false`, поэтому его ключевой материал никогда не удастся прочитать обратно — ни через `exportKey`, ни через `wrapKey`. В этом и смысл флага: закрытый ключ остаётся внутри криптографической реализации браузера, и скрипт (в том числе внедрённый) может его использовать, но не может украсть. Открытые ключи сгенерированной пары экспортируются всегда; эта ошибка касается закрытых и секретных ключей.

Почему это происходит

Как исправить

  1. 1.

    Храните закрытый ключ как объект, а экспортируйте открытый ключ

    Храните неизвлекаемые ключи в IndexedDB как объекты `CryptoKey` и передавайте другим только открытый ключ (SPKI или JWK). Так сохраняется защита, которую даёт флаг.

    js
    const pair = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, false, ["sign", "verify"]);
    // Persist the non-extractable private key as a CryptoKey object (structured clone), never as bytes:
    await idbPut("signing-key", pair.privateKey);
    // Share only the public key:
    const jwk = await crypto.subtle.exportKey("jwk", pair.publicKey);
  2. 2.

    Создавайте извлекаемые ключи, только если экспорт действительно необходим

    Если закрытый ключ действительно должен покинуть браузер, сгенерируйте его извлекаемым и экспортируйте только обёрнутым другим ключом.

    js
    // Only if the private key must leave the browser (e.g. a user-initiated, encrypted backup):
    const pair = await crypto.subtle.generateKey({ name: "ECDSA", namedCurve: "P-256" }, true, ["sign", "verify"]);
    const wrapped = await crypto.subtle.wrapKey("pkcs8", pair.privateKey, backupKey, { name: "AES-GCM", iv });

Связанные ошибки

Открыть всю базу ошибок подписей