Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

AES256 Encryption/Decryption in both NodeJS and C#

AES256 Encryption/Decryption in both NodeJS and C#

Problem

I've taken some liberties with the results of the following questions:

  • AES encrypt in .NET and decrypt with Node.js crypto?
  • Decrypting AES256 encrypted data in .NET from node.js - how to obtain IV and Key from passphrase
  • C# version of OpenSSL EVP_BytesToKey method?

And created the following class file...

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

namespace T1.CoreUtils.Utilities
{
  public static class CryptoUtility
  {
    public static string Encrypt(string input, string passphrase = null)
    {
      byte[] key, iv;
      DeriveKeyAndIV(Encoding.ASCII.GetBytes(passphrase), null, 1, out key, out iv);

      return Convert.ToBase64String(EncryptStringToBytes(input, key, iv));
    }

    public static string Decrypt(string inputBase64, string passphrase = null)
    {
      byte[] key, iv;
      DeriveKeyAndIV(Encoding.ASCII.GetBytes(passphrase), null, 1, out key, out iv);

      return DecryptStringFromBytes(Convert.FromBase64String(inputBase64), key, iv);
    }

    private static void DeriveKeyAndIV(byte[] data, byte[] salt, int count, out byte[] key, out byte[] iv)
    {
      List hashList = new List();
      byte[] currentHash = new byte[0];

      int preHashLength = data.Length + ((salt != null) ? salt.Length : 0);
      byte[] preHash = new byte[preHashLength];

      System.Buffer.BlockCopy(data, 0, preHash, 0, data.Length);
      if (salt != null)
        System.Buffer.BlockCopy(salt, 0, preHash, data.Length, salt.Length);

      MD5 hash = MD5.Create();
      currentHash = hash.ComputeHash(preHash);

      for (int i = 1; i 

From here, I generated the following via node:

var crypto = require('crypto');
var input = "This is î╥≤ what it is.";
var passkey= "This is my password.";
var cipher = crypto.createCipher('aes-256-cbc', passkey);
var encrypted = cipher.update(input, 'utf8', 'base64') + cipher.final('base64');
encrypted
// '9rTbNbfJkYVE2m5d8g/8b/qAfeCU9rbk09Na/Pw0bak='

input = "I am the walrus, coo coo cachoo!";
passkey = "I am a ≥ò'ÿ boy baby!";
cipher = crypto.createCipher('aes-256-cbc', passkey);
encrypted = cipher.update(input, 'utf8', 'base64') + cipher.final('base64');
// 'j/e+f5JU5yerSvO7FBJzR1tGro0Ie3L8sWYaupRW1JJhraGqBfQ9z+h85VhSzEjD'

var decipher = crypto.createDecipher('aes-256-cbc', passkey);
var plain = decipher.update(encrypted, 'base64', 'utf8') + decipher.final('utf8');
plain
// 'I am the walrus, coo coo cachoo!'

From this, I create the following test case:

using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace T1.CoreUtils.Test.Utilities.Tests
{
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        public void EncryptReturnsExpectedValue1_unicode_in_plaintext()
        {
            var passkey = "This is my password.";
            var plain = "This is î╥≤ what it is.";
            var encrypted = "9rTbNbfJkYVE2m5d8g/8b/qAfeCU9rbk09Na/Pw0bak=";

            var actual = T1.CoreUtils.Utilities.CryptoUtility.Encrypt(plain, passkey);
            Assert.AreEqual(encrypted, actual);
        }

        [TestMethod]
        public void EncryptReturnsExpectedValue2_unicode_in_passkey()
        {
            var passkey = "I am a ≥ò'ÿ boy baby!";
            var plain = "I am the walrus, coo coo cachoo!";
            var encrypted = "j/e+f5JU5yerSvO7FBJzR1tGro0Ie3L8sWYaupRW1JJhraGqBfQ9z+h85VhSzEjD";

            var actual = T1.CoreUtils.Utilities.CryptoUtility.Encrypt(plain, passkey);
            Assert.AreEqual(encrypted, actual);
        }

        [TestMethod]
        public void DecryptReturnsExpectedValue1()
        {
            var passkey = "This is my password.";
            var plain = "This is î╥≤ what it is.";
            var encrypted = "9rTbNbfJkYVE2m5d8g/8b/qAfeCU9rbk09Na/Pw0bak=";

            var actual = T1.CoreUtils.Utilities.CryptoUtility.Decrypt(encrypted, passkey);
            Assert.AreEqual(plain, actual);
        }

        [TestMethod]
        public void DecryptReturnsExpectedValue2()
        {
            var passkey = "I am a ≥ò'ÿ boy baby!";
            var plain = "I am the walrus, coo coo cachoo!";
            var encrypted = "j/e+f5JU5yerSvO7FBJzR1tGro0Ie3L8sWYaupRW1JJhraGqBfQ9z+h85VhSzEjD";

            var actual = T1.CoreUtils.Utilities.CryptoUtility.Decrypt(encrypted, passkey);
            Assert.AreEqual(plain, actual);
        }
    }
}

Passes:

  • EncryptReturnsExpectedValue1_unicode_in_plaintext
  • DecryptReturnsExpectedValue1

Fails:

  • EncryptReturnsExpectedValue2_unicode_in_passkey
  • DecryptReturnsExpectedValue2

I can only guess that the issue is in the DeriveKeyAndIV method. Will try a few different approaches and answer if I find it on my own.

Problem courtesy of: Tracker1

Solution

Okay, upon inspecting the node.js source for crypto, I determined, that the encoding was using a new Buffer(passkey, 'binary'), which was only using the original value xand 0xFF for the bytes used, so I created a matching method in C#... here's the method in question...

private static byte[] RawBytesFromString(string input)
{
  var ret = new List();

  foreach (char x in input)
  {
    var c = (byte)((ulong)x & 0xFF);
    ret.Add(c);
  }

  return ret.ToArray();
}

And the updated/working CryptoUtil.cs

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

namespace T1.CoreUtils.Utilities
{
  public static class CryptoUtility
  {
    /*  Wanting to stay compatible with NodeJS
     *  http://stackoverflow.com/questions/18502375/aes256-encryption-decryption-in-both-nodejs-and-c-sharp-net/
     *  http://stackoverflow.com/questions/12261540/decrypting-aes256-encrypted-data-in-net-from-node-js-how-to-obtain-iv-and-key
     *  http://stackoverflow.com/questions/8008253/c-sharp-version-of-openssl-evp-bytestokey-method
     *  
     * var cipher = crypto.createCipher('aes-256-cbc', 'passphrase');
     * var encrypted = cipher.update("test", 'utf8', 'base64') + cipher.final('base64');
     * 
     * var decipher = crypto.createDecipher('aes-256-cbc', 'passphrase');
     * var plain = decipher.update(encrypted, 'base64', 'utf8') + decipher.final('utf8');
     */

    public static string Encrypt(string input, string passphrase = null)
    {
      byte[] key, iv;
      DeriveKeyAndIV(RawBytesFromString(passphrase), null, 1, out key, out iv);

      return Convert.ToBase64String(EncryptStringToBytes(input, key, iv));
    }

    public static string Decrypt(string inputBase64, string passphrase = null)
    {
      byte[] key, iv;
      DeriveKeyAndIV(RawBytesFromString(passphrase), null, 1, out key, out iv);

      return DecryptStringFromBytes(Convert.FromBase64String(inputBase64), key, iv);
    }

    private static byte[] RawBytesFromString(string input)
    {
      var ret = new List();

      foreach (char x in input)
      {
        var c = (byte)((ulong)x & 0xFF);
        ret.Add(c);
      }

      return ret.ToArray();
    }

    private static void DeriveKeyAndIV(byte[] data, byte[] salt, int count, out byte[] key, out byte[] iv)
    {
      List hashList = new List();
      byte[] currentHash = new byte[0];

      int preHashLength = data.Length + ((salt != null) ? salt.Length : 0);
      byte[] preHash = new byte[preHashLength];

      System.Buffer.BlockCopy(data, 0, preHash, 0, data.Length);
      if (salt != null)
        System.Buffer.BlockCopy(salt, 0, preHash, data.Length, salt.Length);

      MD5 hash = MD5.Create();
      currentHash = hash.ComputeHash(preHash);

      for (int i = 1; i 

NOTE: Some more code related to this...

  • https://github.com/tracker1/T1.CoreUtils/blob/master/T1.CoreUtils/Utilities/CryptoUtility.cs
  • https://github.com/tracker1/t1-coreutils-node/blob/master/lib/hashutils.js

These are not in nuget or npm respectively as they really don't belong there... it's mainly for ideas and reference. I do need to flush out the node side a bit better so it matches up.

Solution courtesy of: Tracker1

Discussion

View additional discussion.



This post first appeared on Node.js Recipes, please read the originial post: here

Share the post

AES256 Encryption/Decryption in both NodeJS and C#

×

Subscribe to Node.js Recipes

Get updates delivered right to your inbox!

Thank you for your subscription

×