Home > Enterprise >  C# .NET AES interoperability with JavaScript
C# .NET AES interoperability with JavaScript

Time:06-02

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)

CodePudding user response:

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

CodePudding user response:

Since you are using NodeJS, it makes sense to use the crypto module of NodeJS instead of CryptoJS (see the other answer). CryptoJS is of course also possible. Then the following must be considered:

In the JavaScript code, CBC mode has to be used, and key and IV must be passed as WordArray to CryptoJS.AES.decrypt(). The ciphertext can be passed Base64 encoded, CryptoJS implicitly converts this to a CipherParams object.

CryptoJS applies CBC and PKCS#7 padding by default, so these do not need to be explicitly specified (but may of course).

The ciphertext in the following example was generated with the C# code and can be decrypted with the following CryptoJS code:

const ciphertext = 'yKiV9TBw3eNt2QvK1kdXaw=='; 
const keyStr = "01234567890123456789012345678901"; // 32 bytes -> AES-256
const key = CryptoJS.enc.Utf8.parse(keyStr);
const IV = CryptoJS.enc.Utf8.parse(keyStr.substr(0, 16));
const decrypted = CryptoJS.AES.decrypt(ciphertext, key, {iv: IV}); // apply default: CBC and PKCS#7 padding

console.log(decrypted.toString(CryptoJS.enc.Utf8)); // Hello world!
<script src="https://cdnjs.cloudflare.com/ajax/libs/crypto-js/4.1.1/crypto-js.min.js"></script>

Regarding security:
It's insecure to use the key (or a part of it) as IV. Instead, a random IV should be generated for each encryption. This is not secret and is passed together with the ciphertext, in general concatenated.
Also the generation of the key from a text reduces security (even with correct size the value range is reduced). More secure would be to use a reliable key derivation function like PBKDF2 if a text/passphrase is involved.

  • Related