Search code examples
c#testingencryptionaesfips

Configure .Net AES to produce results of FIPS 197


For testing I want to configure any .Net AES algorithm to produce the results given in the FIPS 197 publication (Appendix B).

I tried different parameters (block size = key size = feedback size = 128, different CipherModes and paddings, IV, ...) but i can't get the results given as example. I have also tried the different implementations (RijnadaelManaged, AesCryptoServiceProvider, ...).

Is there a way to imitate the given example?

This is my current implementation:

var data = new byte[] { 0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d, 0x31, 0x31, 0x98, 0xa2, 0xe0, 0x37, 0x07, 0x34 };
var key = new byte[] { 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c };
var expected = new byte[] { 0x39, 0x25, 0x84, 0x1d, 0x02, 0xdc, 0x09, 0xfb, 0xdc, 0x11, 0x85, 0x97, 0x19, 0x6a, 0x0b, 0x32 };

using (var aesAlg = new System.Security.Cryptography.RijndaelManaged())
{
    aesAlg.Key = key;
    aesAlg.IV = key;
    aesAlg.KeySize = 128;
    aesAlg.Mode = System.Security.Cryptography.CipherMode.ECB;
    aesAlg.BlockSize = 128;
    aesAlg.FeedbackSize = 128;

    var encryptor = aesAlg.CreateEncryptor();
    using (MemoryStream msEncrypt = new MemoryStream())
    {
        using (var csEncrypt = new System.Security.Cryptography.CryptoStream(msEncrypt, encryptor, System.Security.Cryptography.CryptoStreamMode.Write))
        {
            using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
            {
                //Write all data to the stream.
                swEncrypt.Write(data);
            }
            var actual = msEncrypt.ToArray();

            Assert.AreEqual(expected.GetLength(0), actual.GetLength(0));
            for (int i = 0; i < actual.GetLength(0); i++)
            {
                var expectedValue = expected[i];
                var actualValue = actual[i];
                Assert.AreEqual(expectedValue, actualValue);
            }
        }
    }
}

Solution

  • ECB is not entirely the same as the block cipher itself. It is a mode of operation just like CBC or CTR, but it is more primitive and insecure. Instead of encrypting a single block of plaintext it manages to encrypt multiple blocks, although each of those blocks are simply encrypted directly.

    To make sure that it confirms to a generic cipher that supports any message length (up to a certain size), ECB requires padding. Otherwise messages that are not a multiple of the block size cannot be encrypted. By default RijndaelManaged will pad with PKCS#7 compatible padding. This padding is always applied because otherwise the message length cannot be determined (a message could end with the correct padding bytes "by accident"). So what you get when you're encrypting is a block consisting of the expected bytes, followed by a block of encrypted padding bytes.

    To avoid this you can still use the ECB implementation, but then set the Padding property of the class to PaddingMode.None (for more padding modes look here). You could also strip the final block of padding before validating the calculated ciphertext or simply ignore the length check but I'd consider that hacking.

    Here is code that works as intended:

    using System;
    using System.IO;
    using System.Security.Cryptography;
    
    namespace AESEncryption
    {
        class Program
        {
            static void Main(string[] args)
            {
                var data = new byte[] { 0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d, 0x31, 0x31, 0x98, 0xa2, 0xe0, 0x37, 0x07, 0x34 };
                var key = new byte[] { 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c };
                var expected = new byte[] { 0x39, 0x25, 0x84, 0x1d, 0x02, 0xdc, 0x09, 0xfb, 0xdc, 0x11, 0x85, 0x97, 0x19, 0x6a, 0x0b, 0x32 };
    
                using (var aesAlg = new RijndaelManaged())
                {
                    aesAlg.Key = key;
                    aesAlg.Mode = CipherMode.ECB;
                    aesAlg.Padding = PaddingMode.None;
    
                    var encryptor = aesAlg.CreateEncryptor();
                    using (var msEncrypt = new MemoryStream())
                    {
                        using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                        {
                            csEncrypt.Write(data, 0, data.Length);
                        }
                        var actual = msEncrypt.ToArray();
    
                        Console.WriteLine("Expected:");
                        PrintByteArray(expected);
                        Console.WriteLine("Actual:");
                        PrintByteArray(actual);
                    }
                }
            }
    
            static void PrintByteArray(byte[] array)
            {
                foreach (var b in array)
                {
                    Console.Write($"{b:X2} ");
                }
                Console.WriteLine();
            }
        }
    }
    

    Note the following:

    • setting the KeySize after setting the key regenerates the key;
    • setting the IV value also messes up the result, ECB does not use an IV;
    • the default block size is already 128 bits, so it doesn't need to be set, but if it is set I'd set the property right at the start;
    • FeedbackSize is only applicable to OFB/CFB mode so that property should not be set (although it seems to be ignored);
    • CryptoStream already provides a binary stream to write to, do not use StreamWriter for binary streams.

    Note: you should not use RijndaelManaged directly. Instead use Aes.Create() to get the best implementation of AES on your system. Usually that uses AES-NI or similar CPU features for a more secure and faster AES.

    From the documentation:

    The Rijndael and RijndaelManaged types are obsolete. Use Aes instead.