Search code examples
javascriptc#cryptographyaescryptojs

C# .NET AES interoperability with JavaScript


I've been trying to encrypt and decrypt strings in by AES and C# in an interoperable way. My client application is a Node server that communicates with vendor's API is in dot NET.

The vendor uses these methods for encrypting and decrypting the strings:

public static string Encrypt(string data, string key)
{
  string IV = key.Substring(0, 16);
  byte[] iv = Encoding.UTF8.GetBytes(IV);
  byte[] array;
  using(Aes aes = Aes.Create())
  {
    aes.Key = Encoding.UTF8.GetBytes(key);
    aes.IV = iv;
    ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
    using(MemoryStream memoryStream = new MemoryStream())
    {
      using(CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, encryptor, CryptoStreamMode.Write))
      {
        using(StreamWriter streamWriter = new StreamWriter((Stream)cryptoStream))
        {
          streamWriter.Write(data);
        }
        array = memoryStream.ToArray();
      }
    }
  }
  return Convert.ToBase64String(array);
}



public static string Decrypt(string data, string key)
{
  string IV = key.Substring(0, 16);
  byte[] iv = Encoding.UTF8.GetBytes(IV);
  byte[] buffer = Convert.FromBase64String(data);
  using(Aes aes = Aes.Create())
  {
    aes.Key = Encoding.UTF8.GetBytes(key);
    aes.IV = iv;
    ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
    using(MemoryStream memoryStream = new MemoryStream(buffer))
    {
      using(CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, decryptor, CryptoStreamMode.Read))
      {
        using(StreamReader streamReader = new StreamReader((Stream)cryptoStream))
        {
          return streamReader.ReadToEnd();
        }
      }
    }
  }
}

I tried for example crypto-js for decrypting the strings, but I cannot make it work:

const encryptedText = CryptoJS.enc.Base64.parse(base64Value)
const encrypted2 = encryptedText.toString(CryptoJS.enc.Base64);
const decrypt2 = CryptoJS.AES.decrypt(encrypted2, key, {
 mode: CryptoJS.mode.ECB,
 padding: CryptoJS.pad.Pkcs7
});

console.log(decrypt2.toString(CryptoJS.enc.Utf8)) // (also tried various other encodings, Utf16, Utf16LE and others)

Solution

  • The following Node.js code should work correctly with your .NET code.

    We're using the algorithm aes-256-cbc to match the mode used in the C# example.

    const crypto = require("crypto");
    const Algorithm = "aes-256-cbc";
    
    function encrypt(plainText, key, iv, outputEncoding = "base64") {
        const cipher = crypto.createCipheriv(Algorithm, key, iv);
        const output = Buffer.concat([cipher.update(plainText), cipher.final()]).toString(outputEncoding);
        return output.replace('+', '-').replace('/', '_').replace('=', '');
    }
    
    function decrypt(cipherText, key, iv, outputEncoding = "utf8") {
        cipherText = Buffer.from(cipherText, "base64");
        const cipher = crypto.createDecipheriv(Algorithm, key, iv);
        return Buffer.concat([cipher.update(cipherText), cipher.final()]).toString(outputEncoding);
    }
    
    const KEY = 'KFmnMAPzP!g@6Dy5HD?JSgYC9obE&m@m';
    const IV = KEY.slice(0,16);
    
    // Taking the output from our C# sample...
    const encrypted = 'SORoNS48u0KniiANU3Y9Mw==';
    console.log("Encrypted (base64):", encrypted);
    const decrypted = decrypt(encrypted, KEY, IV)
    console.log("Decrypted:", decrypted);
    

    The equivalent C# code is below:

    using System;
    using System.Text;
    using System.Security.Cryptography;
    using System.IO;
                        
    public class Program
    {
        public static void Main()
        {
            var str = Encrypt("test", "KFmnMAPzP!g@6Dy5HD?JSgYC9obE&m@m");
            Console.WriteLine("Encrypted: " + str);
            Console.WriteLine("Decrypted: " + Decrypt(str, "KFmnMAPzP!g@6Dy5HD?JSgYC9obE&m@m"));
        }
        
        public static string Encrypt(string data, string key)
        {
          string IV = key.Substring(0, 16);
          byte[] iv = Encoding.UTF8.GetBytes(IV);
          byte[] array;
          using(Aes aes = Aes.Create())
          {
            aes.Key = Encoding.UTF8.GetBytes(key);
            aes.IV = iv;
            ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
            using(MemoryStream memoryStream = new MemoryStream())
            {
              using(CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, encryptor, CryptoStreamMode.Write))
              {
                using(StreamWriter streamWriter = new StreamWriter((Stream)cryptoStream))
                {
                  streamWriter.Write(data);
                }
                array = memoryStream.ToArray();
              }
            }
          }
          return Convert.ToBase64String(array);
        }
    
    
    
        public static string Decrypt(string data, string key)
        {
          string IV = key.Substring(0, 16);
          byte[] iv = Encoding.UTF8.GetBytes(IV);
          byte[] buffer = Convert.FromBase64String(data);
          using(Aes aes = Aes.Create())
          {
            aes.Key = Encoding.UTF8.GetBytes(key);
            aes.IV = iv;
            ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
            using(MemoryStream memoryStream = new MemoryStream(buffer))
            {
              using(CryptoStream cryptoStream = new CryptoStream((Stream)memoryStream, decryptor, CryptoStreamMode.Read))
              {
                using(StreamReader streamReader = new StreamReader((Stream)cryptoStream))
                {
                  return streamReader.ReadToEnd();
                }
              }
            }
          }
        }
    }
    

    The output for the C# code is:

     Encrypted: SORoNS48u0KniiANU3Y9Mw==
     Decrypted: test
    

    The Node.js code then decrypts this (using the same key and IV):

     Encrypted (base64): SORoNS48u0KniiANU3Y9Mw=
     Decrypted: test