views:

2051

answers:

4

Hi,

I'm trying to setup AES encryption / decryption using the javascript library SlowAES and the RijndaelManaged class in .NET.

I chose this method after reading this post, where Cheeso has managed to get these two encryption methods to play together

"In my tests of the COM-wrapped-SlowAEs, I used CBC mode, and the encryption was completely compatible with the RijndaelManaged class in .NET" - Cheeso

I've taken the javascript code from Cheeso's Windows Scripting Component, the latest slowaes libraries, and using the following javascript script to test:

var key = "12345678901234567890123456789012";
var message = "watson?";
var decrypted;

slowAES.aes.keySize.SIZE_256;
slowAES.modeOfOperation.CBC;
put_PassPhrase(key);
var result = EncryptString(message);
decrypted = DecryptCommaDelimitedStringToString(result)
document.write("Key:" + key + "<br />original:" + message + "<br />Cypher:" + result + "<br />Decrypted:" + decrypted + "<br />IV(): " + get_IV());

I'm getting the following output:

Key:12345678901234567890123456789012
original:watson?
Cypher:245,159,1,1,168,1,1,143,1,1,146,1,1,239,117,1
Decrypted:watson? 
IV(): 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

I've modified the following example found on MSDN to try and match the encryption in C# :

public static void Main()
        {
            try
            {
                string original = "watson?";
                byte[] IV = new byte[16];   // match slowaes IV
                byte[] key = new System.Text.ASCIIEncoding().GetBytes("12345678901234567890123456789012");// match slowaes KEY

                RijndaelManaged myRijndael = new RijndaelManaged();
                myRijndael.BlockSize = 128;
                myRijndael.KeySize = 256;
                myRijndael.Mode = CipherMode.CBC;

                // Encrypt the string to an array of bytes.
                byte[] encrypted = encryptStringToBytes_AES(original, key, IV);

                // Decrypt the bytes to a string.
                string roundtrip = decryptStringFromBytes_AES(encrypted, key, IV);

                //Display the original data and the decrypted data.
                Console.WriteLine("Original:   {0}", original);
                Console.WriteLine("Round Trip: {0}", roundtrip);

            }
            catch (Exception e)
            {
                Console.WriteLine("Error: {0}", e.Message);
            }
        }

Watch of the byte array:

-  encrypted {byte[16]} byte[]
  [0] 139 byte
  [1] 104 byte
  [2] 166 byte
  [3] 35 byte
  [4] 8 byte
  [5] 42 byte
  [6] 216 byte
  [7] 160 byte
  [8] 235 byte
  [9] 153 byte
  [10] 23 byte
  [11] 143 byte
  [12] 105 byte
  [13] 3 byte
  [14] 24 byte
  [15] 255 byte

I've tried all the padding options with the managed .NET class, however I can't get the encrypted outputs to match. Can anyone help me?

Thanks,

Bob

+12  A: 

maybe I can help.

I took your C# code and modified it slightly. The C# code I use, in its entirety, is:

using System;
using System.Security.Cryptography;

public class Bob
{
    internal static string FormatByteArray(byte[] b)
    {
        System.Text.StringBuilder sb1 = new System.Text.StringBuilder();
        int i = 0;
        for (i = 0; i < b.Length; i++)
        {
            if (i != 0 && i % 16 == 0)
                sb1.Append("\n");
            sb1.Append(System.String.Format("{0:X2} ", b[i]));
        }
        return sb1.ToString();
    }

    public static void Main()
    {
        try
        {
            string original = "watson?";

            Console.WriteLine("Original:   {0}", original);

            byte[] IV = new byte[16];   // match slowaes IV
            var ascii = new System.Text.ASCIIEncoding();

            // match slowaes KEY
            string passPhrase = "12345678901234567890123456789012";
            byte[] key = ascii.GetBytes(passPhrase);

            RijndaelManaged myRijndael = new RijndaelManaged();
            myRijndael.BlockSize = 128;
            myRijndael.KeySize = 256;
            myRijndael.IV = IV;
            myRijndael.Padding   = PaddingMode.PKCS7;
            myRijndael.Mode = CipherMode.CBC;
            myRijndael.Key = key;

            // Encrypt the string to an array of bytes.
            byte[] plainText = new System.Text.ASCIIEncoding().GetBytes(original);
            ICryptoTransform transform = myRijndael.CreateEncryptor();
            byte[] cipherText = transform.TransformFinalBlock(plainText, 0, plainText.Length);

            Console.WriteLine("cipherText: {0}", FormatByteArray(cipherText));

            // Decrypt the bytes to a string.
            transform = myRijndael.CreateDecryptor();
            plainText = transform.TransformFinalBlock(cipherText, 0, cipherText.Length);


            string roundtrip = ascii.GetString(plainText);

            Console.WriteLine("Round Trip: {0}", roundtrip);

        }
        catch (Exception e)
        {
            Console.WriteLine("Error: {0}", e.Message);
        }
    }

}

compile the above with

csc.exe /target:exe /out:Bob.exe Bob.cs

I use the slowAES.wsc from the other post you referenced, with 2 changes: I do not call getPaddedBlock() for the key in the EncryptString or DecryptString() methods. This really needs a PBKDF but let's not worry about that now. Here's what the modified EncryptString looks like:

function EncryptString(plainText)
{
  // this is really wrong - need a PBBKDF to get the key, instead
  // of just using the passphrase
  var key = cryptoHelpers.convertStringToByteArray(_passphrase);
  // var nkey = slowAES.getPaddedBlock(key, 0, _keysize, _mode);
  var bytesToEncrypt = cryptoHelpers.convertStringToByteArray(plainText);

  var result = slowAES.encrypt(bytesToEncrypt, 
      _mode,
      key,
      _keysize,
      _iv);
  return result['cipher'];
}

This means you have to use a passPhrase that is exactly the length required by the keysize. If you use AES256, then pass a 32-char string. Seems like you figured this out already.

The client of the WSC component is also Javascript (though it can be any COM language). Here's what I used.

function toHexString(a)
{
    var ret = '';
    for(var i = 0;i < a.length;i++)
        ret += (a[i] < 16 ? '0' : '') + a[i].toString(16) + ' ';
    return ret.toLowerCase();
}


//var plaintext = "Hello. This is a test. of the emergency broadcasting system.";
var plaintext = "watson?";

try
{
    WScript.echo( "plaintext: " + plaintext);
    WScript.echo( "plaintext.length: " + plaintext.length);

    WScript.echo( "instantiate ");
    var aes = new ActiveXObject("Ionic.Com.SlowAES");

    WScript.echo( "keysize ");
    aes.KeySize = 256;

    WScript.echo( "passphrase ");
    aes.PassPhrase= "12345678901234567890123456789012";  // 32 bits long

    WScript.echo( "mode ");
    aes.Mode = "CBC";

    WScript.echo( "encrypting... ");
    var result = aes.EncryptString(plaintext);

    WScript.echo( "Cryptotext: " + toHexString(result));

    WScript.echo( "decrypting... ");
    var decrypted = aes.DecryptBytesToString(result);

    WScript.echo( "decrypted: " + decrypted);
}
catch(e)
{
    WScript.echo("Exception: " + e); 
    //     WScript.echo(e.Number + ": " + e.Name);
    WScript.echo(e.Message);
}

If I then run this code, the Javascript and C# produces the same cipher text for a plaintext of "watson?", using AES256, passphrase of 12345678901234567890123456789012, and an IV of 16 bytes of zero. The ciphertext generated is:

8B 68 A6 23 08 2A D8 A0 EB 99 17 8F 69 03 18 FF

It successfully decrypts in both cases.

EDIT: Though I packaged the slowAES encryption in a WSC, it will be interoperable running outside the COM environment as well. The WSC part is unnecessary for this question, but was necessary to demonstrate the answer for a prior question, which was, "how can I get VBScript and .NET AES to interoperate?"

EDIT2: The source code that demonstrates AES interop between Javascript or VBScript and .NET is available. I extended the basic example given here to produce test apps in 3 languages: C#, Javascript and VBScript. They all take the same set of arguments. They each use a RFC2898-compliant key derivation function. You can specify the password, salt, IV, and plaintext, as well as the number of RFC2898 iterations to use in the PBKDF2. You can easily verify that the ciphertext is the same for each of these test programs. Maybe the example will be useful for someone.

Cheeso
I really respect what efforts you spend for helping someone. +1.
Emiswelt
@Cheeso: I really admire your work to solve this question. Very good job.
backslash17
A: 

Hi Cheeso,

Absolutley amazing! Thank you so much for taking the time and effort to help me understand the process, as well as debug / fix my partial implementation. It's people like you that make the world such a great place.

Just so I'm clear- the incompatability was with SlowAES using the "getPaddedBlock()" function when encrypting / decrypting the text. I'll take your examples apart to try and learn more, as you've replaced this with the pbkdf functions.

A thousand thank you's are in order. Thank you * 1000!

Best,

Bob.

hey Bob, glad to help. I think the getPaddedBlock, in the original code, was being applied to the KEY bytes, in order to guarantee a key of length 16. That keylength was hard-coded in the code. I didn't like that part because it skipped the step of geting a key from a password, and it did not allow 32-byte (256 bit) keys. So I went and got the pbkdf2 logic, and used that to generate the appropriate length keys. No more need for getPaddedBlock.
Cheeso
A: 

Hi Cheeso, I'm using your solution in this topic. Basically I'm trying to encrypt in javascript and decrypt in .Net.
I noticed that slowAES.Encrypt function calls the getPaddedBlock function. As per your solution, I have given PKCS7 as padding. While decrypting, I get an exception like 'Padding is invalid and cannot be removed.' You have mentioned in the post that getPaddedBlock is not needed, but that's only for generating. The slowAES.Encrypt still calls the getPaddedBlock. Can you please solve my problem? Is there any way that padding can be mentioned in javascript Encrypt function?

Regards, NJ

A: 
I have ran some C AES libraries downloaded from the internet and have refer to the NIST FIPS 197 test vectors but the cipher text is 16 bytes. Can anyone see anything wrong with the code?