WebCryptoApi:无法将 "encrypt" 和 "decrypt" 处于活动状态的 "jwk" 格式包装和解包 aes-gcm 密钥

WebCryptoApi: Cannot wrap&unwrap aes-gcm key into&from "jwk" format with "encrypt" and "decrypt" active

我正在生成一个用于加密数据的密钥,然后使用主密钥将其打包并将其与加密数据一起存储。当包装成 raw 格式时一切都很好,但是当包装成 jwk 时我得到错误 DOMException: Data provided to an operation does not meet requirements.

在指定用于加密或解密的密钥时有效,但在两者都指定为密钥用途时无效。

let wrapAlgo = {
    name: "AES-KW",
    length: 256
};
let encAlgo = {
    name:"AES-GCM",
    length:256
}
let format = "jwk";
let extractable=true;
let keyUsages = ["encrypt", "decrypt"];

let kek = await crypto.subtle.generateKey(
    wrapAlgo, 
    false, 
    ["wrapKey", "unwrapKey"]
);

let key = await window.crypto.subtle.generateKey(
    encAlgo,
    extractable, // the key is extractable (i.e. can be used in exportKey)
    keyUsages
);
console.log("key", key);

let wrappedKey = await crypto.subtle.wrapKey(
    format,
    key,
    kek,
    wrapAlgo
);
console.log("wrappedKey", wrappedKey);

let unwrappedKey = await crypto.subtle.unwrapKey(
    format,
    wrappedKey,
    kek,
    wrapAlgo,
    encAlgo,
    extractable,
    keyUsages
);
console.log("key", await crypto.subtle.exportKey("jwk", unwrappedKey));

AES-KW 是 RFC3394. The algorithm is used to wrap i. e. encrypt a key. The input, i.e. the key to be encrypted, must be an integer multiple of 8 bytes, s. also here.

中描述的密钥包装算法

要加密的密钥在 SubtleCrypto.wrapKey() in the 2nd parameter key as CryptoKey 中传递,因此必须在实际加密之前导出。为此,在第一个参数 format:

中指定导出密钥的格式
const result = crypto.subtle.wrapKey(format, key, wrappingKey, wrapAlgo);

在发布的示例中,要包装的密钥是 AES-256 的 32 字节密钥。在 raw 格式中,密钥因此满足 AES-KW 长度标准。然而,在 jwk 格式中,通常不满足长度标准:
如果以 jwk 格式导出的密钥被序列化,它的密钥使用长度 ["encrypt"]["decrypt"] 恰好是 8 字节(112 字节)的整数倍,而这密钥用法并非如此 ["encrypt", "decrypt"] (122 bytes):

(async () => {
    async function getLength(keyUsages) {  
        var key = await window.crypto.subtle.generateKey(
            {name:"AES-GCM", length: 256},
            true, 
            keyUsages
        );
        var expkey = await crypto.subtle.exportKey("jwk", key)
        var expkeySerLen = JSON.stringify(expkey).length;
        return {KeyUsages: keyUsages, length: expkeySerLen, lenMod8: expkeySerLen % 8}; 
    }
    console.log(await getLength(["encrypt"]));            // works
    console.log(await getLength(["decrypt"]));            // works
    console.log(await getLength(["encrypt", "decrypt"])); // doesn't work
})();

这很可能是执行键用法["encrypt"]["decrypt"]的代码,而不执行键用法["encrypt", "decrypt"]的代码的原因。

最重要的是,AES-KW 可靠地 raw 格式,但不适用于 jwk 格式。

但是,jwk 格式可以在 SubtleCrypto.wrapKey() 中用于其他包装算法,例如 AES-GCM:

(async () => {

    let encAlgo = {
        name:"AES-GCM",
        length:256
    };
    let wrapAlgo = {
        name:"AES-GCM",
        length:256
    };
    let aesGcmParams = {
        name:"AES-GCM",
        iv: window.crypto.getRandomValues(new Uint8Array(12))
    };
    let format = "jwk";
    let extractable=true;
    let keyUsages = ["encrypt", "decrypt"];

    let kek = await crypto.subtle.generateKey(
        wrapAlgo, 
        false, 
        ["wrapKey", "unwrapKey"]
    );

    let key = await window.crypto.subtle.generateKey(
        encAlgo,
        extractable, // the key is extractable (i.e. can be used in exportKey)
        keyUsages
    );
    console.log("key (CryptoKey)", key);
    console.log("key (jwk)", await crypto.subtle.exportKey("jwk", key));

    let wrappedKey = await crypto.subtle.wrapKey(
        format,
        key,
        kek,
        aesGcmParams
    );
    console.log("wrappedKey (ArrayBuffer)", wrappedKey);

    let unwrappedKey = await crypto.subtle.unwrapKey(
        format,
        wrappedKey,
        kek,
        aesGcmParams,
        encAlgo,
        extractable,
        keyUsages
    );
    console.log("unwrappedKey (jwk) ", await crypto.subtle.exportKey("jwk", unwrappedKey));

})();