Search code examples
node.jsbashopensslaescryptojs

Cannot decrypt aes-256-cbc encrypted payload using Node.js (crypto)


I can encrypt and decrypt a string using openssl:

ENCRYPTED=$(echo "my_secret_data" | openssl aes-256-cbc -pass "pass:bab3fd92bcd7d464" -pbkdf2 -a -A)

echo -n $ENCRYPTED | base64 -d | openssl aes-256-cbc -d -pass "pass:bab3fd92bcd7d464" -pbkdf2

However, I'm not able to decrypt the string back using Node.js.

I tried the following Node.js code:

const crypto = require('crypto');

const encryptedTextBase64 = 'U2FsdGVkX18AYE13z9uboo3WZhktr03EeV0WFA0MH4o=';
const password = 'bab3fd92bcd7d464';

// Decode the base64-encoded text
const encryptedText = Buffer.from(encryptedTextBase64, 'base64');

// Create a decipher object
const decipher = crypto.createDecipher('aes-256-cbc', password);

// Update the decipher with the encrypted text
let decrypted = decipher.update(encryptedText, 'binary', 'utf8');
decrypted += decipher.final('utf8');

console.log(decrypted);

But I get the error

node:internal/crypto/cipher:199
  const ret = this[kHandle].final();
                            ^

Error: error:1C800064:Provider routines::bad decrypt
    at Decipher.final (node:internal/crypto/cipher:199:29)
    at Object.<anonymous> (.../decrypt.js:14:23)
    at Module._compile (node:internal/modules/cjs/loader:1256:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1310:10)
    at Module.load (node:internal/modules/cjs/loader:1119:32)
    at Module._load (node:internal/modules/cjs/loader:960:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:86:12)
    at node:internal/main/run_main_module:23:47 {
  library: 'Provider routines',
  reason: 'bad decrypt',
  code: 'ERR_OSSL_BAD_DECRYPT'
}

But the following bash code works as expected:

echo -n U2FsdGVkX18AYE13z9uboo3WZhktr03EeV0WFA0MH4o= | base64 -d | openssl aes-256-cbc -d -pass "pass:bab3fd92bcd7d464" -pbkdf2

Any help in order to have a valid Node.js code to decrypt the payload is highly appreciated.

If the bash command also needs to be changed, that's not a problem. My end goal is to be able to encrypt a string with bash and decrypt with Node.js using a known passkey.


Solution

  • The NodeJS code lacks the separation of salt and ciphertext (encryptedText is the concatenation of Salted__ + 8 bytes salt + ciphertext) as well as the determination of a 48 bytes array with PBKDF2 (using the password, the salt, an iteration count of 10000 and SHA-256). The first 32 bytes are the key, the remaining 16 bytes the IV.
    After that the decryption has to be carried out with createDecipheriv() (with as before aes-256-cbc).

    A working code is:

    const crypto = require('crypto');
    
    const encryptedTextBase64 = 'U2FsdGVkX18AYE13z9uboo3WZhktr03EeV0WFA0MH4o=';
    const password = 'bab3fd92bcd7d464';
    
    // Decode the base64-encoded text
    const encryptedText = Buffer.from(encryptedTextBase64, 'base64');
    
    // Extract salt (first 8 bytes) and ciphertext (the rest)
    const salt = encryptedText.subarray(8, 16);
    const ciphertext = encryptedText.subarray(16);
    
    // Derive the key and IV using PBKDF2
    const keyIVBuffer = crypto.pbkdf2Sync(password, salt, 10000, 48, 'sha256');
    const key = keyIVBuffer.subarray(0, 32);
    const iv = keyIVBuffer.subarray(32);
    
    // Create a decipher object with IV
    const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
    
    // Update the decipher with the ciphertext
    let decrypted = decipher.update(ciphertext, 'binary', 'utf8');
    decrypted += decipher.final('utf8');
    
    console.log(decrypted);