跳到主要内容
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”。它们都是 `InvalidAccessError` 类型的 DOMException。

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

含义

该密钥创建时指定了 `extractable: false`,因此它的密钥材料永远无法读出——`exportKey` 不行,`wrapKey` 也不行。这正是这个标志的意义:私钥始终留在浏览器的加密实现内部,脚本(包括被注入的脚本)可以使用它,却无法窃取它。生成的密钥对中的公钥始终可以导出;这个错误针对的是私钥和对称密钥。

原因

修复方法

  1. 1.

    私钥保留为对象,导出公钥

    把不可导出的密钥以 `CryptoKey` 对象的形式存入 IndexedDB,只对外提供公钥(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 });

相关错误

浏览完整的签名错误库