在 Chromium 中使用 WebCrypto 导出 RSA 密钥对

Export RSA key pair with WebCrypto in Chromium

以下代码适用于 Firefox 76.0.1:

"use strict"
let RSAKeys
(async () => {
  RSAKeys = await crypto.subtle.generateKey({
      name: "RSA-OAEP",
      modulusLength: 3072,
      publicExponent: new Uint8Array([1, 0, 1]),
      hash: "SHA-256"},
    true,
    ["wrapKey", "unwrapKey"])
  alert(JSON.stringify(Object.fromEntries(
    await Promise.all(Object.entries(RSAKeys).map(async ([k, v], i) =>
      [k, await cryptoBase64("exportKey", ["pkcs8", "spki"][i], v)])))))
})()

async function cryptoBase64(primitive, ...args) {
  return ArrayBufferToBase64(await crypto.subtle[primitive](...args))
}

function ArrayBufferToBase64(buf) {
  return btoa([...new Uint8Array(buf)].map(x => String.fromCharCode(x)).join(""))
}

但在 Chromium 80 中我得到:

Uncaught (in promise) DOMException: The key is not of the expected type

哪里不一样?它是 Chromium 中的错误吗?有解决方法吗?

(与 相关。应用该解决方案后我仍然遇到问题,事实证明我 运行 喜欢的浏览器之间存在另一个差异。)

Object.entries returns 以对象的属性作为键值对的数组。键值对的顺序任意,见Object.entries():

The Object.entries() method returns an array of a given object's own enumerable string-keyed property [key, value] pairs, in the same order as that provided by a for...in loop...

for...in:

A for...in loop iterates over the properties of an object in an arbitrary order...

另一方面,["pkcs8", "spki"][i] 假定密钥顺序是私钥 (i = 0) 后跟 public 密钥 (i = 1)。在 Firfox 浏览器中顺序恰好匹配,在 Chromium 浏览器中不匹配,从而导致异常。

问题可以通过排序数组来解决,例如使用 sort()localeCompare(),另请参阅 Object.entries():

中的建议
sort((key1, key2) => key1[0].localeCompare(key2[0])) 

另一种方法是根据密钥类型 (private, [设置格式 (pkcs8, spki) =62=]) 而不是排序。

您的 JavaScript 代码 使用两种方法之一完成 在 Firefox 和 Chromium 浏览器中运行:

"use strict"

let RSAKeys
(async () => {
  RSAKeys = await crypto.subtle.generateKey({
    name: "RSA-OAEP",
    modulusLength: 3072,
    publicExponent: new Uint8Array([1, 0, 1]),
    hash: "SHA-256"},
    true,
    ["wrapKey", "unwrapKey"])
  
  // Approach 1
  var result1 = JSON.stringify(Object.fromEntries(
    await Promise.all(Object.entries(RSAKeys)
      .sort((key1, key2) => key1[0].localeCompare(key2[0]))
        .map(async ([k, v], i) => [k, await cryptoBase64("exportKey", ["pkcs8", "spki"][i], v)]))))
  
  console.log(result1.replace(/(.{64})/g, "\n"));
  
  // Approach 2
  var result2 = JSON.stringify(Object.fromEntries(
    await Promise.all(Object.entries(RSAKeys)
      .map(async ([k, v], i) => [k, await cryptoBase64("exportKey", k == "privateKey" ? "pkcs8" : "spki", v)]))))
  
  console.log(result2.replace(/(.{64})/g, "\n"));

})()

async function cryptoBase64(primitive, ...args) {
  return ArrayBufferToBase64(await crypto.subtle[primitive](...args))
}

function ArrayBufferToBase64(buf) {
  return btoa([...new Uint8Array(buf)].map(x => String.fromCharCode(x)).join(""))
}