RSA SubtleCrypto 解密期间的 DOMException

问题描述

我正在尝试使用 SubtleCrypto 来加密字符串、存储加密的字符串并再次解密该字符串,所有这些都使用生成的 RSA-OAEP 密钥对。

下面的代码在解密阶段产生了一个 DOMException,但是我似乎无法得到关于错误的任何细节。我试过使用“SHA-1”进行散列,但遇到同样的问题。

有什么提示吗?

let encoder = new TextEncoder();
let decoder = new TextDecoder('utf-8');

// Generate a key pair

let keyPair = await window.crypto.subtle.generateKey(
    {
        name: "RSA-OAEP",modulusLength: 4096,publicExponent: new Uint8Array([1,1]),hash: "SHA-256"
    },true,["encrypt","decrypt"]
);

let publicKey = await crypto.subtle.exportKey('jwk',keyPair.publicKey);
let privateKey = await crypto.subtle.exportKey('jwk',keyPair.privateKey);

// Encrypt a string

let encodedSecret = encoder.encode("MYSECRETVALUE");
let pubcryptokey = await window.crypto.subtle.importKey(
    'jwk',publicKey,{
        name: "RSA-OAEP",false,["encrypt"]
);
let encrypted = await window.crypto.subtle.encrypt(
    {
        name: "RSA-OAEP"
    },pubcryptokey,encodedSecret
);
let encDV = new DataView(encrypted);
let ct = decoder.decode(encDV);

// Decrypt the string

let encodedCiphertext = encoder.encode(ct);
let privcryptokey = await window.crypto.subtle.importKey(
    'jwk',privateKey,["decrypt"]
);
console.log("Before decrypt");
let decrypted = await window.crypto.subtle.decrypt(
    {
        name: "RSA-OAEP"
    },privcryptokey,encodedCiphertext
);
console.log("After decrypt");
let decDV = new DataView(decrypted);
let pt = decoder.decode(decDV);

console.log(pt);

解决方法

问题在于密文的 UTF-8 编码/解码,这会破坏数据。如果将任意二进制数据(例如密文)存储在字符串中,则必须使用 binary-to-text encoding,例如 Base64,请参见例如here

如果这是固定的,解密工作:

(async () => {

    let encoder = new TextEncoder();
    let decoder = new TextDecoder('utf-8');

    // Generate a key pair

    let keyPair = await window.crypto.subtle.generateKey(
        {
            name: "RSA-OAEP",modulusLength: 4096,publicExponent: new Uint8Array([1,1]),hash: "SHA-256"
        },true,["encrypt","decrypt"]
    );

    let publicKey = await crypto.subtle.exportKey('jwk',keyPair.publicKey);
    let privateKey = await crypto.subtle.exportKey('jwk',keyPair.privateKey);

    // Encrypt a string

    let encodedSecret = encoder.encode("MYSECRETVALUE");
    let pubcryptokey = await window.crypto.subtle.importKey(
        'jwk',publicKey,{
            name: "RSA-OAEP",false,["encrypt"]
    );
    
    let encrypted = await window.crypto.subtle.encrypt(
        {
            name: "RSA-OAEP"
        },pubcryptokey,encodedSecret
    );
    
    //let encDV = new DataView(encrypted);         // Bug: UTF-8 decoding damages the ciphertext
    //let ct = decoder.decode(encDV);
    let ct = ab2b64(encrypted);                    // Fix: Use a binary to text encoding like Base64
    console.log(ct.replace(/(.{48})/g,'$1\n'));

    // Decrypt the string

    //let encodedCiphertext = encoder.encode(ct);  // Bug: s. above
    let encodedCiphertext = b642ab(ct);            // Fix: s. above 
    
    let privcryptokey = await window.crypto.subtle.importKey(
        'jwk',privateKey,["decrypt"]
    );
    
    console.log("Before decrypt");
    let decrypted = await window.crypto.subtle.decrypt(
        {
            name: "RSA-OAEP"
        },privcryptokey,encodedCiphertext
    );
    console.log("After decrypt");

    let decDV = new DataView(decrypted);
    let pt = decoder.decode(decDV);

    console.log(pt);
    
})();

// https://stackoverflow.com/a/11562550/9014097
function ab2b64(arrayBuffer) {
    return btoa(String.fromCharCode.apply(null,new Uint8Array(arrayBuffer)));
}

// https://stackoverflow.com/a/41106346 
function b642ab(base64string){
    return Uint8Array.from(atob(base64string),c => c.charCodeAt(0));
}