node.jsencryptionopensslcryptography

How to encrypt in nodejs via AES-256-CBC to be consistent with openssl


I used to encrypt text in nodejs by invoking openssl via execSync, but it's a bad practice, because it could leak password to system logs. Now I want to use the crypto library, but I get inconsistent results.

Here's a minimal code to reproduce:

const crypto = require('crypto');
const { execSync } = require('child_process');

const encrypt_text = async (plaintext, pass, salt = null) => {
    return new Promise((resolve, reject) => {
        try {
            let command = `echo "${plaintext}" | openssl enc -aes-256-cbc -a -md sha1 -pbkdf2 -pass pass:${pass}`;
            if (salt) {
                const saltHex = salt.toString('hex');
                command = `echo "${plaintext}" | openssl enc -aes-256-cbc -a -md sha1 -pbkdf2 -pass pass:${pass} -S ${saltHex}`;
            }
            const encrypted_text = execSync(command).toString().trim();
            resolve(encrypted_text);
        } catch (error) {
            console.error('Error encrypting text.');
            reject(error);
        }
    });
}

const encrypt_text_new = async (plaintext, pass, salt = null) => {
    return new Promise((resolve, reject) => {
        try {
            if (!salt) {
                salt = crypto.randomBytes(8); // 8 bytes salt
            }
            const salted = Buffer.concat([Buffer.from('Salted__'), salt]); // OpenSSL salt format

            // OpenSSL key derivation
            const keyIv = crypto.pbkdf2Sync(pass, salt, 1, 48, 'sha1'); // 48 bytes for key and IV
            const key = keyIv.slice(0, 32); // first 32 bytes for key
            const iv = keyIv.slice(32); // remaining 16 bytes for IV

            // Create cipher
            const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
            let encrypted = cipher.update(plaintext, 'utf8');
            encrypted = Buffer.concat([encrypted, cipher.final()]);

            // Combine all parts
            const result = Buffer.concat([salted, encrypted]);

            const encrypted_text = result.toString('base64');

            resolve(encrypted_text);
        } catch (error) {
            console.error('Error encrypting text.');
            reject(error);
        }
    });
}

const check_encryption = async () => {
    // Sample data for testing
    const plaintext = 'This is a test.';
    const password = 'mysecretpassword';

    // Generate a random salt for consistency in both functions
    const salt = Buffer.from("12345678", 'utf8');

    (async () => {
        try {
            const encrypted1 = await encrypt_text(plaintext, password, salt);
            const encrypted2 = await encrypt_text_new(plaintext, password, salt);

            console.log('Encrypted with OpenSSL:', encrypted1);
            console.log('Encrypted with crypto:', encrypted2);

            if (encrypted1 === encrypted2) {
                console.log('The encrypted results are the same.');
            } else {
                console.log('The encrypted results are different.');
            }
        } catch (error) {
            console.error('Error during encryption comparison:', error);
        }
    })();
}

check_encryption();

Will output:

Encrypted with OpenSSL: yY0R+o0ikHugU/cFa8O6LnLZA4WWAojrY0VhqA+vY3I=
Encrypted with crypto: U2FsdGVkX18xMjM0NTY3OLl80vSUI4464+tcG/S4C9M=
The encrypted results are different.

How to get nodejs encryption consistent with openssl?


Solution

  • The following changes are required so that the crypto code generates the same ciphertext as the OpenSSL statement:

    With these changes, the crypto code returns the same ciphertext as the OpenSSL statement.