Scopperloit
Scopperloit

Reputation: 961

Encryption with Crypto in Node.js does not give expected result

I have an old embedded device that expects an encrypted password with a given cipher, key and IV. An existing application written in .NET framework creates an AES-128-CBC encrypted string, and I'm re-writing this in Node.js using the Crypto library.

However, when I encrypt a string with the same key and IV in Node.js, the output is not the same as in .NET. I've double checked the encryption with online tools, and they yield the same result as .NET, so Node.js Crypto is the odd one out.

The C# code for .NET (the key and IV has been substituted for demonstration purpose):

byte[] encrypted;
        
        using (AesManaged aes = new AesManaged())
        {
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.Zeros;
            aes.Key = new byte[] { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff };
            aes.IV = new byte[] { 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99 };

            ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);

            using (MemoryStream msEncrypt = new MemoryStream())
            {
                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                    {
                        swEncrypt.Write("admin");
                    }
                    encrypted = msEncrypt.ToArray();
                }
            }
        } 
        return encrypted;

This gives the Base64 string gE1twb8LV/44oO/6UKwwCg==. I've tested the same cipher, key and IV with online tools, and they give the same output.

My Node.js code:

const crypto = require('crypto');

const key = Buffer.from('00112233445566778899aabbccddeeff', 'hex');
const iv = Buffer.from('aabbccddeeff00112233445566778899', 'hex');

const plaintext = 'admin';
var cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
cipher.update(Buffer.from('admin'));
var encrypted = cipher.final().toString('base64');

This gives the Base64 string 4TT9E4WDH/TmlKjJdVFeHQ==.

I have tried everything I can think of with no luck. I've noticed that the .NET code specifies a zero-padding, but I've not been able to find a way to set this in the Node.js Crypto. I'm guessing this is probably why it doesn't give the same output.

Anyone know how to specify zero-padding in Node.js Crypto?

Upvotes: 3

Views: 1964

Answers (1)

Topaco
Topaco

Reputation: 49131

As you have already noticed, the paddings do not match. In the C# code zero padding is specified, in the NodeJS code PKCS#7 is used by default. Since the crypto module of NodeJS does not support zero padding, you have to implement this yourself and disable the default padding.

Also, input and output encoding must be defined for the update() and final() calls and the portions must be concatenated. In the following NodeJS code, UTF-8 is specified as input encoding and Base64 as output encoding:

const crypto = require('crypto');

const key = Buffer.from('00112233445566778899aabbccddeeff', 'hex');
const iv = Buffer.from('aabbccddeeff00112233445566778899', 'hex');

const plaintext = 'admin';
var cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
cipher.setAutoPadding(false); // Disable default PKCS#7 padding
var paddedPlaintext = zeroPad(plaintext, 16); // Zero pad plaintext
var encrypted = cipher.update(paddedPlaintext, 'utf8', 'base64'); // Specify input-/outputencoding
encrypted += cipher.final('base64'); // Specify outputencoding and concatenate
console.log(encrypted); // gE1twb8LV/44oO/6UKwwCg==

function zeroPad(text, bs){
    var padLength = text.length;
    if (text.length % bs > 0){
      padLength += bs - text.length % bs;
    }
    return text.padEnd(padLength, '\0');
}

with the output:

gE1twb8LV/44oO/6UKwwCg==

which matches the output of the C# code.


Note that a static IV is insecure. It is more secure to use a randomly generated IV for each encryption. For testing purposes, of course, this is fine.
Also, zero padding is unreliable. A better choice is PKCS#7 padding (which is the crypto default).

Upvotes: 3

Related Questions