如何通过通过CryptoJS加密的Subtle Crypto正确解密文本

问题描述

我有使用CryptoJS.AES加密用户数据的代码,将密钥,iv和加密内容存储在不同的位置。 它还使用存储的密钥和iv根据用户需求解密加密的内容

我想使用Subtle Crypto浏览器API进行加密。

但是我也希望能够使用Subtle Crypto解密旧数据(使用CryptoJS.AES加密的旧数据)。

旧数据是使用以下代码生成

  var CryptoJS = require("crypto-js/core");
  CryptoJS.AES = require("crypto-js/aes");

  let encKey = generaterandomString();
  let aesEncrypted = CryptoJS.AES.encrypt(content,encKey);
  let encrypted = {
    key: aesEncrypted.key.toString(),iv: aesEncrypted.iv.toString(),content: aesEncrypted.toString()
  };

我已经尝试按照以下方式对其进行解密

  let keyArrayBuffer = hexArrayToArrayBuffer(sliceArray(encrypted.key,2));
  let decKey = await importKey(keyArrayBuffer);
  let decIv = hexArrayToArrayBuffer(sliceArray(encrypted.iv,2));
  let encContent = stringToArrayBuffer(encrypted.content);
  let decryptedByteArray = await crypto.subtle.decrypt(
    { name: "AES-CBC",iv: decIv },decKey,encContent
  );
  let decrypted = new TextDecoder().decode(decrypted);

我收到DOMException错误await crypto.subtle.decrypt

完整复制可在https://codesandbox.io/s/crypto-js-to-subtle-crypto-u0pgs?file=/src/index.js

上找到

解决方法

在CryptoJS代码中,密钥作为字符串传递。因此,它被解释为密码,与随机生成的8字节盐一起从中得出32字节密钥和16字节IV,请参见here。专有(相对不安全)的OpenSSL密钥派生函数EVP_BytesToKey用于此目的。

CryptoJS.AES.encrypt()返回一个CipherParams对象,该对象封装了各种参数,例如生成的密钥和IV为WordArray,请参见heretoString()应用于键或IV WordArray,返回十六进制编码的数据。 toString()应用于CipherParams对象,以OpenSSL格式返回密文,即,第一个块(=前16个字节)由Salted__的ASCII编码组成,后跟8个字节盐和实际密文(均由Base64编码),请参见here。这意味着实际的密文从第二个块开始(在Base64解码之后)。

以下代码说明了如何使用WebCrypto API解密由CryptoJS生成的密文

//
// CryptoJS
//
const content = "The quick brown fox jumps over the lazy dog";
const encKey = "This is my passphrase";

const aesEncrypted = CryptoJS.AES.encrypt(content,encKey);
const encrypted = {
    key: aesEncrypted.key.toString(),iv: aesEncrypted.iv.toString(),content: aesEncrypted.toString()
};

//
// WebCrypto API
// 
// https://stackoverflow.com/a/50868276
const fromHex = hexString => new Uint8Array(hexString.match(/.{1,2}/g).map(byte => parseInt(byte,16)));
// https://stackoverflow.com/a/41106346
const fromBase64 = base64String => Uint8Array.from(atob(base64String),c => c.charCodeAt(0));
 
async function decryptDemo(){
    
    const rawKey = fromHex(encrypted.key);
    const iv = fromHex(encrypted.iv);
    const ciphertext = fromBase64(encrypted.content).slice(16);
        
    const key = await window.crypto.subtle.importKey(           
        "raw",rawKey,"AES-CBC",true,["encrypt","decrypt"]
    );

    const decrypted = await window.crypto.subtle.decrypt(
        {
            name: "AES-CBC",iv: iv
        },key,ciphertext
    );

    const decoder = new TextDecoder();
    const plaintext = decoder.decode(decrypted);
    console.log(plaintext);     
}
    
decryptDemo();
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.0.0/crypto-js.min.js"></script>