【问题标题】:Generate RSA key pair with WebCrypto in Chromium在 Chromium 中使用 WebCrypto 生成 RSA 密钥对
【发布时间】:2020-05-31 07:38:52
【问题描述】:

以下代码适用于 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。你确定吗?更重要的是,您知道解决方法吗? (添加encrypt 用法有所帮助,但只是第一次,然后是同样的错误。)它必须是支持包装密钥的非对称密码。根据the relevant chapter of the spec中的表格,RSA-OAEP是唯一的可能。

【问题讨论】:

  • 它可以在 chrome 83 或更高版本中使用吗?

标签: google-chrome rsa chromium webcrypto-api


【解决方案1】:

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

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

但是,如果您在 Firefox 浏览器控制台中查看使用密钥用法["wrapKey"] 生成的密钥对,您可以看到公钥的密钥用法未更改["wrapKey"],而私钥是empty

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

现在这是 Chromium 错误吗?实际上,在没有密钥用法的情况下创建密钥没有多大意义,因为它无法使用!

火狐浏览器示例:如果在火狐浏览器中执行以下代码,则确实生成了密钥对,并且由于密钥使用情况而对密钥进行了包装wrapKey对于公钥,但如果私钥的密钥用法 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>

因此,我不会将此归类为错误(但这只是我的观点)。

【讨论】:

  • 感谢您提供详细且举例说明的答案。 "unwrapKey" 确实明显不见了。我唯一的挑剔是,如果规范要求不同的行为,那就是一个错误。但另请参阅my issue against the spec
  • 虽然["wrapKey", "unwrapKey"] 确实不再发生此错误,但我得到了一个不同的错误:> 未捕获(承诺)DOMException:密钥不是预期的类型
  • 嗯,我是这么认为的,但我进一步调查,实际上没有。我发现了 Chromium 和 Firefox 之间的另一个区别。在 Chromium 中,当我尝试导出密钥时会发生第二个错误。我已经问过another question