2

I'm trying to encrypt a value in node.js that I can decrypt in .net. I've been given the code that they use on the .net side of things for encrypting a value and i'm trying to achieve the same encrypted value in my node.js script.

I'm definitely not an encryption buff so please help me figure out where i'm going wrong. My node.js encrypted value is not matching that of the .net encrypted value, and my node.js encrypted value is actually not returning the same value every time I run the script either.

Here's the .net encryption logic:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("start:");
            string key = "mysecretkey";
            string secret = "encryptThisMessage";

            string crypto = EncryptString(secret, key);
            Console.WriteLine(crypto);

            string returnValue = DecryptString(crypto, key);
            Console.WriteLine(returnValue);
            Console.ReadKey();

        }

        /// <summary>
        /// Encrpyts the sourceString, returns this result as an Aes encrpyted, BASE64 encoded string
        /// </summary>
        /// <param name="plainSourceStringToEncrypt">a plain, Framework string (ASCII, null terminated)</param>
        /// <param name="passPhrase">The pass phrase.</param>
        /// <returns>
        /// returns an Aes encrypted, BASE64 encoded string
        /// </returns>
        public static string EncryptString(string plainSourceStringToEncrypt, string passPhrase)
        {
            //Set up the encryption objects
            using (AesCryptoServiceProvider acsp = GetProvider(Encoding.Default.GetBytes(passPhrase)))
            {
                byte[] sourceBytes = Encoding.ASCII.GetBytes(plainSourceStringToEncrypt);
                ICryptoTransform ictE = acsp.CreateEncryptor();

                //Set up stream to contain the encryption
                MemoryStream msS = new MemoryStream();

                //Perform the encrpytion, storing output into the stream
                CryptoStream csS = new CryptoStream(msS, ictE, CryptoStreamMode.Write);
                csS.Write(sourceBytes, 0, sourceBytes.Length);
                csS.FlushFinalBlock();

                //sourceBytes are now encrypted as an array of secure bytes
                byte[] encryptedBytes = msS.ToArray(); //.ToArray() is important, don't mess with the buffer

                //return the encrypted bytes as a BASE64 encoded string
                return Convert.ToBase64String(encryptedBytes);
            }
        }


        /// <summary>
        /// Decrypts a BASE64 encoded string of encrypted data, returns a plain string
        /// </summary>
        /// <param name="base64StringToDecrypt">an Aes encrypted AND base64 encoded string</param>
        /// <param name="passphrase">The passphrase.</param>
        /// <returns>returns a plain string</returns>
        public static string DecryptString(string base64StringToDecrypt, string passphrase)
        {
            //Set up the encryption objects
            using (AesCryptoServiceProvider acsp = GetProvider(Encoding.Default.GetBytes(passphrase)))
            {
                byte[] RawBytes = Convert.FromBase64String(base64StringToDecrypt);
                ICryptoTransform ictD = acsp.CreateDecryptor();

                //RawBytes now contains original byte array, still in Encrypted state

                //Decrypt into stream
                MemoryStream msD = new MemoryStream(RawBytes, 0, RawBytes.Length);
                CryptoStream csD = new CryptoStream(msD, ictD, CryptoStreamMode.Read);
                //csD now contains original byte array, fully decrypted

                //return the content of msD as a regular string
                return (new StreamReader(csD)).ReadToEnd();
            }
        }

        private static AesCryptoServiceProvider GetProvider(byte[] key)
        {
            AesCryptoServiceProvider result = new AesCryptoServiceProvider();
            result.BlockSize = 128;
            result.KeySize = 128;
            result.Mode = CipherMode.CBC;
            result.Padding = PaddingMode.PKCS7;

            result.GenerateIV();
            result.IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };

            byte[] RealKey = GetKey(key, result);
            result.Key = RealKey;
            // result.IV = RealKey;
            return result;
        }

        private static byte[] GetKey(byte[] suggestedKey, SymmetricAlgorithm p)
        {
            byte[] kRaw = suggestedKey;
            List<byte> kList = new List<byte>();

            for (int i = 0; i < p.LegalKeySizes[0].MinSize; i += 8)
            {
                kList.Add(kRaw[(i / 8) % kRaw.Length]);
            }
            byte[] k = kList.ToArray();
            return k;
        }
    }
}

My node.js script:

var crypto = require('crypto-js');
var key = "mysecretkey";
var secret = "encryptThisMessage";

e1 = crypto.AES.encrypt(secret, key, {mode: crypto.mode.CBC, padding: crypto.pad.Pkcs7});
e2 = crypto.AES.encrypt(secret, key, {mode: crypto.mode.CBC, padding: crypto.pad.Pkcs7});

console.log('e1');
console.log(crypto.enc.Hex.stringify(e1));
console.log(e1.toString());
console.log(e1.salt.toString());
console.log(e1.iv.toString());
console.log(e1.ciphertext.toString());
console.log(e1.ciphertext.toString(crypto.enc.Base64));
console.log('e2');
console.log(e2.toString());
console.log(e2.salt.toString());
console.log(e2.iv.toString());
console.log(e2.ciphertext.toString(crypto.enc.Base64));

When running the encryption piece in the c# code, the value looks like this: (slightly modified for security purposes) dp+8cjr/ajEw5oePdiG+4g==. How can I change my node.js code to output this matched encrypted value?

Output of node.js script:

enter image description here

Catfish
  • 18,876
  • 54
  • 209
  • 353
  • You are completely missing the key derivation part in your C# code. Ciphertext *should* look random even if the plaintext is the same. This is likely due to a different salt for each run. – Maarten Bodewes Jun 30 '14 at 15:26
  • Can you elaborate (maybe in an answer rather than comment)? I didn't write the c# piece and don't fully understand all of it. – Catfish Jun 30 '14 at 15:31
  • Try to use [this](http://stackoverflow.com/questions/8008253/c-sharp-version-of-openssl-evp-bytestokey-method/8011654#8011654) as a starting point. – Maarten Bodewes Jun 30 '14 at 16:16
  • So ultimately is there a problem with the c# implementation, or is it just that i'm not using all the appropriate settings/configs in the node.js version? – Catfish Jun 30 '14 at 19:02
  • Depends, if you want to use a password, change the C# implementation. To implement it using a key, add an explicit IV to the NodeJS implementation. – Maarten Bodewes Jun 30 '14 at 23:00

1 Answers1

3

You are mixing apples and oranges.

When you pass a string as key to CryptoJS, it derives a key and iv that it uses for the decryption. The string is treated as a passphrase, which is salted. Run this code a couple of times in node.js:

var key = "mysecretkey";
var secret = "encryptThisMessage";
e1 = crypto.AES.encrypt(secret, key, {mode: crypto.mode.CBC, padding: crypto.pad.Pkcs7});
console.log("key: " + crypto.enc.Base64.stringify(e1.key));
console.log("iv: " + crypto.enc.Base64.stringify(e1.iv));
console.log("salt: " + crypto.enc.Base64.stringify(e1.salt));
console.log("ciphertext: " + crypto.enc.Base64.stringify(e1.ciphertext));
p = crypto.AES.decrypt(e1, key, {mode: crypto.mode.CBC, padding: crypto.pad.Pkcs7});
console.log("decrypted: " + crypto.enc.Utf8.stringify(p));

Note that it produces different keys and IVs each time, yet it always decrypts back to the original (because e1 carries the salt which lets decrypt derive the same key). Check out this documentation for CryptoJS here.

In your C# code, you are always using the same key and IV. These don't match the key and IV in CryptoJS. Try this code which exactly matches the key and IV that your C# code produces:

var key = crypto.enc.Base64.parse('bXlzZWNyZXRrZXlteXNlYw=='); // Matching C# code's key
var iv = crypto.enc.Base64.parse('AAAAAAAAAAAAAAAAAAAAAA=='); // 16 ZERO bytes, same as C# code
var secret = "encryptThisMessage";
e1 = crypto.AES.encrypt(secret, key, {iv: iv, mode: crypto.mode.CBC, padding: crypto.pad.Pkcs7});
console.log("ciphertext: " + crypto.enc.Base64.stringify(e1.ciphertext));

Note that this time I am not passing CryptoJS a string for the key, to be interpreted as a passphrase, but rather, a CryptoJS word array to be interpreted directly as key bytes. Also, I am passing the IV in the params.

That last bit of code produces the same ciphertext as your C# code. I'm using Base64 here for the key and IV as a convenient shortcut to create word arrays. Use the same key and IV on both ends and it will work.

EDIT:

I thought it interesting to show the CryptoJS -- i.e. OpenSSL -- key derivation in code, so that instead of having CryptoJS match C#, have C# match CryptoJS.

OpenSSL key derivation is described here.

This derives the key and IV -- kept simple for clarity:

public byte[] Derive48(string passphrase, byte[] salt)
{
    using (var md5 = new MD5CryptoServiceProvider())
    {
        var source = Encoding.UTF8.GetBytes(passphrase).Concat(salt).ToArray();

        var data = md5.ComputeHash(source);

        var output = data;

        while (output.Length < 48)
        {
            data = md5.ComputeHash(data.Concat(source).ToArray());

            output = output.Concat(data).ToArray();
        }

        return output.Take(48).ToArray();
    }
}

You can use it like this:

string key = "mysecretkey";
string secret = "encryptThisMessage";
byte[] salt = Convert.FromBase64String("zTEeMVPN2eY=");

string crypto = EncryptString(secret, key, salt);
Console.WriteLine(crypto);

string returnValue = DecryptString(crypto, key, salt);
Console.WriteLine(returnValue);

...

public string EncryptString(string plainSourceStringToEncrypt, string passPhrase, byte[] salt)
{
    //Set up the encryption objects
    using (AesCryptoServiceProvider acsp = GetProvider(passPhrase, salt))
    {

...

private AesCryptoServiceProvider GetProvider(string passphrase, byte[] salt)
{
    AesCryptoServiceProvider result = new AesCryptoServiceProvider();
    result.BlockSize = 128;
    result.KeySize = 128;
    result.Mode = CipherMode.CBC;
    result.Padding = PaddingMode.PKCS7;

    var derived = this.Derive48(passphrase, salt);

    result.Key = derived.Take(32).ToArray();
    result.IV = derived.Skip(32).Take(16).ToArray();

    return result;
}
Jim Flood
  • 8,144
  • 3
  • 36
  • 48