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
decryptfunction) but -- as the error message says -- the 3rd argument toSubtleCrypto.decryptwhich should be the ciphertext but is insteadundefined.This is because of your line
aesEncryptreturns an object containingkey iv ciphertextbut notencrypted, thus this setskeyandivto the members from the returned object (which are correct), but does not setencryptedto anything and discards the value of memberciphertext. Change this and the following code to extract and useciphertextand it works -- and with just plainiv.