在 Chromium 中使用 WebCrypto 生成 RSA 密钥对

Generate 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,
// Chromium bug causes it to falsely complain that the array is empty. Sometimes adding "encrypt" helps.
    ["wrapKey"])
})()

但在 Chromium 80 中我得到:

Uncaught (in promise) DOMException: Usages cannot be empty when creating a key.

["wrapKey"] 显然不是一个空数组,所以它似乎是一个浏览器错误。大概this one. Can you confirm? And more importantly, do you know a workaround? (Adding the encrypt usage helped but only the first time, then the same error.) It has to be an asymmetric cipher supporting wrapping keys. According to the table in the relevant chapter of the spec,RSA-OAEP是唯一的可能。

我可以在 Chromium 版本 85.0.4162.0 上重现该问题:密钥用法 ["wrapKey"] 生成发布的错误消息。但我无法重现添加密钥用法 encrypt(即 ["wrapKey", "encrypt"])可以解决问题(甚至不是第一次)。但是,通过添加密钥用法 unwrapKey(即 ["wrapKey", "unwrapKey"]),错误不再发生。

SubtleCrypto.generateKey() returns a CryptoKeyPair for "RSA-OAEP" 包含 RSA 密钥对。如果您在 Firefox 浏览器控制台中查看使用密钥用法 ["wrapKey", "unwrapKey"] 生成的密钥对,您可以看到 public 密钥的密钥用法是 ["wrapKey"],私钥是["unwrapKey"]。两者都是合理的,因为 public 密钥用于包装而私钥用于解包:

但是,如果您在 Firefox 浏览器控制台中查看使用密钥用法 ["wrapKey"] 生成的密钥对,您可以看到 ["wrapKey"] 的密钥用法=78=]密钥不变["wrapKey"],私钥:

所以Chromium通过相应的错误信息(显然是指私钥使用为空)来防止生成没有使用密钥的密钥。与 Chromium 相比,Firefox 显然允许这样做。

那是 Chromium 错误吗?实际上,创建一个没有密钥用法的密钥没有多大意义,因为它不能使用!

以Firefox浏览器为例:如果在Firefox浏览器中执行以下代码,则确实生成了密钥对,并且因为密钥的使用而包装了一个密钥wrapKey 用于 public 密钥,但如果缺少用于私钥的密钥用法 unwrapKey,解包将失败并显示 InvalidAccessError

var test = async () => {

  try {
      
    var mode = document.querySelector('input[name="keyUsages"]:checked').value;
    var keyUsages = (mode === "wrap") ? ["wrapKey"] : ["wrapKey", "unwrapKey"] 
     
    // Create RSA key pair
    var RSAKeys = await crypto.subtle.generateKey(
      {name: "RSA-OAEP", modulusLength: 3072, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: {name: "SHA-256"}},
      true,
      keyUsages);
    
    // Create key to wrap
    var keyToWrap = await window.crypto.subtle.generateKey(
      {name: "AES-GCM", length: 128},
      true,
      ["encrypt", "decrypt"]);
    
    // Wrap key
    var wrappedKey = await window.crypto.subtle.wrapKey(
      "raw",
      keyToWrap,
      RSAKeys.publicKey,
      {name: "RSA-OAEP", hash: {name: "SHA-256"}});
     
    // Unwrap key
    var unwrappedKey = await window.crypto.subtle.unwrapKey(
      "raw",  
      wrappedKey,  
      RSAKeys.privateKey, 
      {name: "RSA-OAEP", modulusLength: 3072, publicExponent: new Uint8Array([0x01, 0x00, 0x01]), hash: {name: "SHA-256"}},
      {name: "AES-GCM", length: 128},
      false, 
      ["encrypt", "decrypt"]); 
    
    document.getElementById("result").innerHTML = "Secret key for " + unwrappedKey.algorithm.name + " unwrapped.";
    console.log(unwrappedKey);
 
    } catch(e) {
      document.getElementById("result").innerHTML = e.name + ": " + e.message;
    }
}
.as-console-wrapper { max-height: 7.0em !important; }
<!DOCTYPE html>
<html>
<body height="200">
    <input type="radio" name="keyUsages" value="wrap" checked="true"> ["wrapKey"] 
    <input type="radio" name="keyUsages" value="wrapUnwrap"> ["wrapKey", "unwrapKey"] 
    <button onclick="test()">Run</button><br/> 
    <p id="result"></p>
</body>
</html>

因此我不会将其归类为错误(但这只是我的意见)。