使用 crypto API 解密数据时如何修复此 OperationError 错误?

How to fix this OperationError error when decrypting data with crypto API?

我已经使用 crypto API 成功加密了数据。完成后,我将初始化向量和加密数据保存为单个 base64 字符串。

解密时,我将这两个信息还原为与原始匹配的Uint8Array。但是解密总是失败并出现以下错误:

error decrypt Error: OperationError

代码如下:

// generate key
generateKey (){
  crypto.subtle.generateKey(
    { name: "AES-GCM", length: 256 },
    false,
    ["encrypt", "decrypt"]
  );
}

// encrypt
  async encrypt(data, secretKey) {
    const initializationVector = crypto.getRandomValues(new Uint8Array(96));
    const encodedData = new TextEncoder().encode(JSON.stringify(data));

    const encryptedBuffer = await crypto.subtle.encrypt(
      {
        name: "AES-GCM",
        iv: initializationVector,
        tagLength: 128,
      },
      secretKey,
      encodedData
    );

    const encryptedDataBase64 = btoa(new Uint8Array(encryptedBuffer));
    const initializationVectorBase64 = btoa(initializationVector);
    return `${encryptedDataBase64}.${initializationVectorBase64}`;
  }

// convert base64 string to uint8array
  base64ToUint8Array(base64String) {
    return new Uint8Array(
      atob(base64String)
        .split(",")
        .map((n) => +n)
    );
  }

//decrypt
  async decrypt(encryptedData, secretKey) {
    const { 0: data, 1: iv } = encryptedData.split(".");
    const initializationVector = base64ToUint8Array(iv);
    const _data = base64ToUint8Array(data);
    const decryptedData = await crypto.subtle.decrypt(
      {
        name: "AES-GCM",
        iv: initializationVector,
        tagLength: 128,
      },
      secretKey,
      _data
    );
    return new TextDecoder().decode(decryptedData)
  }

我在加密和解密期间检查了初始化向量和数据 Uint8Array。它们与原始版本相匹配。所以我不知道我哪里做错了。

感谢您的帮助!

ArrayBuffer 到 Base64 的转换不正确,反之亦然。此外,在创建 IV 或实例化 Uint8Array 时,长度必须以字节而不是位为单位指定。一个可能的修复是:

(async () => {
    var key = await generateKey();
    
    var plaintext = {"data": "The quick brown fox jumps over the lazy dog"};
    var ciphertext = await encrypt(plaintext, key);
    console.log(ciphertext.replace(/(.{48})/g,'\n'));
    
    var decrypted = await decrypt(ciphertext, key);
    console.log(JSON.parse(decrypted));
})();

// generate key
function generateKey (){                                            
    return crypto.subtle.generateKey(                                   
        { name: "AES-GCM", length: 256 },
        false,
        ["encrypt", "decrypt"]
    );
}

// encrypt
async function encrypt(data, secretKey) {                                   
    const initializationVector = crypto.getRandomValues(new Uint8Array(12)); // Fix: length in bytes
    const encodedData = new TextEncoder().encode(JSON.stringify(data));

    const encryptedBuffer = await crypto.subtle.encrypt(
        {
            name: "AES-GCM",
            iv: initializationVector,
            tagLength: 128,
        },
        secretKey,
        encodedData
    );

    const encryptedDataBase64 = ab2b64(encryptedBuffer); // Fix: Apply proper ArrayBuffer to Base64 conversion
    const initializationVectorBase64 = ab2b64(initializationVector); // Fix: Apply proper ArrayBuffer to Base64 conversion 
    return `${encryptedDataBase64}.${initializationVectorBase64}`;
}

// decrypt
async function decrypt(encryptedData, secretKey) {                      
    const { 0: data, 1: iv } = encryptedData.split(".");
    const initializationVector = b642ab(iv); // Fix: Apply proper Base64 to ArrayBuffer conversion
    const _data = b642ab(data); // Fix: Apply proper Base64 to ArrayBuffer conversion
    const decryptedData = await crypto.subtle.decrypt(
        {
            name: "AES-GCM",
            iv: initializationVector,
            tagLength: 128,
        },
        secretKey,
        _data
    );
    return new TextDecoder().decode(decryptedData)
}

//  or 
function ab2b64(arrayBuffer) {
      return btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));
}

//  or 
function b642ab(base64string){
      return Uint8Array.from(atob(base64string), c => c.charCodeAt(0));
}