Advanced Encryption Standard

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 Controlsfor 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).

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(2 8)
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(2 8).
The GF(2 8) field consists of a set of 256 values from 0x00 to 0xff, plus addition and multiplication, hence the (2 8). GF stands for Galois Field, named after the mathematician who founded field theory. One of the characteristics of GF(2 8) 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(2 8) addition is simple: GF(2 8) addition is just the XOR operation.
Multiplication in GF(2 8) 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(2 8) multiplication theory in general, I will explain it just for these seven specific cases.
Multiplication by 0x01 in GF(2 8) 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(2 8) is the XOR operation. Multiplication in GF(2 8) 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(2 8).


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.

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(2 8) 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(2 8). 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:
temp = 15 16 17 14
Then SubWord is applied:
temp = 59 47 f0 fa
Then XORed with Rcon[row / Nk] = Rcon[6 / 6] = 01 00 00 00:
temp = 58 47 f0 fa
This is then XORed with w[row-Nk] = w[6-6] = 00 01 02 03, yielding the following result:
w[6] = 58 46 f2 f9
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.

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.

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:
newVal = prevVal * 0x02;
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];

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.

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.

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.

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]

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(2 8) 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 decipher algorithm is simple: to decrypt an encrypted block, just undo every operation in the reverse order. Although this is the basic concept, there are a few details to handle.
The AES specification names the decipher routine InvCipher rather than alternatives Decipher or Decrypt. This is a reflection of the mathematics behind AES, which are based in terms of inverse mathematical operations.
If you compare this code with the Cipher code, you will see that it is pretty much what you might expect, but with two exceptions. First, the order of the inverse method calls (like InvSubBytes) in the InvCipher method is not exactly the reverse of the corresponding calls (like SubBytes) in the Cipher method, and second, InvCipher calls the AddRoundKey method instead of an InvAddRoundKey method. Notice that the InvCipher algorithm uses the key schedule table but starts at the higher-numbered indexes and works its way down to row 0.
The code for the InvSubBytes, InvShiftRows, and InvMixColumns methods closely mirrors the code for the related SubBytes, ShiftRows, and MixColumns methods. The InvSubBytes method is just like the SubBytes method except that it uses the inverse substitution table iSbox[] instead of the Sbox[] table.
As you might guess, iSbox[] just undoes any mapping performed by Sbox[]. For example, if you have byte b that equals 0x20 and find its substitution value in Sbox[], you get 0xb7. If you look up the substitution value for 0xb7 in iSbox[], you get 0x20.
Similarly the InvShiftRows method undoes the ShiftRows method—row[0] is shifted 0 positions to the right, row[1] is shifted 1 position right, row[2] is shifted 2 positions right, and row[3] is shifted 3 positions right.
The InvMixColumns method undoes the work of MixColumns, but not in an obvious way. Recall that MixColumns replaces each byte in the state matrix with a linear combination of bytes in the original byte's column and that the coefficients were 0x01, 0x02, and 0x03. Once again, field theory comes into play. It turns out that the inverse operation is similar but uses multiplication by 0x09, 0x0b, 0x0d, and 0x0e like this:
State[0,c] = 0x0e * State[0,c] + 0x0b * State[1,c] + 0x0d * State[2,c] + 0x09 * State[3,c] State[1,c] = 0x09 * State[0,c] + 0x0e * State[1,c] + 0x0b * State[2,c] + 0x0d * State[3,c] State[2,c] = 0x0d * State[0,c] + 0x09 * State[1,c] + 0x0e * State[2,c] + 0x0b * State[3,c] State[3,c] = 0x0b * State[0,c] + 0x0d * State[1,c] + 0x09 * State[2,c] + 0x0e * State[3,c]
As with the MixColumns method, I decided to write dedicated helper functions rather than expand the already long expressions inline or write a general multiplication helper function. Let me show you how I wrote the function that multiplies any byte, b, by the constant 0x0e (14 in base 10). The number 14, like any number, can be expressed as the sum of powers of 2. In this case, 14 equals 2 + 4 + 8. And since 4 equals 2 squared and 8 equals 2 cubed, you can express 14 as 2 + 22 + 23. Remember that addition is just XOR (^) in GF(2 8) and since I already have the gfmultby02 function, I can use it to get my result:
return (byte)( (int)gfmultby02(gfmultby02(gfmultby02(b))) ^ /* 23 + */ (int)gfmultby02(gfmultby02(b)) ^ /* 22 + */ (int)gfmultby02(b) ); /* 2 */
All of the operations used by the AES encryption algorithm are invertible, so the decryption algorithm essentially reverses all the operations performed by encryption.


Using the AES Class
One of the features of AES as implemented in C# is its simplicity. Consider the code in  Figure 15 that I used to generate the output shown in  Figure 1. After declaring hardcoded values for the 16-byte plaintext input and the 24-byte (192-bit) seed key, an AES object is initialized, the encrypting Cipher method encrypts the plaintext to cipher text, and then the cipher text is decrypted back using InvCipher. Very clean and simple.

static void Main(string[] args) { byte[] plainText = new byte[] {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff}; byte[] cipherText = new byte[16]; byte[] decipheredText = new byte[16]; byte[] keyBytes = new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17}; Aes a = new Aes(Aes.KeySize.Bits192, keyBytes); Console.WriteLine("\nAdvanced Encryption System Demo in .NET"); Console.WriteLine("\nThe plaintext is: "); DisplayAsBytes(plainText); Console.WriteLine("\nUsing a " + Aes.KeySize.Bits192.ToString() + "-key of: "); DisplayAsBytes(keyBytes); a.Cipher(plainText, cipherText); Console.WriteLine("\nThe resulting ciphertext is: "); DisplayAsBytes(cipherText); a.InvCipher(cipherText, decipheredText); Console.WriteLine("\nAfter deciphering the ciphertext, the result is: "); DisplayAsBytes(decipheredText); Console.WriteLine("\nDone"); Console.ReadLine(); } // Main() static void DisplayAsBytes(byte[] bytes) { for (int i = 0; i < bytes.Length; ++i) { Console.Write(bytes[i].ToString("x2") + " " ); if (i > 0 && i % 16 == 0) Console.Write("\n"); } Console.WriteLine(""); } // DisplayAsBytes()

Because an AES object works on byte arrays, you can easily adapt it to work on other .NET data types. I constructed a little Windows®-based demo app that accepts a single 8-character (16-byte) string and then encrypts and decrypts it. A sample run is shown in  Figure 16.

Figure 16  Encryption Demo 
Because both the encryption and decryption routines need to know what key size the user has specified, I declared it as a class-scope variable, like this:
private Aes.KeySize keysize;
Notice that the seed key is not specified by the user. The demo uses a "null key" which consists of all zero bytes by supplying a dummy new byte[16] to the constructor. The size of the dummy argument is irrelevant because the seed key will also be initialized to all zeros. Null key encryption and decryption is an easy and effective way to deter casual external examination of your data. The Encoding.Unicode.GetBytes and Encoding.Unicode.GetString methods in System.Text make it very easy to convert a .NET string to a byte array, and vice versa.


Implementation Alternatives
Now let's look at some important variations of the AES implementation presented in this article, possible extensions of the code presented here, and cryptanalysis attacks against AES.
As much as any code that I've ever worked on, the AES algorithm has significant alternative approaches to implementation. Why is this important? AES is intended to be applicable to a wide range of systems, from smart cards with tiny memory capacities to large multiprocessor mainframe systems. In many scenarios, performance is critical and sometimes memory or processing resources are limited. Virtually every routine in AES can be modified to optimize performance at the expense of memory, or vice versa. For example, assigning the 256 values to the substitution table Sbox[] seems straightforward enough. However, these values are based on GF(2 8) theory and it turns out that these values can be generated programmatically. The same is true for the inverse substitution table and the round constants table.
Another interesting possibility for alternate implementation is the GF(2 8) multiplication used by the Cipher and InvCipher methods. My implementation codes a basic function that multiplies by 0x02 and then six additional functions which call gfmultby02. Another possibility would be to write a general multiplication function and use it instead of implementing seven separate functions like I did. At another extreme you could use a complete table of the product of all 256 possible byte values multiplied by 0x01, 0x02, 0x03, 0x09, 0x0b, 0x0d, and 0x0e. Yet another way to approach GF(2 8) multiplication is to implement it as a lookup into two 256-byte arrays, usually called alog[] and log[] because they are based on certain logarithm-like properties of GF(2 8).
Although the AES class presented here is fully capable of encrypting any form of .NET data, you might want to consider extending it in a number of ways. First, because the emphasis of this article is on presenting a clear explanation of AES, all error checking was stripped away. In my experience, adding a reasonable amount of error checking to a class similar to this AES class would triple the size of the source code. Because AES uses so many arrays, there is a lot of index bounds checking that should be done. For example, the constructor presented does not even check the size of the seed key parameter.
You might also consider extending this AES class by adding more features. The most obvious place to start would be to add methods that encrypt and decrypt fundamental .NET data types such as System.String and System.Int32. A more ambitious extension would be to implement an encrypted stream class.
How secure is AES? This is a hard question to answer, but the general consensus is that it is the most secure encryption algorithm available. AES has been subjected to more scrutiny than any other encryption algorithm to date. On both a theoretical and practical basis, AES is considered "secure" in the sense that the only effective way to crack it is through a brute-force generation of all possible keys. With a key size of 256 bits, no known brute-force attack can break AES in a reasonable amount of time (it would take years even on the fastest systems available).
Note that the most likely successful attack on an AES cipher results from a weak implementation that allows what is called a timing attack. The attacker uses different keys and precisely measures the time the encryption routine requires. If the encryption routine is carelessly coded so that execution time depends on the key value, it is possible to deduce information about the key. In AES, this is most likely to occur in the MixColumns routine because of field multiplication. Two safeguards against this attack are to insert dummy instructions so that all multiplications require the same number of instructions, or to implement field multiplication as a lookup table, as I've described.
There are many possible implementations of AES, especially using lookup tables rather than computation. The basic AES class presented in this article can be used to encrypt and decrypt any form of .NET data or can be extended into a class with added functionality.


Conclusion
The new AES will certainly become the de facto standard for encrypting all forms of electronic information, replacing DES. AES-encrypted data is unbreakable in the sense that no known cryptanalysis attack can decrypt the AES cipher text without using a brute-force search through all possible 256-bit keys.
The major obstacle I found when implementing an AES class in the Microsoft® .NET Framework was that the official specification document was written from a mathematician's point of view rather than from a software developer's point of view. In particular, the specification assumes that the reader is fairly familiar with the GF(2 8) field and it leaves out a few key facts regarding GF(2 8) multiplication that are necessary to correctly implement AES. I've tried here to remove the mystery from AES, especially surrounding GF(2 8) field multiplication.
It is only a question of time before AES encryption becomes widely available from Microsoft and third-party vendors in the form of .NET Framework libraries. However, having this code in your skill set will remain valuable for a number of reasons. This implementation is particularly simple and will have low resource overhead. In addition, access to and an understanding of the source code will enable you to customize the AES class and use any implementation of it more effectively.
Security is no longer an afterthought in anyone's software design and development process. AES is an important advance and using and understanding it will greatly increase the reliability and safety of your software systems.


For background information see:
The Design of Rijndael: AES - The Advanced Encryption Standard by Joan Daemen and Vincent Rijmen. (Springer-Verlag, 2002)
Announcing the Advanced Encryption Standard (AES): Federal Information Processing Standards Pub 197


高级加密标准(Advanced Encryption Standard,AES),在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密中最流行的算法之一。
该算法为比利时密码学家Joan Daemen和Vincent Rijmen所设计,结合两位作者的名字,以Rijndael之命名之,投稿高级加密标准的甄选流程。(Rijndael的发音近于 "Rhine doll")


沿革

Rijndael是由Daemen和Rijmen早期所设计的Square改良而来;而Square则是由SHARK发展而来。

不同于它的前任标准DES,Rijndael使用的是置换-组合架构,而非Feistel架构。AES在软件及硬件上都能快速地加解密,相对来说较易于实现,且只需要很少的存储器。作为一个新的加密标准,目前正被部署应用到更广大的范围。


密码说明

严格地说,AES和Rijndael加密法并不完全一样(虽然在实际应用中二者可以互换),因为Rijndael加密法可以支持更大范围的区块和密钥长度:AES的区块长度固定为128 位,密钥长度则可以是128,192或256位;而Rijndael使用的密钥和区块长度可以是32位的整数倍,以128位为下限,256位为上限。加密过程中使用的密钥是由Rijndael密钥生成方案产生。

大多数AES计算是在一个特别的有限域完成的。

AES加密过程是在一个4×4的字节矩阵上运作,这个矩阵又称为“体(state)”,其初值就是一个明文区块(矩阵中一个元素大小就是明文区块中的一个Byte)。(Rijndael加密法因支持更大的区块,其矩阵行数可视情况增加)加密时,各轮AES加密循环(除最后一轮外)均包含4个步骤:
AddRoundKey — 矩阵中的每一个字节都与该次回合金钥(round key)做XOR运算;每个子密钥由密钥生成方案产生。
SubBytes — 通过一个非线性的替换函数,用查找表的方式把每个字节替换成对应的字节。
ShiftRows — 将矩阵中的每个横列进行循环式移位。
MixColumns — 为了充分混合矩阵中各个直行的操作。这个步骤使用线性转换来混合每内联的四个字节。

最后一个加密循环中省略MixColumns步骤,而以另一个AddRoundKey取代。


AddRoundKey 步骤

在AddRoundKey 步骤中,将每个状态中的字节与该回合金钥做异或(⊕)。

AddRoundKey步骤,回合金钥将会与原矩阵合并。在每次的加密循环中,都会由主密钥产生一把回合金钥(通过Rijndael密钥生成方案产生),这把金钥大小会跟原矩阵一样,以与原矩阵中每个对应的字节作异或(⊕)加法。


SubBytes 步骤

在SubBytes步骤中,矩阵中各字节被固定的8位查找表中对应的特定字节所替换,S; bij = S(aij).

在SubBytes步骤中,矩阵中的各字节通过一个8位的S-box进行转换。这个步骤提供了加密法非线性的变换能力。S-box与GF(28)上的乘法反元素有关,已知具有良好的非线性特性。为了避免简单代数性质的攻击,S-box结合了乘法反元素及一个可逆的仿射变换矩阵建构而成。 此外在建构S-box时,刻意避开了固定点与反固定点,即以S-box替换字节的结果会相当于错排的结果。 此条目有针对S-box的详细描述:Rijndael S-box


ShiftRows 步骤

在ShiftRows 步骤中,矩阵中每一行的各个字节循环向左方位移。位移量则随着行数递增而递增。

ShiftRows是针对矩阵的每一个横列操作的步骤。 在此步骤中,每一行都向左循环位移某个偏移量。在AES中(区块大小128位),第一行维持不变,第二行里的每个字节都向左循环移动一格。同理,第三行及第四行向左循环位移的偏移量就分别是2和3。128位和192位的区块在此步骤的循环位移的模式相同。经过ShiftRows之后,矩阵中每一竖列,都是由输入矩阵中的每个不同列中的元素组成。Rijndael算法的版本中,偏移量和AES有少许不同;对于长度256位的区块,第一行仍然维持不变,第二行、第三行、第四行的偏移量分别是1字节、3字节、4字节。除此之外,ShiftRows操作步骤在Rijndael和AES中完全相同。


MixColumns 步骤

在 MixColumns 步骤中,每个直行都在modulo x4 + 1之下,和一个固定多项式 c(x) 作乘法。

在MixColumns步骤,每一直行的四个字节通过线性变换互相结合。每一直行的四个元素分别当作1,x,x2,x3的系数,合并即为GF(28)中的一个多项式,接着将此多项式和一个固定的多项式c(x) = 3x3 + x2 + x + 2在modulo x4 + 1下相乘。此步骤亦可视为 Rijndael有限域之下的矩阵乘法。MixColumns函数接受4个字节的输入,输出4个字节,每一个输入的字节都会对输出的四个字节造成影响。因此ShiftRows和MixColumns两步骤为这个密码系统提供了扩散性。

以下条目有对MixColumns更加详细的描述:Rijndael mix columns


加密算法优化

使用32或更多位寻址的系统,可以事先对所有可能的输入建立对应表,利用查表来实现SubBytes,ShiftRows 和 MixColumns步骤以达到加速的效果。 这么作需要产生4个表,每个表都有256个格子,一个格子记载32位的输出;约占去4KB( 4096 字节 )存储器空间,即每个表占去 1KB 的存储器空间。如此一来,在每个加密循环中,只需要查16次表,作12次32位的XOR运算,以及AddRoundKey步骤中4次32位XOR运算。

若使用的平台存储器空间不足4KB,也可以利用循环交换的方式一次查一个256格32位的表。


AES是美国高级加密标准算法,将在未来几十年里代替DES在各个领域中得到广泛应用。本文在研究分析AES加密算法原理的基础上,着重说明算法的实现步骤,并结合AVR汇编语言完整地实现AES加密和解密。根据AES原理,提出几种列变化的优化算法,并根据实验结果分析和比较它们的优缺点。关键词:AES算法 DES AVR汇编语言 加密算法 解密算法

引 言


  随着对称密码的发展,DES数据加密标准算法由于密钥长度较小(56位),已经不适应当今分布式开放网络对数据加密安全性的要求,因此1997年NIST公开征集新的数据加密标准,即AES[1]。经过三轮的筛选,比利时Joan Daeman和Vincent Rijmen提交的Rijndael算法被提议为AES的最终算法。此算法将成为美国新的数据加密标准而被广泛应用在各个领域中。尽管人们对AES还有不同的看法,但总体来说,AES作为新一代的数据加密标准汇聚了强安全性、高性能、高效率、易用和灵活等优点。AES设计有三个密钥长度:128,192,256位,相对而言,AES的128密钥比DES的56密钥强1021倍[2]。AES算法主要包括三个方面:轮变化、圈数和密钥扩展。本文以128为例,介绍算法的基本原理;结合AVR汇编语言,实现高级数据加密算法AES。

1 AES加密、解密算法原理和AVR实现

  AES是分组密钥,算法输入128位数据,密钥长度也是128位。用Nr表示对一个数据分组加密的轮数(加密轮数与密钥长度的关系如表1所列)。每一轮都需要一个与输入分组具有相同长度的扩展密钥Expandedkey(i)的参与。由于外部输入的加密密钥K长度有限,所以在算法中要用一个密钥扩展程序(Keyexpansion)把外部密钥K扩展成更长的比特串,以生成各轮的加密和解密密钥。

1.1圈变化
AES每一个圈变换由以下三个层组成:
非线性层——进行Subbyte变换;
线行混合层——进行ShiftRow和MixColumn运算;
密钥加层——进行AddRoundKey运算。

① Subbyte变换是作用在状态中每个字节上的一种非线性字节转换,可以通过计算出来的S盒进行映射。
Schange:
ldi zh,$01;将指针指向S盒的首地址
mov zl,r2;将要查找的数据作为指针低地址
ldtemp,z+;取出这个对应的数据
mov r2,temp;交换数据完成查表
.
.
.
ret

② ShiftRow是一个字节换位。它将状态中的行按照不同的偏移量进行循环移位,而这个偏移量也是根据Nb的不同而选择的[3]。
shiftrow:;这是一个字节换位的子程序
mov temp,r3;因为是4×4
mov r3,r7; r2 r6 r10 r14 r2 r6 r10 r14
mov r7,r11; r3 r7 r11 r15---r7 r11 r15 r3
mov r11,r15; r4 r8 r12 r17 r12 r17 r4 r8
mov r15,temp; r5 r9 r13 r18 r18 r5 r9 r13
mov temp,r4
mov temp1,r8
mov r4,r12
mov r8,r17
mov r12,temp
mov r17,temp1
mov temp,r18
mov r18,r13
mov r13,r9
mov r9,r5
mov r5,temp
ret

③ 在MixColumn变换中,把状态中的每一列看作GF(28)上的多项式a(x)与固定多项式c(x)相乘的结果。b(x)=c(x)*a(x)的系数这样计算:*运算不是普通的乘法运算,而是特殊的运算,即
b(x)=c(x)·a(x)(mod x4+1)
对于这个运算
b0=02。a0+03。a1+a2+a3
令xtime(a0)=02。a0
其中,符号“。”表示模一个八次不可约多项式的同余乘法[3]。
mov temp,a0;这是一个mixcolimn子程序
rcall xtime;调用xtime程序
mov a0,temp
mov temp,a1
rcall xtime
eor a0,a1
eor a0,temp
eor a0,a2
eor a0,a3;完成b(x)的计算
.
.
.
xtime:;这是一个子程序
ldi temp1,$1b
lsl temp
brcs next1;如果最高位是1,则转移
next: ret;否则什么也不变化
next1:eor temp,temp1
rjmp next

128位分组对称加密算法:AES
对于逆变化,其矩阵C要改变成相应的D,即b(x)=d(x)*a(x)。
④ 密钥加层运算(addround)是将圈密钥状态中的对应字节按位“异或”。

⑤ 根据线性变化的性质[1],解密运算是加密变化的逆变化。这里不再详细叙述。
 
1.2轮变化   
 
       对不同的分组长度,其对应的轮变化次数是不同的,如表1所列。
128位分组对称加密算法:AES
1.3密钥扩展  
       AES算法利用外部输入密钥K(密钥串的字数为Nk),通过密钥的扩展程序得到共计4(Nr+1)字的扩展密钥。它涉及如下三个模块:
 
① 位置变换(rotword)——把一个4字节的序列[A,B,C,D]变化成[B,C,D,A];

② S盒变换(subword)——对一个4字节进行S盒代替;

③ 变换Rcon[i]——Rcon[i]表示32位比特字[xi-1,00,00,00]。这里的x是(02),如
Rcon[1]=[01000000];Rcon[2]=[02000000];Rcon[3]=[04000000]……

  扩展密钥的生成:扩展密钥的前Nk个字就是外部密钥K;以后的字W[[i]]等于它前一个字W[[i-1]]与前第Nk个字W[[i-Nk]]的“异或”,即W[[i]]=W[[i-1]]W[[i- Nk]]。但是若i为Nk的倍数,则W[i]=W[i-Nk]Subword(Rotword(W[[i-1]]))Rcon[i/Nk]。

  程序执行的时候,主要调用以上几个子程序,具体实现如下:
Keyexpansion:
rcall rotwoed
rcall subword
rcall Rcon
.
.
.

AES的加密与解密流程如图1所示。

128位分组对称加密算法:AES

              图1AES的加密和解密流程

2 AES加密、解密算法的优化

  由以上算法的流程中可以清楚地看到,整个算法中程序耗时最多的就是圈变化部分,因此对于算法的优化也就在此;而圈变化部分可以优化的也就是列变化。因为列变化是一个模乘同余规则。由于AES加密和解密是不对称的,如果不对其进行优化,会使算法的解密速度远远大于加密的速度[1]。

① 加密运算。对列变换(Mixcolumn)可以通过调用xtime子程序进行优化。具体算法[1]实现如下:


  另一种有效的优化方法就是离线构造一个表格,即列变化表格。这样只要通过查表的方式就可以提高加密速度。

② 解密算法的优化。由于解密的列变换的系数分别是09、0E、0B和0D。在AVR单片机上实现以上的乘法显然是需要很多的时间,从而导致了解密的性能降低。

  优化方法一:对列变化进行分解使倍乘次数降低。

  仔细研究解密矩阵的系数,不难发现解密矩阵和加密矩阵有着一定的联系,即解密矩阵等于加密矩阵和一个矩阵的相乘。通过这样的联系,就可以对算法进行优化:
       

  这样一来,只用几个简单的“异或”就可以实现列变化,使倍乘的次数降低,提高解密的速度。
优化方法二:构造表格。

  同加密构造方法一样,可以构造四个表格T[ea]=e×a; T[9a]=9×a;T[9a]=9×a;T[ba]=b×a。这样一来,也只需要进行查表和简单的异或就可以完成解密的任务。虽然这种方法将增加额外的开销,但是它却是一种有效的方法。

3 AES加密与解密的实验仿真

  根据以上实验步骤和优化方法得出表2、3所列实验结果。
    
设主密钥为:000102030405060708090a0b0c0d0e0f(128bit)。
加密明文:00112233445566778899AABBCCDDEEFF。
密文:69C4E0D86A7B0430D8CDB78070B4C55A。
解密密文:69C4E0D86A7B0430D8CDB78070B4C55A。
明文:00112233445566778899AABBCCDDEEFF。

  总之,AES密码是一个非对称密码体制,它的解密要比加密复杂和费时。解密优化算法没有增加存储空间的基础上,以列变化为基础进行处理,程序比原始的要小,而且节约了时间。解密优化方法速度最快,效率最高,但要增加系统的存储空间,因此它的程序也是最大的一个。

  注:AES128数据加密解密程序见本刊网站(www.dpj.com.cn)。

结语

  AES高级数据加密算法不管是从安全性、效率,还是密钥的灵活性等方面都优于DES数据加密算法,在今后将逐步代替DES而被广泛应用。本文基于AVR的高速计算性能实现了AES算法,并结合汇编语言进行了算法的优化。根据实际应用的具体需要,可以选用相应的方法。


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明 YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明YOLO高分设计资源源码,详情请查看资源内容中使用说明

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值