Search code examples
c#cryptographyaesdllimportrijndaelmanaged

C# AES CFB compatibility with 3rd party C implementation


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.


Solution

  • 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!!