como descriptografar corretamente o texto via Subtle Crypto que foi criptografado via CryptoJS
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
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>