【问题标题】:How to fix this OperationError error when decrypting data with crypto API?使用加密 API 解密数据时如何解决此 OperationError 错误?
【发布时间】:2021-06-28 08:36:45
【问题描述】:

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

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

错误解密错误: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。他们匹配他们的原始版本。所以我不知道我在哪里做错了。

感谢您的帮助!

【问题讨论】:

    标签: javascript node.js cryptography cryptoapi


    【解决方案1】:

    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,'$1\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)
    }
    
    // https://stackoverflow.com/a/11562550/9014097 or https://stackoverflow.com/a/9458996/9014097
    function ab2b64(arrayBuffer) {
          return btoa(String.fromCharCode.apply(null, new Uint8Array(arrayBuffer)));
    }
    
    // https://stackoverflow.com/a/41106346 or https://stackoverflow.com/a/21797381/9014097
    function b642ab(base64string){
          return Uint8Array.from(atob(base64string), c => c.charCodeAt(0));
    }

    【讨论】:

    • 谢谢您的更正工作!在您看来,这种 AES 加密是否足够安全,可以将数据存储在本地存储中?或者我应该使用不同的算法,在附加数据条目中添加密码等?
    • @DoneDeal0 - 无法在评论中回答安全概念(尤其是对于 JavaScript),也超出了此问题的范围。网上有很多关于这个主题的帖子。但是可以这么说:AES-GCM 是安全的(如果使用正确的话)。问题在于保护密钥(尤其是在 JavaScript 环境中)。一个概念可能是将 CryptoKey(配置为不可提取)而不是在 IndexedDB 中(而不是在本地存储中)存储,参见例如this post.
    • 是的,我就是这样做的,密钥存储在 indexedDB 中。我一定会尽快阅读有关此主题的更多信息。再次感谢您的帮助,我很感激。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-03-22
    相关资源
    最近更新 更多