Node.JS RC2-CBC Encryption and Decryption ciphers are not matching with C#

2.1k Views Asked by At

I have an existing encryption and decryption login inplemented in C# using RSC2-cbc algorithm using Key and IV. Now I am going to implement the same in node.js. So I have written following code to encryprt and decrypt. The problem I am facing is the node.js encrypted string (chiper) or decrypted string are not matching with C# encryptrd strings.

Existing C# code

byte[] arrbIV = Encoding.ASCII.GetBytes("dleftaba");
byte[] arrbKey = Encoding.ASCII.GetBytes(Key); 
byte[] arrbData = Encoding.ASCII.GetBytes(sData); //Text to be encryptrd

RC2 oEncryptor = new RC2CryptoServiceProvider();

oEncryptor.Mode = CipherMode.CBC;
oEncryptor.Key = arrbKey;
oEncryptor.IV = arrbIV;

// Create memory stream to store encrypted string
MemoryStream oMemoryStream = new MemoryStream();
CryptoStream oCryptoStream = new CryptoStream(oMemoryStream, oEncryptor.CreateEncryptor(),      
CryptoStreamMode.Write);

 // Peform the encryption
 oCryptoStream.Write(arrbData, 0, arrbData.Length);
 // We have written all the data in the stream and now we can apply padding
 oCryptoStream.Close();
 string sRetVal = Convert.ToBase64String(oMemoryStream.ToArray());

Equivalent/ Translated node.js code

var crypto = require('crypto')

var SECRET_KEY = "435353553" 

var IV = "dleftaba"

var ENCODING = 'base64'

var text = "My Text"


Encryption 

var cipher = crypto.createCipheriv('rc2-cbc',key,  iv) 
var cryptedPassword = cipher.update(text, 'utf-8', 'base64')
cryptedPassword+= cipher.final('base64')

Decryption

var decipher = crypto.createDecipheriv('rc2-cbc', SECRET_KEY, IV)
var decryptedPassword = decipher.update(cryptedPassword, 'base64','utf-8')
decryptedPassword += decipher.final('utf-8')

Please suggest what is going wrong over here. Why node. js is not resulting into the identical chiper like C#.
2

There are 2 best solutions below

0
On

This appears to be a duplicate of this question. Answering here too in case it helps.

I hit a similar situation. There is existing .NET (core) code using RC2CryptoServiceProvider to decrypt a string. I wanted to replicate this in node.

The .NET code uses keysize 128 (which also appears to be the default) so I assumed the comparable algorithm in node (openssl) would be rc2-128. But this always failed when decrypting.

After some trial and error I discovered that using using the rc2-64 algorithm in node behaves the same as the .NET code using keysize 128. Just don't ask me why!

0
On

Though not the same algorithm you are using... here's my reference implementation in C# ... I've put comments on what the node implementation should be... and it matches up...

My goal at the time was to keep the .Net side compatible with the node side's defaults...

Related:

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
{
    public static class CryptoUtility
    {
        /*  Wanting to stay compatible with NodeJS
         *  https://stackoverflow.com/questions/18502375/aes256-encryption-decryption-in-both-nodejs-and-c-sharp-net/
         *  https://stackoverflow.com/questions/12261540/decrypting-aes256-encrypted-data-in-net-from-node-js-how-to-obtain-iv-and-key
         *  https://stackoverflow.com/questions/8008253/c-sharp-version-of-openssl-evp-bytestokey-method
         */

        /* EncrypteDefault - as NodeJS
         * var cipher = crypto.createCipher('aes-256-cbc', 'passphrase');
         * var encrypted = cipher.update("test", 'utf8', 'base64') + cipher.final('base64');
         */
        public static string EncryptDefault(string input, string passphrase = null)
        {
            byte[] key, iv;
            PassphraseToDefaultKeyAndIV(RawBytesFromString(passphrase), null, 1, out key, out iv);

            return Convert.ToBase64String(EncryptBytes(Encoding.UTF8.GetBytes(input), key, iv));
        }

        /* DecryptDefault - as NodeJS
         * var decipher = crypto.createDecipher('aes-256-cbc', 'passphrase');
         * var plain = decipher.update(encrypted, 'base64', 'utf8') + decipher.final('utf8');
         */
        public static string DecryptDefault(string inputBase64, string passphrase = null)
        {
            byte[] key, iv;
            PassphraseToDefaultKeyAndIV(RawBytesFromString(passphrase), null, 1, out key, out iv);

            return Encoding.UTF8.GetString(DecryptBytes(Convert.FromBase64String(inputBase64), key, iv));
        }

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

            return Convert.ToBase64String(EncryptBytes(Encoding.UTF8.GetBytes(input), key, iv));
        }

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

            return Encoding.UTF8.GetString(DecryptBytes(Convert.FromBase64String(inputBase64), key, iv));

        }

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

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

            return ret.ToArray();
        }

        public static void PassphraseToSCryptKeyAndIV(string passphrase, out byte[] key, out byte[] iv)
        {
            var hashList = HashUtility.HashSCrypt(Encoding.UTF8.GetBytes(passphrase)).ToList();
            key = new byte[32];
            iv = new byte[16];
            hashList.CopyTo(0, key, 0, 32);
            hashList.CopyTo(32, iv, 0, 16);
        }

        public static void PassphraseToDefaultKeyAndIV(byte[] data, byte[] salt, int count, out byte[] key, out byte[] iv)
        {
            List<byte> hashList = new List<byte>();
            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 < count; i++)
            {
                currentHash = hash.ComputeHash(currentHash);
            }

            hashList.AddRange(currentHash);

            while (hashList.Count < 48) // for 32-byte key and 16-byte iv
            {
                preHashLength = currentHash.Length + data.Length + ((salt != null) ? salt.Length : 0);
                preHash = new byte[preHashLength];

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

                currentHash = hash.ComputeHash(preHash);

                for (int i = 1; i < count; i++)
                {
                    currentHash = hash.ComputeHash(currentHash);
                }

                hashList.AddRange(currentHash);
            }
            hash.Clear();
            key = new byte[32];
            iv = new byte[16];
            hashList.CopyTo(0, key, 0, 32);
            hashList.CopyTo(32, iv, 0, 16);
        }

        public static byte[] EncryptBytes(byte[] input, byte[] Key, byte[] IV)
        {
            // Check arguments. 
            if (input == null || input.Length <= 0)
                return new byte[0]; //nothing to encode
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("Key");

            // Create an RijndaelManaged object 
            // with the specified key and IV. 
            using (RijndaelManaged cipher = new RijndaelManaged())
            {
                cipher.Key = Key;
                cipher.IV = IV;
                cipher.Mode = CipherMode.CBC;
                cipher.Padding = PaddingMode.PKCS7;

                // Create a decrytor to perform the stream transform.
                ICryptoTransform encryptor = cipher.CreateEncryptor(cipher.Key, cipher.IV);

                // Create the streams used for encryption. 
                using (MemoryStream outputStream = new MemoryStream())
                {
                    using (CryptoStream encryptStream = new CryptoStream(outputStream, encryptor, CryptoStreamMode.Write))
                    {
                        encryptStream.Write(input, 0, input.Length);
                        encryptStream.FlushFinalBlock();
                        outputStream.Seek(0, 0);
                        return outputStream.ToArray();
                    }
                }
            }

        }

        public static byte[] DecryptBytes(byte[] cipherText, byte[] Key, byte[] IV)
        {
            // Check arguments. 
            if (cipherText == null || cipherText.Length <= 0)
                throw new ArgumentNullException("cipherText");
            if (Key == null || Key.Length <= 0)
                throw new ArgumentNullException("Key");
            if (IV == null || IV.Length <= 0)
                throw new ArgumentNullException("Key");

            // Create an RijndaelManaged object 
            // with the specified key and IV. 
            using (var cipher = new RijndaelManaged())
            {
                cipher.Key = Key;
                cipher.IV = IV;
                cipher.Mode = CipherMode.CBC;
                cipher.Padding = PaddingMode.PKCS7;

                // Create a decrytor to perform the stream transform.
                ICryptoTransform decryptor = cipher.CreateDecryptor(cipher.Key, cipher.IV);

                // Create the streams used for decryption. 
                using (var inputStream = new MemoryStream(cipherText))
                {
                    using (var outputStream = new MemoryStream())
                    {
                        using (CryptoStream decryptedStream = new CryptoStream(inputStream, decryptor, CryptoStreamMode.Read))
                        {
                            var buffer = new byte[1024];
                            decryptedStream.Flush();
                            var read = decryptedStream.Read(buffer, 0, buffer.Length);
                            while (read > 0)
                            {
                                outputStream.Write(buffer, 0, read);
                                decryptedStream.Flush();
                                read = decryptedStream.Read(buffer, 0, buffer.Length);
                            }
                            outputStream.Seek(0, 0);
                            return outputStream.ToArray();
                        }
                    }
                }
            }
        }
    }
}

Node.js

var crypto = require('crypto');

module.exports = {
  encrypt: encryptValue,
  decrypt: decryptValue
}

function encryptValue(input, passphrase) {
  var cipher = crypto.createCipher('aes-256-cbc', passphrase);
  var encrypted = cipher.update(input, 'utf8', 'base64') + cipher.final('base64');
  return encrypted;
}

function decryptValue(inputBase64, passphrase) {
  var decipher = crypto.createDecipher('aes-256-cbc', passphrase);
  var plain = decipher.update(inputBase64, 'base64', 'utf8') + decipher.final('utf8');
  return plain;
}

My .Net code references HashUtility which isn't strictly needed, and I don't have my full node.js implementation with scrypt available... It was mainly for testing against.