como descriptografar corretamente o texto via Subtle Crypto que foi criptografado via CryptoJS

Sep 25 2020

Eu tenho um código que criptografa os dados do usuário usando CryptoJS.AES, armazena a chave, iv e o conteúdo criptografado em diferentes lugares. Além disso, ele descriptografa o conteúdo criptografado usando a chave armazenada e iv por demanda do usuário.

Quero usar a API do navegador Subtle Crypto para criptografia, que está pronta.

Mas eu também quero ter a possibilidade de descriptografar dados antigos (que foram criptografados usando CryptoJS.AES) usando Subtle Crypto.

dados antigos foram gerados com o seguinte código

  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()
  };

e tentei descriptografá-lo da seguinte forma

  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);

Recebo DOMExceptionerro sem backtrace emawait crypto.subtle.decrypt

a reprodução completa pode ser encontrada em https://codesandbox.io/s/crypto-js-to-subtle-crypto-u0pgs?file=/src/index.js

Respostas

3 Topaco Sep 25 2020 at 18:09

No código CryptoJS, a chave é passada como string. Portanto, ela é interpretada como uma senha, da qual em combinação com um sal de 8 bytes gerado aleatoriamente, uma chave de 32 bytes e um IV de 16 bytes são derivados, veja aqui . A função de derivação de chave OpenSSL proprietária (e relativamente insegura) EVP_BytesToKeyé usada para isso.

CryptoJS.AES.encrypt()retorna um CipherParamsobjeto que encapsula vários parâmetros, como a chave gerada e IV as WordArray, veja aqui . toString()aplicado à chave ou IV WordArray, retorna os dados codificados em hexadecimal. toString()aplicado ao CipherParamsobjeto, retorna o texto cifrado no formato OpenSSL, ou seja, o primeiro bloco (= os primeiros 16 bytes) consiste na codificação ASCII de Salted__, seguido pelo sal de 8 bytes e o texto cifrado real, todos juntos codificados em Base64, veja aqui . Isso significa que o texto cifrado real começa (após a decodificação Base64) com o segundo bloco.

O código a seguir ilustra como o texto cifrado gerado com CryptoJS pode ser descriptografado com a API WebCrypto

//
// 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>