2

I have a 3rd party AES library for C (from Lantronix). I wrapped their API from within C#'s managed code as shown below, and it works:

    [DllImport("cbx_enc.dll", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
    static extern unsafe void VC_blockEncrypt(char* iv, char* key, int length, char* text, int RDkeyLen);

    /// <summary>
    /// Managed Encrypt Wrapper     
    /// </summary>
    /// <param name="buffer">provides the plain text and receives the same length cipher text</param>
    static readonly string key = "abcdef0123456789";
    static readonly string iv = "0123456789ABCDEF";
    public static void Encrypt(ref byte[] buffer)
    {
        var keyPtr = Marshal.StringToHGlobalAnsi(key);
        var ivPtr = Marshal.StringToHGlobalAnsi(iv);

        byte[] temp = new byte[16];
        Marshal.Copy(ivPtr, temp, 0, 16);
        int index = 0; 
        for (int i = 0; i < buffer.Length; i++)
        {
            if (index == 0)
            {
                Marshal.Copy(temp, 0, ivPtr, 16);
                unsafe
                {
                    VC_blockEncrypt((char*) ivPtr, (char*) keyPtr, 0, (char*) ivPtr, 128);
                }
                Marshal.Copy(ivPtr, temp, 0, 16);
                index = 16;
            }

            temp[16 - index] ^= buffer[i];
            buffer[i] = temp[16 - index];
            index--;
        }

        Marshal.FreeHGlobal(ivPtr);
        Marshal.FreeHGlobal(keyPtr);
    }

Now, when I wrote my own, using System.Security.Cryptography to completely avoid using their unmanaged DLL, my final ciphertext seems to differ from them! I am using the same mode, same key, same iv and same plain text, yet the algorithms are not compatible. Shown below is the property settings for the RijndaelManaged object and the code; am I missing something that causes this incompatibility?

    /// <summary>
    /// Managed Encrypt     
    /// </summary>
    /// <param name="buffer">provides the plain text and receives the same length cipher text</param>
    static readonly string key = "abcdef0123456789";
    static readonly string iv = "0123456789ABCDEF";
    public static void Encrypt(ref byte[] buffer)
    {
        using (RijndaelManaged cipher = new RijndaelManaged())
        {
            cipher.Mode = CipherMode.CFB;
            cipher.Key = Encoding.ASCII.GetBytes(key);
            cipher.IV = Encoding.ASCII.GetBytes(iv);
            cipher.Padding = PaddingMode.None;
            cipher.FeedbackSize = 128;

            ICryptoTransform encryptor = cipher.CreateEncryptor(cipher.Key, cipher.IV);
            using (MemoryStream msEncrypt = new MemoryStream())
            {
                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    using (BinaryWriter swEncrypt = new BinaryWriter(csEncrypt))
                    {
                        swEncrypt.Write(buffer);
                    }
                    buffer = msEncrypt.ToArray();
                }
            }
        }
    }

Alternatively, the algorithm that I elucidated from Lantronix architecture looks very straightforward - the API does the encryption, and XORing the output with the plain text is done in the calling-method. Whereas, with .NET library, I don't have such access to the intermediate encrypted output (or is there one?), so that I could XOR the way Lantronix does manually after the encryption...

The end goal is to stop using the unmanaged code, yet should be able to generate the same ciphertext using fully managed .NET code.

Thanks for your help in advance.

p.s. I can provide the 3rd party C library cbx_enc.dll, if you need.

Edit: @Topaco, here are some sample data as requested. Haven’t heard from the vendor as with distributing their DLL; working on it…

Common inputs to CFB:

byte[] buffer = Encoding.ASCII.GetBytes("AAAAAAAAAAAAAABBBBBBBBBBBBBBBBBD"); //plain text string key = "abcdef0123456789"; string iv = "0123456789ABCDEF";

I/O from the wrapper to the unmanaged DLL:

PlainText Hex: 4141414141414141414141414141424242424242424242424242424242424244 CipherText Hex: C9094F820428E07AE035B6749E18546C62F9D5FD4A78480215DA3625D376A271

I/O from the managed code with FeedbackSize = 128; //CFB128:

PlainText Hex: 4141414141414141414141414141424242424242424242424242424242424244 CipherText Hex: 6A1A5088ACDA505B47192093DD06CD987868BFD85278A4D7D3120CC85FCD3D83

I/O from the managed code with FeedbackSize = 8 //CFB8:

PlainText Hex: 4141414141414141414141414141424242424242424242424242424242424244 CipherText Hex: 6ACA3B1159D38568504248CDFF159C87BB2D3850EDAEAD89493BD91087ED7507

I also did the additional test using ECB to see whether their API behaves like ECB (hence comes the need for external XORing). So, I passed the IV to my ECB code as plain text as shown below, and compared it with their output right before the first XOR – they both don’t match either!

Passed IV as the PlainText to ECB : 30313233343536373839414243444546 CipherText Hex: 2B5B11C9ED9B111A065861D29C478FDA

CipherText Hex from the unmanaged DLL, before the first XOR: 88480EC34569A13BA174F735DF59162E

And finally, here is my ECB implementation for the above test:

   static readonly string key = "abcdef0123456789";
    static readonly string iv = "0123456789ABCDEF";
    public static void Encrypt(ref byte[] buffer)
    {
        buffer = Encoding.ASCII.GetBytes(iv);
        Console.WriteLine($"PlainText:  {HexHelper.ToHexString(buffer)}");

        var aes = new AesManaged
        {
            KeySize = 128,
            Key = Encoding.ASCII.GetBytes(key),
            BlockSize = 128,
            Mode = CipherMode.ECB,
            Padding = PaddingMode.None,
            IV = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
        };

        ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
        buffer = encryptor.TransformFinalBlock(buffer, 0, buffer.Length);

        Console.WriteLine($"CipherText: {HexHelper.ToHexString(buffer)}");
    } 

Thanks.

Noble
  • 135
  • 1
  • 11
  • 1
    I don’t know for sure what’s going on in your case, but one thing to be aware of is CFB mode has an extra parameter as well as key and iv. I think it might be what the `FeedbackSize` is referring to. Try setting that to 8 or 1 and see if the result matches the library. – matt Sep 10 '19 at 17:29
  • @matt, tried `feedbacksize` to 1, but it fails: `System.Security.Cryptography.CryptographicException: 'Specified feedback size is invalid.' `. Size 8 is works, but the output is still different! – Noble Sep 10 '19 at 17:56
  • 1
    Here an example would be helpful, because due to the missing .dll the upper code cannot be executed. So you should encrypt a plaintext for each of the two codes and post the plaintext, the two ciphertextes (hexadecimal or Base64 encoded) as well as the used key and IV (and all parameters that differ from the already posted data, e.g. FeedbackSize, Padding etc.). In fact, it would make most sense to provide the .dll, but only if you are allowed to. – Topaco Sep 11 '19 at 10:55
  • @Topaco, I can definitely provide you the DLL, it's about 44KB; please provide an email (or is there a way to forward it to your account in SO?). I will also post the all the data from both the modules shortly. – Noble Sep 11 '19 at 13:23
  • You could upload the file e.g. to [File Dropper](https://www.filedropper.com/) and then post the link here in your question. This would give all interested users access to it. **But you should only do this if it doesn't violate any copyrights.** If you are not absolutely sure, or if the answer you have received in the meantime solves the problem, don't do it. – Topaco Sep 11 '19 at 14:19
  • 1
    I've tried a few guesses based on your sample data, but nothing substantial has come up here. A description of the [`cbx_enc.dll`](https://www.lantronix.com/wp-content/uploads/pdf/Encryption-Library_UG.pdf) can be found on the web. On page 22 `VC_BlockEncrypt` is described, on page 11 there is an example that corresponds to your implementation. I would agree that the logic is CFB128. The parameters are described on page 10. It is remarkable that the key should be passed as a hexadecimal string, i.e. a 16 byte string is represented by 32 characters, so the string `abcdef0123456789` – Topaco Sep 12 '19 at 06:07
  • 1
    used by you should not simply be ASCII encoded, but should be replaced by the hexadecimal string `61626364656630313233343536373839` concerning the unmanaged dll. This does not seem to have been taken into account in the codes and thus different keys are used. Unlike the key, IV and plaintext buffers seem to be conventional byte-arrays. Thus, I would give a comparison with appropriate keys another chance. Another point is the unknown implementation of `VC_BlockEncrypt`. Here you could check if it behaves as it should in the case of raw AES-128 (ECB with one block). – Topaco Sep 12 '19 at 06:07
  • 1
    If the plaintext buffer consists of a single block (16 byte) with nothing but `0`, then the result should correspond to the AES-128 encryption of the IV. You already tried something similar in the last example, but with the possibly wrong key (see above). Also helpful could be the source code of `VC_BlockEncrypt`, which you might have access to and which might give you new insights. – Topaco Sep 12 '19 at 06:07
  • @Topaco, Awesome! will give it a try with hex key and other options... – Noble Sep 12 '19 at 17:02

1 Answers1

1

Like to express my thanks all for the help, especially to @Topaco many thanks – your insight in encoding the plain text in HEX as according to the docs helped! Here is the revised wrapper code; as you can see its cipher now matches the managed code’s cipher! Perfect!!

    static readonly string key = "61626364656630313233343536373839"; //"abcdef0123456789";
    static readonly string iv = "0123456789ABCDEF";
    public static void Encrypt(ref byte[] buffer)
    {
        Console.WriteLine($"PlainText: {HexHelper.ToHexString(buffer)}");

        var keyPtr = Marshal.StringToHGlobalAnsi(key);
        var ivPtr = Marshal.StringToHGlobalAnsi(iv);
        byte[] temp = new byte[16];
        Marshal.Copy(ivPtr, temp, 0, 16);
        int index = 0; 
        for (int i = 0; i < buffer.Length; i++)
        {
            if (index == 0)
            {
                Marshal.Copy(temp, 0, ivPtr, 16);
                unsafe
                {
                    VC_blockEncrypt((char*) ivPtr, (char*) keyPtr, 0, (char*) ivPtr, 128);
                }
                Marshal.Copy(ivPtr, temp, 0, 16);
                index = 16;
                Console.WriteLine($"CipherText BeforeXOR: {HexHelper.ToHexString(temp)}");
            }

            temp[16 - index] ^= buffer[i];
            buffer[i] = temp[16 - index];
            index--;
        }

        Marshal.FreeHGlobal(ivPtr);
        Marshal.FreeHGlobal(keyPtr);

        Console.WriteLine($"CipherText: {HexHelper.ToHexString(buffer)}");

    }

I/O from the revised wrapper code: PlainText: 4141414141414141414141414141424242424242424242424242424242424244 CipherText: 6A1A5088ACDA505B47192093DD06CD987868BFD85278A4D7D3120CC85FCD3D83

I/O from the managed code: PlainText: 4141414141414141414141414141424242424242424242424242424242424244 CipherText: 6A1A5088ACDA505B47192093DD06CD987868BFD85278A4D7D3120CC85FCD3D83

Cheers!!

Noble
  • 135
  • 1
  • 11