該構造函數首先調用一個輔助方法 SetNbNkNr 給 Nb、Nk和Nr 賦值,如 Figure 8 所示。如果考慮到效率,你可能將這些代碼直接放入構造函數 以避免方法調用的開銷。
接下來,你必須將傳入構造函數的字節拷貝到類域變量中。密鑰用其它的類域聲明,並且用如下方法獲得它的值:
this.key = new byte[this.Nk * 4];
keyBytes.CopyTo(this.key, 0);
我決定在構造函數中調用私有輔助方法 BuildSbox和BuildInvSbox 來初始化替換表 Sbox[]和iSbox[] 。現在密鑰擴展例程 、Cipher 方法和 InvCipher 方法各自都需要 Sbox[]和iSbox[],因此我本來可以在Cipher和InvCipher 兩個方法中初始化 Sbox[] 並調用 KeyExpansion 方法,但是將它們放入構造函數會代碼結構更加清晰。在Figure 9中 sBox[] 被填充。填充 iSbox[] 代碼 類似。為了可讀性對代碼進行了結構化處理。正如後面你將看到的,還有另外一個可供選擇的令人驚訝的方法為Sbox和iSbox 表提供值。
在構造函數中聲明密鑰調度表 w[]、輪常數表 Rcon[] 和狀態矩陣 State[],並用私有輔助方法來給 Rcon[]和w[] 賦值在我看來似乎是組織它們的最好辦法,但那主要還是個風格問題。置換輪常數表 Rcon的賦值代碼參見 Figure 7。
回想一下,GF(28)中,Rcon[] 每一行左邊的字節都 2的冪,因此這個表可用下面的方法建立:
newVal = prevVal * 0x02;
AES 構造函數在建立完密鑰調度表 w[] 後結束,而 w[] 是在KeyExpansion 方法中完成的(參見 Figure 10)。 其代碼相當簡單。規范文檔使用一個假設的 4-字節的字數據類型。因為C# 沒有那樣的類型,但可以用一個4個字節的數組來模擬。在用 new 操作符為密鑰調度 表 w[] 分配空間後,w[] 最初的 Nk(4, 6, 或 8) 行從被傳遞到構造函數的種子密鑰 key[] 數組中獲值。
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 到 int的強制類型轉換並轉回到 byte,因為異或操作“^”是不能定義在C#的 byte 類型上,例如:
temp[0] = (byte)( (int)temp[0] ^ (int)this.Rcon[row/Nk,0] );
用來替代:
temp[0] = temp[0] ^ this.Rcon[row/Nk,0];
KeyExpansion 方法有條件地調用私有方法 SubWord和RotWord 以保持同規范命名的一致性。此外,因為在C#中沒有 word類型,我用 4字節數組實現了一個字。SubWord和RotWord的代碼是相當簡單,參見本文附帶的 AesLib 源代碼,它應該很容易理解。
稍微具備有些技巧的部分是在SubWord中查找替代值。回想一下,為了尋找代替值,你將輸入字節分成最左邊的4位比特和最右邊的4位比特。對於一個給定字節,用 >> 操作符右移 4 位將得到 x 索引,並且與0000 1111 進行邏輯與得到 y 值。雖然有些長,但比實際代碼更可讀,我可以象下面這樣:
int x = Word[0] >> 4;
int y = Word[0] & 0x0f;
byte substitute = this.Sbox[x,y];
result[0] = substitute;
代替我原來用的代碼:
result[0] = this.Sbox[ word[0] >> 4, Word[0] & 0x0f ];
總的來說,AES 構造函數接受一個密鑰的長度為128,192 或 256 位和一個字節數組種子密鑰值。構造函數為輸入塊長度,種子密鑰長度 以及加密算法的輪數賦值,並將種子密鑰拷貝到一個名為key的數據成員中。構造函數還創建了四個表:兩個由加密和解密方法使用的替換表,一個輪常數表,和一個輪密鑰的密鑰調度表。
用C#編寫的 AES Cipher 方法
Cipher方法如 Figure 11 所示。它真的非常簡單,因為它分出了大部分的工作給私有方法AddRoundKey, SubBytes, ShiftRows和MixColumns。
Cipher 方法以拷貝明文輸入數組到狀態矩陣 State[] 為開始。最初調用 AddRoundKey 之後,Cipher 方法比總輪數少迭代一次。在最後一輪時,正如規范中所說的那樣,MixColumns 調用被省略了。
AddRoundKey和SubBytes 私有方法的代碼如 Figure 12 所示。AddRoundKey 方法需要知道它處在那一輪,以便它正確引用4行密鑰調度數組 w[]。請注意 State[r,c] 是用 w[c,r] 來異或並不是w[r,c]。SubBytes 方法從輸入字節中提取索引,與KeyExpansion 方法中所用的右移4位和 0x0f 屏蔽技術相同。
ShiftRows 方法的代碼如 Figure 13 所示。回想一下,ShiftRows(可能叫做 RotateRows 更好)將 row[0] 向左旋轉 0 個位置,將 row[1] 向左旋轉 1 位置等等。
把 State[] 拷貝到 temp[] 矩陣之後,然後用下面的這行代碼實現轉換:
this.State[r, (c + r) % Nb ] = temp[r,c];
這裡利用%操作符的優點抱合一行。
MixColumns 方法(Figure 14)用GF(28)加和乘,以字節列中所有其它值的線性組合對每一個字節進行替換。
乘法所用的常量系數基於域論的,並且是0x01, 0x02或 0x03中的任意一個值。給定某一列 c ,其替代式如下:
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]
這些表達式稍微有些長,因此我決定編寫返回 GF(28)與0x01,0x02和0x03 之乘積的私有輔助函數。這些輔助函數非常短。例如,一個字節 b 被 0x03 域乘的代碼如下:
return (byte) ( (int)gfmultby02(b) ^ (int)b );
正如我前面討論的,被 0x02 乘是所有 GF(28) 乘法的基本操作。我調用了我的 gfmultby02 方法,我改變了使用與規范相同的方法命名慣例,規范上稱此例程為xtime。
Cipher 方法其輸入反復應用四個操作來產生加密的輸出。AddRoundKey 用源於單個原始種子密鑰的多重輪密鑰來替代字節。SubBytes 用某個替換表中的值替代字節。ShiftRows 用移動字節行置換字節,而 MixColumns 用某一列的域加和乘法值來替代字節。