I want to perform basic AES-CBC decryption. I have the string encData
that is encrypted with the 128-bits key rawKey
, the initialization vector defaultIV
is zero. I want to use only Web Crypto API, without 3th-party libraries. Is it possible to do?
window.crypto.subtle.decrypt
of Web Crypto API throws the exception when I use it: DOMException
(and no more information) in Chromium and OperationError: The operation failed for an operation-specific reason
in Firefox.
What is it the problem?
The key and encrypted data is OK, I have checked it in an online decryption (use the hex string from the console output).
The code:
!async function script() {
// ArrayBuffer to Hex String. https://stackoverflow.com/a/40031979/11468937
function buf2hex(buffer) {
return Array.prototype.map.call(new Uint8Array(buffer), x => ('00' + x.toString(16)).slice(-2)).join('');
}
const defaultIV = new Uint8Array(16);
const rawKey = new Uint8Array([42, 40, 254, 9, 99, 201, 174, 52, 226, 21, 90, 155, 81, 50, 2, 9]);
const encData = new Uint8Array([102, 80, 220, 73, 185, 233, 85, 7, 195, 196, 137, 107, 65, 150, 162, 161, 80, 82, 26, 18, 110, 247, 189, 176, 35, 197, 140, 4, 138, 75, 159, 197, 75, 88, 131, 23, 235, 125, 96, 81, 41, 170, 220, 45, 64, 55, 30, 68, 39, 6, 112, 194, 243, 209, 177, 173, 54, 71, 21, 172, 62, 147, 112, 76]);
console.log("defaultIV\n", defaultIV, buf2hex(defaultIV));
console.log("rawKey\n", rawKey, buf2hex(rawKey));
console.log("encData\n", encData, buf2hex(encData));
const key = await crypto.subtle.importKey(
"raw",
rawKey,
"AES-CBC",
true,
["decrypt"]
);
console.log("key", key);
// It throws "Uncaught (in promise) DOMException"
const decrypted = await crypto.subtle.decrypt(
{
name: "AES-CBC",
iv: defaultIV
},
key,
encData
);
console.log("decrypted", decrypted);
}();
A padding (PKCS #7). It was the problem. My encrypted text that I get from a 3th-party service has no padding (that adds before encryption).
Web Crypto API does not work with data encrypted without the padding.
AES-JS has easy to use API, but it works only with a text without the padding (In my case it's OK), but if you use it to decrypt a cipher text with the padding you need to manually remove it from the result string.
CryptoJS API looks ancient, but it works fine in both case, when the text has the padding or not.
You need to transform ArrayBuffer to something other:
let key = ab_to_hex(keyArrayBuffer);
let iv = ab_to_bin_str(ivArrayBuffer);
let encrypted = ab_to_bin_str(encryptedArrayBuffer);
function decrypt(key, iv, encrypted) {
const plaintextArray = CryptoJS.AES.decrypt(
{ ciphertext: CryptoJS.enc.Latin1.parse(encrypted) },
CryptoJS.enc.Hex.parse(key),
{ iv: CryptoJS.enc.Latin1.parse(iv) }
);
return CryptoJS.enc.Utf8.stringify(plaintextArray);
}
function ab_to_hex(buffer) {
return Array.from(new Uint8Array(buffer)).map(n => ("0" + n.toString(16)).slice(-2)).join("");
}
function ab_to_bin_str(buffer) {
return String.fromCharCode(...new Uint8Array(buffer));
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With