Related Articles
We introduce you to the benefits of building composite applications with the Composite Application Guidance for WPF from Microsoft patterns & practices.
By Glenn Block (September 2008)
ADO.NET Data Services provide Web-accessible endpoints that allow you to filter, sort, shape, and page data without having to build that functionality yourself.
By Shawn Wildermuth (September 2008)
See how routed events and routed commands in Windows Presentation Foundation form the basis for communication between the parts of your UI.
By Brian Noyes (September 2008)
Technology changes at a lightening-fast pace. This month Howard Dierking considers how the rapid changes affect developer priorities and magazine focus.
By Howard Dierking (September 2008)
More ...
Articles by this Author
This time James McCaffrey sets up a virtual environment to use for configuration testing to introduce you to software configuration testing with Microsoft Virtual Server
By Dr. James McCaffrey (September 2008)
This month James McCaffrey builds a test harness for WCF applications that really puts them through the paces.
By Dr. James McCaffrey (July 2008)
Did you know you can use Windows PowerShell to perform lightweight request/response testing for an ASP.NET Web app? Here's how.
By Dr. James McCaffrey (May 2008)
Language Integrated Query makes lots of things easier. Here we put LINQ, or more specifically the LINQ to SQL provider, to use testing SQL stored procedures.
By Dr. James McCaffrey (April 2008)
Here we show you how to use Windows PowerShell to create quick and easy UI test automation for ASP.NET and classic ASP Web applications.
By Dr. James McCaffrey (March 2008)
In this month's column Dr. James McCaffrey describes some of the ways you can use the Visual Studio 2005 Team System to manage custom software test automation.
By Dr. James McCaffrey (Launch 2008)
James McCaffrey shows you how to get started with UI test automation using the new Microsoft UI Automation library.
By Dr. James McCaffrey (February 2008)
This installment of Test Run is a guide to using Windows PowerShell to perform ultra lightweight UI automation.
By Dr. James McCaffrey (December 2007)
More ...
Popular Articles
Here we present techniques for programmatic and declarative data binding and display with Windows Presentation Foundation.
By Josh Smith (July 2008)
Speech Server 2007 lets you create sophisticated voice-response applications with Microsoft .NET Framework and Visual Studio tool integration. Here’s how.
By Michael Dunn (April 2008)
Here the author introduces SQL Server Data Services, which exposes its functionality over standard Web service interfaces.
By David Robinson (July 2008)
In this article, author John Torjo presents a guide to his C++ GUI library called eGUI++ and explains how it makes user interface programming easier.
By John Torjo (June 2008)
More ...
Read the Blog
SQL Server 2008 supports a new data type, HierarchyID, that helps solve some of the problems in modeling and querying hierarchical information. In the September 2008 issue of MSDN Magazine, Kent Tegels introduces you to the ... Read more!
Many people using SharePoint technologies don't realize that there is auditing support built directly into the Windows SharePoint Services (WSS) 3.0 platform. In the September 2008 issue of MSDN Magazine, Ted Pattison walks you through a ... Read more!
The September 2008 issue of MSDN Magazine is now available online. Here's what's in the issue: Hierarchy ID: Model ... Read more!
Silverlight 2 features a rich and robust control model that is the basis for the controls included in the platform and for third-party control packages. You can also use this control model to build controls of your own. In the August 2008 issue of MSDN Magazine, Jeff Prosise describes how to ... Read more!
In the August 2008 issue of MSDN Magazine, Matt Milner covers several topics regarding development with Windows Workflow Foundation, some that are intended to address specific reader questions, such as how to safely share a persistence database ... Read more!
LINQ is a powerful tool enabling quick filtering data based on a standard query language. It can tear through a structured set of data using a simple and straightforward syntax. In the August 2008 issue of MSDN Magazine, Jared Parsons demonstrates a ... Read more!
More ...
|
Encrypt It
Keep Your Data Secure with the New Advanced Encryption Standard
James McCaffrey
This article assumes you're familiar with C# and bit manipulation
Level of Difficulty
1
2
3
SUMMARY
The Advanced Encryption Standard (AES) is a National Institute of Standards and Technology specification for the encryption of electronic data. It is expected to become the accepted means of encrypting digital information, including financial, telecommunications, and government data. This article presents an overview of AES and explains the algorithms it uses. Included is a complete C# implementation and examples of encrypting .NET data. After reading this article you will be able to encrypt data using AES, test AES-based software, and use AES encryption in your systems.
 Contents
The National Institute of Standards and Technology (NIST) established the new Advanced Encryption Standard (AES) specification on May 26, 2002. In this article I will provide a working implementation of AES written in C#, and a complete explanation of exactly what AES is and how the code works. I'll show you how to encrypt data using AES and extend the code given here to develop a commercial-quality AES class. I'll also explain how and why to incorporate AES encryption into your software systems, and how to test AES-based software.
Note that the code presented in this article and any other implementation based on this article is subject to applicable Federal cryptographic module export controls (see Commercial Encryption
Export Controls for the exact regulations).
AES is a new cryptographic algorithm that can be used to protect electronic data. Specifically, AES is an iterative, symmetric-key block cipher that can use keys of 128, 192, and 256 bits, and encrypts and decrypts data in blocks of 128 bits (16 bytes). Unlike public-key ciphers, which use a pair of keys, symmetric-key ciphers use the same key to encrypt and decrypt data. Encrypted data returned by block ciphers have the same number of bits that the input data had. Iterative ciphers use a loop structure that repeatedly performs permutations and substitutions of the input data. Figure 1 shows AES in action encrypting and then decrypting a 16-byte block of data using a 192-bit key.
Figure 1 Some Data
AES is the successor to the older Data Encryption Standard (DES). DES was approved as a Federal standard in 1977 and remained viable until 1998 when a combination of advances in hardware, software, and cryptanalysis theory allowed a DES-encrypted message to be decrypted in 56 hours. Since that time numerous other successful attacks on DES-encrypted data have been made and DES is now considered past its useful lifetime.
In late 1999, the Rijndael (pronounced "rain doll") algorithm, created by researchers Joan Daemen and Vincent Rijmen, was selected by the NIST as the proposal that best met the design criteria of security, implementation efficiency, versatility, and simplicity. Although the terms AES and Rijndael are sometimes used interchangeably, they are distinct. AES is widely expected to become the de facto standard for encrypting all forms of electronic data including data used in commercial applications such as banking and financial transactions, telecommunications, and private and Federal information.
Overview of the AES Algorithm
The AES algorithm is based on permutations and substitutions. Permutations are rearrangements of data, and substitutions replace one unit of data with another. AES performs permutations and substitutions using several different techniques. To illustrate these techniques, let's walk through a concrete example of AES encryption using the data shown in Figure 1.
The following is the 128-bit value that you will encrypt with the indexes array:
|
00 11 22 33 44 55 66 77 88 99 aa bb cc dd ee ff
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
The 192-bit key value is:
|
00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
Figure 2 Sbox
When the AES constructor is called, two tables that will be used by the encryption method are initialized. The first table is a substitution box named Sbox. It is a 16 × 16 matrix. The first five rows and columns of Sbox are shown in Figure 2. Behind the scenes, the encryption routine takes the key array and uses it to generate a "key schedule" table named w[], shown in Figure 3.
Figure 3 Key Sched.
The first Nk (6) rows of w[] are seeded with the original key value (0x00 through 0x17) and the remaining rows are generated from the seed key. The variable Nk represents the size of the seed key in 32-bit words. You'll see exactly how w[] is generated later when I examine the AES implementation. The point is that there are now many keys to use instead of just one. These new keys are called the round keys to distinguish them from the original seed key.
Figure 4 State
The AES encryption routine begins by copying the 16-byte input array into a 4×4 byte matrix named State (see Figure 4). The AES encryption algorithm is named Cipher and operates on State[] and can be described in pseudocode (see Figure 5).
 Figure 5 Cipher Algorithm Pseudocode
|
Cipher(byte[] input, byte[] output)
{
byte[4,4] State;
copy input[] into State[]
AddRoundKey
for (round = 1; round < Nr-1; ++round)
{
SubBytes
ShiftRows
MixColumns
AddRoundKey
}
SubBytes
ShiftRows
AddRoundKey
copy State[] to output[]
}
|
The encryption algorithm performs a preliminary processing step that's called AddRoundKey in the specification. AddRoundKey performs a byte-by-byte XOR operation on the State matrix using the first four rows of the key schedule, and XORs input State[r,c] with round keys table w[c,r].
For example, if the first row of the State matrix holds the bytes { 00, 44, 88, cc }, and the first column of the key schedule is { 00, 04, 08, 0c }, then the new value of State[0,2] is the result of XORing State[0,2] (0x88) with w[2,0] (0x08), or 0x80:
|
1 0 0 0 1 0 0 0
0 0 0 0 1 0 0 0 XOR
1 0 0 0 0 0 0 0
|
The main loop of the AES encryption algorithm performs four different operations on the State matrix, called SubBytes, ShiftRows, MixColumns, and AddRoundKey in the specification. The AddRoundKey operation is the same as the preliminary AddRoundKey except that each time AddRoundKey is called, the next four rows of the key schedule are used. The SubBytes routine is a substitution operation that takes each byte in the State matrix and substitutes a new byte determined by the Sbox table. For example, if the value of State[0,1] is 0x40 and you want to find its substitute, you take the value at State[0,1] (0x40) and let x equal the left digit (4) and y equal the right digit (0). Then you use x and y as indexes into the Sbox table to find the substitution value, as shown in Figure 2.
ShiftRows is a permutation operation that rotates bytes in the State matrix to the left. Figure 6 shows how ShiftRows works on State[]. Row 0 of State is rotated 0 positions to the left, row 1 is rotated 1 position left, row 2 is rotated 2 positions left, and row 3 is rotated 3 positions left.
Figure 6 Running ShiftRows on State
The MixColumns operation is a substitution operation that is the trickiest part of the AES algorithm to understand. It replaces each byte with the result of mathematical field additions and multiplications of values in the byte's column. I will explain the details of special field addition and multiplication in the next section.
Suppose the value at State[0,1] is 0x09, and the other values in column 1 are 0x60, 0xe1, and 0x04; then the new value for State[0,1] is shown in the following:
|
State[0,1] = (State[0,1] * 0x01) +
(State[1,1] * 0x02) +
(State[2,1] * 0x03) +
(State[3,1] * 0x01)
= (0x09 * 0x01) + (0x60 * 0x02) + (0xe1 * 0x03) +
(0x04 * 0x01)
= 0x57
|
The addition and multiplication are special mathematical field operations, not the usual addition and multiplication on integers.
The four operations SubBytes, ShiftRows, MixColumns, and AddRoundKey are called inside a loop that executes Nr times—the number of rounds for a given key size, less 1. The number of rounds that the encryption algorithm uses is either 10, 12, or 14 and depends on whether the seed key size is 128, 192, or 256 bits. In this example, because Nr equals 12, the four operations are called 11 times. After this iteration completes, the encryption algorithm finishes by calling SubBytes, ShiftRows, and AddRoundKey before copying the State matrix to the output parameter.
In summary, there are four operations that are at the heart of the AES encryption algorithm. AddRoundKey substitutes groups of 4 bytes using round keys generated from the seed key value. SubBytes substitutes individual bytes using a substitution table. ShiftRows permutes groups of 4 bytes by rotating 4-byte rows. MixColumns substitutes bytes using a combination of both field addition and multiplication.
Field Addition and Multiplication in GF(28)
As you've seen, the AES encryption algorithm uses fairly straightforward techniques for substitution and permutation, except for the MixColumns routine. The MixColumns routine uses special addition and multiplication. The addition and multiplication used by AES are based on mathematical field theory. In particular, AES is based on a field called GF(28).
The GF(28) field consists of a set of 256 values from 0x00 to 0xff, plus addition and multiplication, hence the (28). GF stands for Galois Field, named after the mathematician who founded field theory. One of the characteristics of GF(28) is that the result of an addition or multiplication operation must be in the set {0x00 ... 0xff}. Although the theory of fields is rather deep, the net result for GF(28) addition is simple: GF(28) addition is just the XOR operation.
Multiplication in GF(28) is trickier, however. As you'll see later in the C# implementation, the AES encryption and decryption routines need to know how to multiply by only the seven constants 0x01, 0x02, 0x03, 0x09, 0x0b, 0x0d, and 0x0e. So instead of explaining GF(28) multiplication theory in general, I will explain it just for these seven specific cases.
Multiplication by 0x01 in GF(28) is special; it corresponds to multiplication by 1 in normal arithmetic and works the same way—any value times 0x01 equals itself.
Now let's look at multiplication by 0x02. As in the case of addition, the theory is deep, but the net result is fairly simple. If the value being multiplied is less than 0x80, then the result of multiplication is just the value left-shifted 1 bit position. If the value being multiplied is greater than or equal to 0x80, then the result of multiplication is the value left-shifted 1 bit position XORed with the value 0x1b. This prevents "field overflow" and keeps the product of the multiplication in range.
Once you've established addition and multiplication by 0x02 in GF(2 8), you can use them to define multiplication by any constant. To multiply by 0x03 in GF(2 8), you can decompose 0x03 as powers of 2 and additions. To multiply an arbitrary byte b by 0x03, observe that 0x03 = 0x02 + 0x01. Thus:
|
b * 0x03 = b * (0x02 + 0x01)
= (b * 0x02) + (b * 0x01)
|
This can be done because you know how to multiply by 0x02 and 0x01 and how to perform addition. Similarly, to multiply an arbitrary byte b by 0x0d, you do this:
|
b * 0x0d = b * (0x08 + 0x04 + 0x01)
= (b * 0x08) + (b * 0x04) + (b * 0x01)
= (b * 0x02 * 0x02 * 0x02) + (b * 0x02 * 0x02) + (b * 0x01)
|
The other multiplications needed for the AES MixColumns routine in the encryption and decryption algorithm follow the same general pattern, as shown here:
|
b * 0x09 = b * (0x08 + 0x01)
= (b * 0x02 * 0x02 * 0x02) + (b * 0x01)
b * 0x0b = b * (0x08 + 0x02 + 0x01)
= (b * 0x02 * 0x02 * 0x02) + (b * 0x02) + (b * 0x01)
b * 0x0e = b * (0x08 + 0x04 + 0x02)
= (b * 0x02 * 0x02 * 0x02) + (b * 0x02 * 0x02) + (b * 0x02)
|
To summarize, addition in GF(28) is the XOR operation. Multiplication in GF(28) reduces to additions and multiplications by 0x02, where multiplication by 0x02 is a conditional 1-bit left shift. The AES specification contains a lot of additional information about operations in GF(28).
Key Expansion
The AES encryption and decryption algorithms use a key schedule generated from the seed key array of bytes. The AES specification refers to this as the KeyExpansion routine. Generating, in essence, multiple keys from an initial key instead of using a single key greatly increases the diffusion of bits. Although not overwhelmingly difficult, understanding KeyExpansion is one of the trickier parts of the AES algorithm. In high-level pseudocode, the KeyExpansion routine looks like the following:
|
KeyExpansion(byte[] key, byte[][4] w)
{
copy the seed key into the first rows of w
for each remaining row of w
{
use two of the previous rows to create a new row
}
}
|
The "use two of the previous rows to create a new row" routine makes use of two subroutines, RotWord and SubWord, and a table of constants named Rcon (for "round constants"). Let's look at each of these three items and then come back to the KeyExpansion routine as a whole.
The RotWord routine is simple. It accepts an array of 4 bytes and rotates them 1 position left. Because the round schedule table w[] has four columns, RotWord rotates a row of w[] to the left. Notice that the RotWord function used by KeyExpansion is very similar to the ShiftRows routine used by the encryption algorithm except that it works on a single row of the key schedule w[] instead of the entire encryption state table State[].
The SubWord routine performs a byte-by-byte substitution on a given row of the key schedule table w[] using the substitution table Sbox. The substitutions in KeyExpansion operate exactly like those in the encryption algorithm. The input byte to be substituted is separated into an (x,y) pair which are used as indexes into the substitution table Sbox. For example, substitution for 0x27 results in x = 2 and y = 7, and Sbox[2,7] returns 0xcc.
The KeyExpansion routine uses an array Rcon[], called the round constant table. These constants are 4 bytes each to match with a row of the key schedule table. The AES KeyExpansion routine requires 11 round constants. You can see these constants listed in Figure 7.
 Figure 7 Initializing Rcon
|
private void BuildRcon()
{
this.Rcon = new byte[11,4] { {0x00, 0x00, 0x00, 0x00},
{0x01, 0x00, 0x00, 0x00},
{0x02, 0x00, 0x00, 0x00},
{0x04, 0x00, 0x00, 0x00},
{0x08, 0x00, 0x00, 0x00},
{0x10, 0x00, 0x00, 0x00},
{0x20, 0x00, 0x00, 0x00},
{0x40, 0x00, 0x00, 0x00},
{0x80, 0x00, 0x00, 0x00},
{0x1b, 0x00, 0x00, 0x00},
{0x36, 0x00, 0x00, 0x00} };
} // BuildRcon()
|
The leftmost byte of each round constant is a power of 2 in the GF(28) field. Another way of looking at it is to observe that each value is the previous value times 0x02, as described in the previous section discussing multiplication in GF(28). Notice that 0x80 × 0x02 = 0x1b is 0x80 left-shifted 1 bit followed by an XOR with 0x1b, as described earlier.
Now let's take a closer look at the loop inside KeyExpansion. In more detailed pseudocode than before, the loop is:
|
for (row = Nk; row < (4 * Nr+1); ++row)
{
temp = w[row-1]
if (row % Nk == 0)
temp = SubWord(RotWord(temp)) xor Rcon[row/Nk]
else if (Nk == 8 and row % Nk == 4)
temp = SubWord(temp)
w[row] = w[row-Nk] xor temp
}
|
Ignoring the if clause for a moment, you'll see that each row of the key schedule table w[] is the result of XORing the previous row with the row Nk (4, 6, or 8 depending on the key size) rows before. The first part of the if conditional modifies every fourth, sixth, or eighth row of the key schedule with SubWord, RotWord, and XORing with a round constant, depending on whether the key size is 128, 192, or 256 bits. The second part of the conditional will modify rows 12, 20, 28 and so on—every eighth row—for a 256-bit key to add additional variability to the key schedule.
Let's see how KeyExpansion gets started with the example presented at the beginning of this article. The seed key is the 192-bit / 6-word value:
|
00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17
|
The key schedule byte table w[] has the dimensions 4 columns and Nb × (Nr + 1) equals 4 × (12 + 1), or 52 rows. The KeyExpansion routine copies the values in the seed key into the first rows of the key schedule byte table w[]. Because my seed key is 192 bits (24 bytes), and the w[] table always has 4 columns, in this case KeyExapansion copies the seed key into the first 6 rows of w[]. Now let's see how the KeyExpansion routine fills the rest of the key schedule table. In my example, the first calculated row is row 6 because rows 0 to 5 were filled with the seed key values:
|
temp = w[row-1] = 14 15 16 17
|
The condition (row % Nk == 0) is true, so first the RotWord subroutine is applied:
Then SubWord is applied:
Then XORed with Rcon[row / Nk] = Rcon[6 / 6] = 01 00 00 00:
This is then XORed with w[row-Nk] = w[6-6] = 00 01 02 03, yielding the following result:
The process repeats itself for all of the remaining rows in key schedule table w[].
To summarize, an important part of AES encryption and decryption is the generation of multiple round keys from the initial seed key. This KeyExpansion algorithm generates a key schedule and uses substitution and permutation in a way that is similar in most respects to the encryption and decryption algorithms.
The AES Class Constructor in C#
Now that I've examined all the components of the AES encryption algorithm, I'll implement it in C#. The official specification of the AES algorithm is contained in Federal Information Processing Standards Publication 197. I decided to base my implementation on it as closely as possible, but I quickly discovered that the specification was more of a theory document than an implementation guide. To exploit the official specification as a resource, I have used the same variable names as those used in the standards publication (even when they are rather cryptic like "Nr" and "w").
My design uses the nine data members and one enumeration type as shown here:
|
public enum KeySize { Bits128, Bits192,
Bits256 };
private int Nb;
private int Nk;
private int Nr;
private byte[] key;
private byte[,] Sbox;
private byte[,] iSbox;
private byte[,] w;
private byte[,] Rcon;
private byte[,] State;
|
Because the key size can only be 128, 192, or 256 bits, it is a great candidate for an enumerated type:
|
public enum KeySize { Bits128, Bits192, Bits256 };
|
The specification document generally uses bytes as its basic storage unit but uses 4-byte words as the size for two important data members. The two members Nb and Nk represent the block size in words and key size in words, respectively. Nr represents the number of rounds. The block size is always 16 bytes (or 128 bits, which is 4 words for AES), so it could have been declared as a constant. The key size is assigned a value of 4, 6, or 8 according to the value of the enumeration parameter KeySize. The AES algorithm iterates through a number of rounds to increase the complexity of the encrypted data. The number of rounds is either 10, 12, or 14 and is based on cryptanalysis theory. It depends directly on the key size.
When designing a class interface, I like to work backwards. I imagine calling the constructor and methods from an application. Using this approach, I decided that I wanted to instantiate an AES object like the following:
|
Aes a = new Aes(the key size, the seed key)
|
I called the encryption and decryption routines as follows:
|
a.Cipher(plainText, cipherText);
a.InvCipher(cipherText, decipheredText);
|
I chose the slightly awkward method names Cipher and InvCipher because they are used in the AES specification document. Here is the code for the AES class constructor:
|
public Aes(KeySize keySize, byte[] keyBytes)
{
SetNbNkNr(keySize);
this.key = new byte[this.Nk * 4];
keyBytes.CopyTo(this.key, 0);
BuildSbox();
BuildInvSbox();
BuildRcon();
KeyExpansion();
}
|
The constructor first set the values of Nb, Nk, and Nr by calling a helper method SetNbNkNr, which is shown in Figure 8. If efficiency is a concern, you could put this code directly in the constructor to avoid the overhead of a method call.
 Figure 8 SetNbNkNr Method
|
private void SetNbNkNr(KeySize keySize)
{
this.Nb = 4;
if (keySize == KeySize.Bits128)
{
this.Nk = 4;
this.Nr = 10;
}
else if (keySize == KeySize.Bits192)
{
this.Nk = 6;
this.Nr = 12;
}
else if (keySize == KeySize.Bits256)
{
this.Nk = 8;
this.Nr = 14;
}
} // SetNbNkNr()
|
Next, you have to copy the bytes that are passed into the constructor into the class field variable. The key is declared with the other class fields and it gets its value by doing this:
|
this.key = new byte[this.Nk * 4];
keyBytes.CopyTo(this.key, 0);
|
I decided to call the initialization of the substitution tables Sbox[] and iSbox[] using the private helper methods BuildSbox and BuildInvSbox in the constructor. Now Sbox[] and iSbox[] are required by the key expansion routine and the Cipher and InvCipher methods, respectively, so I could have put initialization of Sbox[] and invocation of the KeyExpansion method in both the Cipher and the InvCipher methods, but putting them in the constructor results in a cleaner code structure. sBox[] gets populated in Figure 9. The code that populates iSbox[] is similar. The code is structured for readability. As you'll see later, there is a surprising alternative to this way of supplying values for the Sbox and iSbox tables.
 Figure 9 Sbox Initialization
|
private void BuildSbox()
{
this.Sbox = new byte[16,16] { // populate the Sbox matrix
/* 0 1 2 3 4 5 6 7 8 9 a b c d e f */
/*0*/ {0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76},
/*1*/ {0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0},
/*2*/ {0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15},
/*3*/ {0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75},
/*4*/ {0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84},
/*5*/ {0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf},
/*6*/ {0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8},
/*7*/ {0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2},
/*8*/ {0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73},
/*9*/ {0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb},
/*a*/ {0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79},
/*b*/ {0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08},
/*c*/ {0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a},
/*d*/ {0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e},
/*e*/ {0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf},
/*f*/ {0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16} };
} // BuildSbox()
|
Declaring the key schedule table w[], the round constants table Rcon[] and the state matrix State[] in the constructor, and assigning values to Rcon[] and w[] with private helper methods seems to me to be the best way to organize them, but that is mostly a matter of style. The code that populates the round constant table Rcon is shown in Figure 7.
Recall that the left byte of each row of Rcon[] is a power of 2 in GF(2 8) so this table could be built computationally using something like the following:
The AES constructor finishes by building the key schedule table w[] which is done in the KeyExpansion method (see Figure 10). The code is fairly straightforward. The specification document uses a hypothetical 4-byte word data type. Since C# has no such type, it is simulated with an array of 4 bytes. After the key schedule w[] is allocated space using the operator new, the first Nk (4, 6, or 8) rows of w[] get values from the seed key[] array that was passed into the constructor:
|
this.w[row,0] = this.key[4*row];
this.w[row,1] = this.key[4*row+1];
this.w[row,2] = this.key[4*row+2];
this.w[row,3] = this.key[4*row+3];
|
 Figure 10 KeyExpansion Method
|
private void KeyExpansion()
{
this.w = new byte[Nb * (Nr+1), 4];
for (int row = 0; row < Nk; ++row)
{
this.w[row,0] = this.key[4*row];
this.w[row,1] = this.key[4*row+1];
this.w[row,2] = this.key[4*row+2];
this.w[row,3] = this.key[4*row+3];
}
byte[] temp = new byte[4];
for (int row = Nk; row < Nb * (Nr+1); ++row)
{
temp[0] = this.w[row-1,0]; temp[1] = this.w[row-1,1];
temp[2] = this.w[row-1,2]; temp[3] = this.w[row-1,3];
if (row % Nk == 0)
{
temp = SubWord(RotWord(temp));
temp[0] = (byte)( (int)temp[0] ^ (int)this.Rcon[row/Nk,0] );
temp[1] = (byte)( (int)temp[1] ^ (int)this.Rcon[row/Nk,1] );
temp[2] = (byte)( (int)temp[2] ^ (int)this.Rcon[row/Nk,2] );
temp[3] = (byte)( (int)temp[3] ^ (int)this.Rcon[row/Nk,3] );
}
else if ( Nk > 6 && (row % Nk == 4) )
{
temp = SubWord(temp);
}
// w[row] = w[row-Nk] xor temp
this.w[row,0] = (byte) ( (int)this.w[row-Nk,0] ^ (int)temp[0] );
this.w[row,1] = (byte) ( (int)this.w[row-Nk,1] ^ (int)temp[1] );
this.w[row,2] = (byte) ( (int)this.w[row-Nk,2] ^ (int)temp[2] );
this.w[row,3] = (byte) ( (int)this.w[row-Nk,3] ^ (int)temp[3] );
} // for loop
} // KeyExpansion()
|
The operation of XORing 2 bytes together happens a lot in this code. It requires some casting from byte to int and back to byte because the XOR operator ^ is not defined on the C# byte type. For example
|
temp[0] = (byte)( (int)temp[0] ^ (int)this.Rcon[row/Nk,0] );
|
is used instead of:
|
temp[0] = temp[0] ^ this.Rcon[row/Nk,0];
|
The KeyExpansion method conditionally calls the private methods SubWord and RotWord to maintain naming consistency with the specification. Again, because there is no word type in C#, I implemented one using an array of 4 bytes. The code for SubWord and RotWord is fairly simple and you should be able to understand it easily by examining it in the AesLib source code that accompanies this article.
The slightly tricky part is looking up the substitution values in SubWord. Recall that to find the substitute value, you separate the input byte into its leftmost 4 bits and its rightmost 4 bits. For a given byte, right-shifting 4 bits with the >> operator will yield the x index, and logical ANDing with 0000 1111 will yield the y value. In slightly longer, but more readable form than the actual code, I could have done something like the following
|
int x = word[0] >> 4;
int y = word[0] & 0x0f;
byte substitute = this.Sbox[x,y];
result[0] = substitute;
|
instead of the code I used:
|
result[0] = this.Sbox[ word[0] >> 4, word[0] & 0x0f ];
|
To summarize, the AES constructor accepts a key size of 128, 192, or 256 bits and a byte array seed key value. The constructor assigns values for the input block size, the seed key size, and the number of rounds for the encryption algorithm and copies the seed key to a data member named key. The constructor also builds four tables: two substitution tables used by the encryption and decryption methods, a table of round constants, and a key schedule of round keys.
The AES Cipher Method in C#
The code for the Cipher method is shown in Figure 11. It is really very simple because it mostly farms out the work to the private methods AddRoundKey, SubBytes, ShiftRows, and MixColumns.
 Figure 11 The Cipher Method
|
public void Cipher(byte[] input, byte[] output)
{
// state = input
this.State = new byte[4,Nb];
for (int i = 0; i < (4 * Nb); ++i)
{
this.State[i % 4, i / 4] = input[i];
}
AddRoundKey(0);
for (int round = 1; round <= (Nr - 1); ++round)
{
SubBytes();
ShiftRows();
MixColumns();
AddRoundKey(round);
}
SubBytes();
ShiftRows();
AddRoundKey(Nr);
// output = state
for (int i = 0; i < (4 * Nb); ++i)
{
output[i] = this.State[i % 4, i / 4];
}
} // Cipher()
|
The Cipher method starts by copying the plaintext input array to the state matrix State[]. After an initial call to AddRoundKey, the Cipher method iterates one time fewer than the total number of rounds. On the last round, the call to MixColumns is omitted as described in the specification.
The code for the AddRoundKey and SubBytes private methods is shown in Figure 12. The AddRoundKey method needs to know what round it is at so that it can reference the correct four rows of the key schedule array w[]. Notice that State[r,c] is XORed with w[c,r] and not w[r,c]. The SubBytes method extracts indexes from the input byte using the same right-shift-4-bits and mask-with-0x0f technique used in the KeyExpansion method.
 Figure 12 AddRoundKey and SubBytes Methods
|
private void AddRoundKey(int round)
{
for (int r = 0; r < 4; ++r)
{
for (int c = 0; c < 4; ++c)
{
this.State[r,c] = (byte) ( (int)this.State[r,c] ^
(int)w[(round*4)+c,r] );
}
}
} // AddRoundKey()
private void SubBytes()
{
for (int r = 0; r < 4; ++r)
{
for (int c = 0; c < 4; ++c)
{
this.State[r,c] = this.Sbox[ (this.State[r,c] >> 4),
(this.State[r,c] & 0x0f) ];
}
}
} // SubBytes
|
The code for the ShiftRows method is shown in Figure 13. Recall that ShiftRows (which might have been better named RotateRows) rotates row[0] 0 positions to the left, row[1] 1 position to the left, and so forth.
 Figure 13 ShiftRows Method
|
private void ShiftRows()
{
byte[,] temp = new byte[4,4];
for (int r = 0; r < 4; ++r)
{
for (int c = 0; c < 4; ++c)
{
temp[r,c] = this.State[r,c];
}
}
for (int r = 1; r < 4; ++r) //
{
for (int c = 0; c < 4; ++c)
{
this.State[r,c] = temp[ r, (c + r) % Nb ];
}
}
} // ShiftRows()
|
After copying State[] into a temp[] matrix, the shifts are then performed with:
|
this.State[r, (c + r) % Nb ] = temp[r,c];
|
This takes advantage of the % operator to wrap around a row.
The MixColumns method (see Figure 14) takes every byte and substitutes it with a linear combination of all the other values in the byte's column using GF(2 8) addition and multiplication. The constant coefficients used for multiplication are based on field theory and are either 0x01, 0x02, or 0x03. The substitution for a given column c is:
|
State[0,c] = 0x02 * State[0,c] + 0x03 * State[1,c] + 0x01 * State[2,c] +
0x01 * State[3,c]
State[1,c] = 0x01 * State[0,c] + 0x02 * State[1,c] + 0x03 * State[2,c] +
0x01 * State[3,c]
State[2,c] = 0x01 * State[0,c] + 0x01 * State[1,c] + 0x02 * State[2,c] +
0x03 * State[3,c]
State[3,c] = 0x03 * State[0,c] + 0x01 * State[1,c] + 0x01 * State[2,c] +
0x02 * State[3,c]
|
 Figure 14 MixColumns Method
|
private void MixColumns()
{
byte[,] temp = new byte[4,4];
for (int r = 0; r < 4; ++r)
{
for (int c = 0; c < 4; ++c)
{
temp[r,c] = this.State[r,c];
}
}
for (int c = 0; c < 4; ++c)
{
this.State[0,c] = (byte) ( (int)gfmultby02(temp[0,c]) ^
(int)gfmultby03(temp[1,c]) ^
(int)gfmultby01(temp[2,c]) ^
(int)gfmultby01(temp[3,c]) );
this.State[1,c] = (byte) ( (int)gfmultby01(temp[0,c]) ^
(int)gfmultby02(temp[1,c]) ^
(int)gfmultby03(temp[2,c]) ^
(int)gfmultby01(temp[3,c]) );
this.State[2,c] = (byte) ( (int)gfmultby01(temp[0,c]) ^
(int)gfmultby01(temp[1,c]) ^
(int)gfmultby02(temp[2,c]) ^
(int)gfmultby03(temp[3,c]) );
this.State[3,c] = (byte) ( (int)gfmultby03(temp[0,c]) ^
(int)gfmultby01(temp[1,c]) ^
(int)gfmultby01(temp[2,c]) ^
(int)gfmultby02(temp[3,c]) );
}
} // MixColumns
|
These expressions are a bit long already so I decided to write private helper functions that return the product of GF(2 8) multiplication by 0x01, 0x02, and 0x03. The helper functions are very short. For example, the code that's used to field-multiply a byte b by 0x03 is as follows:
|
return (byte) ( (int)gfmultby02(b) ^ (int)b );
|
As I discussed earlier, multiplication by 0x02 is the essential operation for all GF(28) multiplication. I called my gfmultby02 method, bending my convention of using the same method names that have been used in the specification; the specification calls this routine xtime.
The Cipher method iteratively applies four operations on its input to produce encrypted output. AddRoundKey substitutes bytes using multiple round keys derived from the single original seed key. SubBytes substitutes bytes using values in a substitution table. ShiftRows permutes bytes by shifting rows of bytes, and MixColumns substitutes bytes using field addition and multiplication of values in a column.
The AES InvCipher Method in C#
The basic premise behind the AES dec |