Here's my code:
var key = 'aaaaaaaaaaaaaaaa'
var iv = 'bbbbbbbbbbbbbbbb';
var ciphertext = '10f42fd95857ed2775cfbc4b471bc213';
// from https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#PKCS_8_import
function str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
key = new TextEncoder().encode(key);
iv = new TextEncoder().encode(iv);
ciphertext = str2ab(ciphertext);
window.crypto.subtle.importKey(
'raw',
key,
{
name: 'AES-CBC'
},
true, // can the key be extracted using SubtleCrypto.exportKey() / SubtleCrypto.wrapKey()?
['decrypt'] // keyUsages
).then(function(key) {
window.crypto.subtle.decrypt(
{
name: "AES-CBC",
iv: iv
},
key,
ciphertext
).then(function(plaintext) {
console.log(new TextDecoder().decode(plaintext));
})
});
When I run it I get Uncaught (in promise) Error
in the JS Console.
Here's a JSFiddle showing the error:
https://jsfiddle.net/96gx7hz3/
Any ideas?
The ciphertext is hex encoded and must therefore be hex decoded, e.g. with the following helper function:
function hex2ab(hex){
return new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function (h) {return parseInt(h, 16)}));
}
The str2ab()
function used in the code is commonly applied for a latin1 encoding, which however would be wrong in this case.
With this change, decryption works:
var key = 'aaaaaaaaaaaaaaaa'
var iv = 'bbbbbbbbbbbbbbbb';
var ciphertext = '10f42fd95857ed2775cfbc4b471bc213';
/*
// from https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/importKey#PKCS_8_import
function str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
*/
function hex2ab(hex){
return new Uint8Array(hex.match(/[\da-f]{2}/gi).map(function (h) {return parseInt(h, 16)}));
}
key = new TextEncoder().encode(key);
iv = new TextEncoder().encode(iv);
//ciphertext = str2ab(ciphertext);
ciphertext = hex2ab(ciphertext); // Fix: Replcae latin1 encoding with hex decoding
window.crypto.subtle.importKey(
'raw',
key,
{
name: 'AES-CBC'
},
true, // can the key be extracted using SubtleCrypto.exportKey() / SubtleCrypto.wrapKey()?
['decrypt'] // keyUsages
).then(function(key) {
window.crypto.subtle.decrypt(
{
name: "AES-CBC",
iv: iv
},
key,
ciphertext
).then(function(plaintext) {
console.log(new TextDecoder().decode(plaintext));
})
});
A note on key and IV: In the code, the key material is UTF-8 encoded and used directly as key. This is fine for testing purposes as here, but in general a key should be generated as a random byte sequence or, if a passphrase is used, derived using a key derivation function in conjunction with a random salt.
Likewise, to avoid reusing key/IV pairs, no static IV may be applied. Instead, a random IV is commonly generated for each encryption and passed along with the ciphertext.