EN VI

Node.js - crypto.subtle unable to decrypt, iv isn not ArrayBuffer, encrypted data is undefined?

2024-03-11 13:30:04
Node.js - crypto.subtle unable to decrypt, iv isn not ArrayBuffer, encrypted data is undefined

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

Solution:

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.

Answer

Login


Forgot Your Password?

Create Account


Lost your password? Please enter your email address. You will receive a link to create a new password.

Reset Password

Back to login