Create 16 byte key for AES Encryption from Salt and Password in Node JS Crypto

Avinash picture Avinash · Dec 4, 2018 · Viewed 7.8k times · Source

I'm trying to match an AES 256 CBC encryption implemented in C# by using node JS crypto module.

This is my C# code

using System;
using System.Security.Cryptography; 
using System.Text;
public class Program
{
    public static void Main()
    {
        Console.WriteLine(EncryptExt("Hello World"));
        Console.WriteLine(DecryptExt(EncryptExt("Hello World")));
    }

    public static string EncryptExt(string raw)

        {

            using (var csp = new AesCryptoServiceProvider())

            {

                ICryptoTransform e = GetCryptoTransformExt(csp, true);

                byte[] inputBuffer = Encoding.UTF8.GetBytes(raw);

                byte[] output = e.TransformFinalBlock(inputBuffer, 0, inputBuffer.Length);



                string encrypted = Convert.ToBase64String(output);



                return encrypted;

            }

        }



        public static string DecryptExt(string encrypted)

        {

            using (var csp = new AesCryptoServiceProvider())

            {

                var d = GetCryptoTransformExt(csp, false);

                byte[] output = Convert.FromBase64String(encrypted);

                byte[] decryptedOutput = d.TransformFinalBlock(output, 0, output.Length);



                string decypted = Encoding.UTF8.GetString(decryptedOutput);

                return decypted;

            }

        }



        private static ICryptoTransform GetCryptoTransformExt(AesCryptoServiceProvider csp, bool encrypting)

        {

            csp.Mode = CipherMode.CBC;

           csp.Padding = PaddingMode.PKCS7;

            var passWord = Convert.ToString("AvbSkj3BVbf4o6mdlAofDp0/SD0susEWo0pKdmqas");

            var salt = Convert.ToString("ABj4PQgf3j5gblQ0iDp0/Gb07ukQWo0a");



            String iv = Convert.ToString("aAB1jhPQ89o=f619");



            var spec = new Rfc2898DeriveBytes(Encoding.UTF8.GetBytes(passWord), Encoding.UTF8.GetBytes(salt), 65536);

            byte[] key = spec.GetBytes(16);





            csp.IV = Encoding.UTF8.GetBytes(iv);

            csp.Key = key;

            if (encrypting)

            {

                return csp.CreateEncryptor();

            }

            return csp.CreateDecryptor();

        }

}

And this is my Node JS implementation

const crypto = require('crypto'),
  algorithm = 'aes-128-cbc',
  password = 'AvbSkj3BVbf4o6mdlAofDp0/SD0susEWo0pKdmqas',
  salt = 'ABj4PQgf3j5gblQ0iDp0/Gb07ukQWo0a',
  iv = 'aAB1jhPQ89o=f619',
  inputEncoding = 'utf8',
  outputEncoding = 'base64';


 function encrypt(text) {
  let cipher = crypto.createCipheriv(algorithm,createHashPassword(), iv);
  let encrypted = cipher.update(text, inputEncoding, outputEncoding)
  encrypted += cipher.final(outputEncoding);
  return encrypted;
}

function createHashPassword(){
    let nodeCrypto = crypto.pbkdf2Sync(Buffer.from(password), Buffer.from(salt), 65536, 16, 'sha1');

    return nodeCrypto || nodeCrypto.toString('hex');
};

function decrypt(encrypted) {
  let decipher = crypto.createDecipheriv(algorithm, Buffer.from(createHashPassword(),"hex"), iv)
  let dec = decipher.update(encrypted, outputEncoding, inputEncoding)
  dec += decipher.final(inputEncoding);
  return dec;
}

console.log(encrypt('Hello World'));
console.log(decrypt(encrypt('Hello World')));

The encrypted data from both this options are coming different hence, not able to work this out.

So far what I have seen is,

  • node crypto createCipheriv method takes only 32 byte buffer and if I pass it a 16 byte buffer it says, invalid length.
  • If I convert the 16 byte key to hex encoded string, the encrypted value changes and does not match with the C# implementation.
  • I can't change the C# implementation as its already in production and been used by multiple applications.
  • So there seems to be an issue with generating key from salt and password in node js, matching what is done in C# and I'm not able to figure that out.

Code can be tested in the below link: C# Implementation: https://dotnetfiddle.net/bClrpW Node JS Implementation: https://runkit.com/a-vi-nash/5c062544509d8200156f6111

Answer

Afshin picture Afshin · Dec 4, 2018

It seems that you are creating a AES-128 instance in your C# code, because you are using 16 bytes keylen.

AES-256 keylen is 32 bytes, not 16 bytes.

Bugs in code:

  1. Since you set 16 bytes for key in C#, it uses AES-128, not AES-256. So you need to change node.js to AES-128 or change generated key to 32 bytes in both sides.
  2. Since you are using text string salt and password(not base64 encoded), your node.js side uses incorrect pbkdf2Sync parameters.
  3. IV len for AES algorithm in 16 bytes and you cannot use shorter ones.

Since you wanted AES-256 here are your changed to both sides:

C# side:

String iv = Convert.ToString("SOME_IV_SOME_IV_"); // 16 bytes IV
....
byte[] key = spec.GetBytes(32); // 32 bytes key

node.js side:

iv = 'SOME_IV_SOME_IV_' // 16 bytes IV similar to C#
...
// Bugs in this function
function createHashPassword(){
    // Change parameters to `base64` only if salt and password are base64. it may be true for salt, but it is can rarely be correct for password.
    let nodeCrypto = crypto.pbkdf2Sync(Buffer.from(password), Buffer.from(salt), 65536, 32, 'sha1');

    return nodeCrypto;
};

IMPORTANT NOTES:

  1. Remember that IV must be selected as a random buffer(neither fixed not text) and since it seems that you are sending it over network, you need to send IV with it too.
  2. SALT must be a random buffer(not text) and fixed on both sides.
  3. i suggest to use over 100000 iteration for PBKDF2 at least.