I'm trying encrypt files and upload them to the cloud and decrypt them locally with Node.js v21.6.2. Unfortunately, I'm having trouble getting the decrypt to work. I'm able to generate a key and encrypt, but decryption fails on the iv which allegedly isn't an ArrayBuffer, but should be. Is there a better way to make sure the iv is a buffer so I can properly decrypt the file? I've noticed that the encrypted data object is undefined, I'm not sure if that's the problem. Anyone have any ideas as to what I'm doing wrong? Here's the error I get:
node:internal/webidl:181
const err = new TypeError(message);
^
TypeError: Failed to execute 'decrypt' on 'SubtleCrypto': 3rd argument is not instance of ArrayBuffer, Buffer, TypedArray, or DataView.
at codedTypeError (node:internal/webidl:181:15)
at makeException (node:internal/webidl:190:10)
at converters.BufferSource (node:internal/crypto/webidl:206:11)
at SubtleCrypto.decrypt (node:internal/crypto/webcrypto:961:28)
at decrypt (/Users/me/code/encryptiontest/aes/aes.js:31:43)
at testEncryptionDecryption (/Users/me/code/encryptiontest/aes/aes.js:52:29) {
code: 'ERR_INVALID_ARG_TYPE'
}
Here is my code:
const { subtle } = globalThis.crypto;
async function generateAesKey(length = 256) {
const key = await subtle.generateKey({
name: 'AES-CBC',
length,
}, true, ['encrypt', 'decrypt']);
return key;
}
async function aesEncrypt(plaintext) {
const ec = new TextEncoder();
const key = await generateAesKey();
const iv = crypto.getRandomValues(new Uint8Array(16));
const ciphertext = await crypto.subtle.encrypt({
name: 'AES-CBC',
iv,
}, key, ec.encode(plaintext));
return {
key,
iv,
ciphertext,
};
}
async function decrypt(ciphertext, key, iv) {
const dec = new TextDecoder();
const plaintext = await crypto.subtle.decrypt({
name: 'AES-CBC',
iv,
}, key, ciphertext);
return dec.decode(plaintext);
}
// takes Uint8Array and returns ArrayBuffer
function typedArrayToBuffer(array) {
return array.buffer.slice(array.byteOffset, array.byteLength + array.byteOffset);
}
async function testEncryptionDecryption() {
const data = "Hello, world!";
console.log("ENCRYPTING DATA");
const { key, iv, encrypted } = await aesEncrypt(data);
console.log(iv);
console.log("DECRYPTING DATA");
console.log(typeof iv.buffer);
const decrypted = await decrypt(encrypted, key, typedArrayToBuffer(iv));
console.log("Original:", data);
console.log("Decrypted:", decrypted);
}
testEncryptionDecryption();
I tried using iv, iv.buffer, and iv.buffer.slice in the decrypt, but instead I get an error about iv not being an instance of ArrayBuffer, Buffer, TypedArray, or DataView
Your problem is not the IV (which is the 3rd argument to your decrypt
function) but -- as the error message says -- the 3rd argument to SubtleCrypto.decrypt
which should be the ciphertext but is instead undefined
.
This is because of your line
const { key, iv, encrypted } = await aesEncrypt(data);
aesEncrypt
returns an object containing key iv ciphertext
but not encrypted
, thus this sets key
and iv
to the members from the returned object (which are correct), but does not set encrypted
to anything and discards the value of member ciphertext
. Change this and the following code to extract and use ciphertext
and it works -- and with just plain iv
.