AES算法簡介算法
AES的全稱是Advanced Encryption Standard,意思是高級加密標準。 AES密碼分組大小和密鑰大小能夠爲128位、192位和256位。然而AES只要求分組大小爲128位。本文只對分組大小128位,密鑰長度也爲128位的Rijndael算法進行分析。密鑰長度爲192位和256位的處理方式和128位的處理方式相似,只不過密鑰長度每增長64位,算法的循環次數就增長2輪,128位循環10輪、192位循環12輪、256位循環14輪。數組
AES算法使用邏輯就是:發送方將要發送的明文數據X使用祕鑰K進行AES加密後會獲得密文Y,將密文進行網絡傳輸,接受方在收到密文Y後使用祕鑰K進行AES解密後技能獲得明文X,這樣即便密文Y在網絡上傳輸時被截獲了,沒有祕鑰也難以破解其真實意思。網絡
在AES算法中的MixColumn層中會用到伽羅瓦域中的乘法運算,而伽羅瓦域的運算涉及一些數學知識以下:app
素域:ide
有限域有時也稱伽羅瓦域,它指的是由有限個元素組成的集合,在這個集合內能夠執行加、減、乘和逆運算。而在密碼編碼學中,咱們只研究擁有有限個元素的域,也就是有限域。域中包含元素的個數稱爲域的階。只有當m是一個素數冪時,即m=pn(其中n爲正整數是p的次數,p爲素數),階爲m的域才存在。p稱爲這個有限域的特徵。也就是說,有限域中元素的個數能夠是11(p=11是一個素數,n=1)、能夠是81(p=3是一個素數,n=4)、也能夠是256(p=2是一個素數,n=8).....但有限域的中不可能擁有12個元素,由於12=2·2·3,所以12也不是一個素數冪。有限域中最直觀的例子就是階爲素數的域,即n=1的域。的元素能夠用整數0、一、...、p-1l來表示。域的兩種操做就是模整數加法和整數乘法模p。加上p是一個素數,整數環Z表示爲GF(p),也成爲擁有素數個元素的素數域或者伽羅瓦域。GF(p)中全部的非零元素都存在逆元,GF(p)內全部的運算都是模p實現的。函數
素域內的算數運算規則以下:動畫
(1)加法和乘法都是經過模p實現的;編碼
(2)任何一個元素a的加法逆元都是由a+(a的逆元)=0 mod p獲得的;加密
(3)任何一個非零元素a的乘法逆元定義爲a·a的逆元=1。spa
例:在素域GF(5)={0、一、二、三、4}中,2的加法逆元爲3,這是由於2+(3)=5,5mod5=0,因此2+3=5mod5=0。2的乘法逆元爲3,這是由於2·3=6,6mod5=1,因此2·3=6mod5=1。(在不少地方a的加法逆元用-a表示,a的乘法逆元用1/a表示)
注:GF(2)是一個很是重要的素域,也是存在的最小的有限域,因爲GF(2)的加法,即模2加法與異或(XOR)門等價,GF(2)的乘法與邏輯與(AND)門等價,因此GF(2)對AES很是重要。
擴展域:
若是有限域的階不是素數,則這樣的有限域內的加法和乘法運算就不能用模整數加法和整數乘法模p表示。並且m>1的域被稱爲擴展域,爲了處理擴展域,咱們就要使用不一樣的符號表示擴展域內的元素,使用不一樣的規則執行擴展域內元素的算術運算。
在擴展域GF(2^m)中,元素並非用整數表示的,而是用係數爲域GF(2)中元素的多項式表示。這個多項式最大的度(冪)爲m-1,因此每一個元素共有m個係數,在AES算法使用的域GF(2^8)中,每一個元素A∈GF(28)均可以表示爲:
注意:在域GF(28)中這樣的多項式共有256個,這256個多項式組成的集合就是擴展域GF(28)。每一個多項式均可以按一個8位項鍊的數值形式存儲:
像x7、x6等因子都無需存儲,由於從位的位置就能夠清楚地判斷出每一個係數對應的冪。
擴展域GF(2m)內的加減法:
在AES算法中的密鑰加法層中就使用了這部分的知識,可是不是很明顯,由於咱們一般把擴展域中的加法看成異或運算進行處理了,由於在擴展域中的加減法處理都是在底層域GF(2)內完成的,與按位異或運算等價。假設A(x)、B(x)∈GF(2m),計算兩個元素之和的方法就是:
而兩個元素之差的計算公式就是:
注:在減法運算中減號之因此變成加號,這就和二進制減法的性質有關了。從上述兩個公式中咱們發如今擴展域中加法和減法等價,而且與XOR等價(異或運算也被稱做二進制加法)。
擴展域GF(2^m)內的乘法
擴展域的乘法主要運用在AES算法的列混淆層(Mix Column)中,也是列混淆層中最重要的操做。咱們項要將擴展域中的兩個元素用多項式形式展開,而後使用標準的多項式乘法規則將兩個多項式相乘:
注:一般在多項式乘法中C(x)的度會大於m-1,所以須要對此進行化簡,而化簡的基本思想與素域內乘法狀況類似:在素域GF(p)中,將兩個整數相乘獲得的結果除以一個素數,化簡後的結果就是最後的餘數。而在擴展域中進行的操做就是:將兩個多項式相乘的結果除以一個不可約多項式,最後的結果就是最後的餘數。(這裏的不可約多項式大體能夠看做一個素數)
例:
AES算法主要有四種操做處理,分別是密鑰加法層(也叫輪密鑰加,英文Add Round Key)、字節代換層(SubByte)、行位移層(Shift Rows)、列混淆層(Mix Column)。而明文x和密鑰k都是由16個字節組成的數據(固然密鑰還支持192位和256位的長度,暫時不考慮),它是按照字節的前後順序從上到下、從左到右進行排列的。而加密出的密文讀取順序也是按照這個順序讀取的,至關於將數組還原成字符串的模樣了,而後再解密的時候又是按照4·4數組處理的。AES算法在處理的輪數上只有最後一輪操做與前面的輪處理上有些許不一樣(最後一輪只是少了列混淆處理),在輪處理開始前還單獨進行了一次輪密鑰加的處理。在處理輪數上,咱們只考慮128位密鑰的10輪處理。接下來,就開始一步步的介紹AES算法的處理流程了。
AES算法流程圖:
密鑰加法層
在密鑰加法層中有兩個輸入的參數,分別是明文和子密鑰k[0],並且這兩個輸入都是128位的。k[0]實際上就等同於密鑰k,具體緣由在密鑰擴展生成中進行介紹。咱們前面在介紹擴展域加減法中提到過,在擴展域中加減法操做和異或運算等價,因此這裏的處理也就異常的簡單了,只須要將兩個輸入的數據進行按字節異或操做就會獲得運算的結果。
圖示:
1 int AddRoundKey(unsigned char(*PlainArray)[4], unsigned char(*ExtendKeyArray)[44], unsigned int MinCol) 2 { 3 int ret = 0; 4 5 for (int i = 0; i < 4; i++) 6 { 7 for (int j = 0; j < 4; j++) 8 { 9 PlainArray[i][j] ^= ExtendKeyArray[i][MinCol + j]; 10 } 11 } 12 13 return ret; 14 }
字節代換層
字節代換層的主要功能就是讓輸入的數據經過S_box表完成從一個字節到另外一個字節的映射,這裏的S_box表是經過某種方法計算出來的,具體的計算方法在此不作詳述,咱們主須要知道如何使用S_box結果便可。S_box表是一個擁有256個字節元素的數組,可以將其定義爲一維數組,也能夠將其定義爲16·16的二維數組,若是將其定義爲二維數組,讀取S_box數據的方法就是要將輸入數據的每一個字節的高四位做爲第一個下標,第四位做爲第二個下標,略有點麻煩。這裏建議將其視做一維數組便可。逆S盒與S盒對應,用於解密時對數據處理,咱們對解密時的程序處理稱做逆字節代換,只是使用的代換表盒加密時不一樣而已。
S盒
逆S盒
加密圖示:
1 //S盒 2 const unsigned char S_Table[16][16] = 3 { 4 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 5 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 6 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 7 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 8 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 9 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 10 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 11 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 12 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 13 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 14 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 15 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 16 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 17 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 18 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 19 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16 20 }; 21 22 //字節代換 23 int Plain_S_Substitution(unsigned char *PlainArray) 24 { 25 int ret = 0; 26 27 for (int i = 0; i < 16; i++) 28 { 29 PlainArray[i] = S_Table[PlainArray[i] >> 4][PlainArray[i] & 0x0F]; 30 } 31 32 return ret; 33 } 34 35 36 //逆S盒 37 const unsigned char ReS_Table[16][16] = 38 { 39 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, 40 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, 41 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, 42 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, 43 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, 44 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, 45 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, 46 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, 47 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, 48 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, 49 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, 50 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, 51 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, 52 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, 53 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, 54 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D 55 }; 56 //逆字節代換 57 int Cipher_S_Substitution(unsigned char *CipherArray) 58 { 59 int ret = 0; 60 61 for (int i = 0; i < 16; i++) 62 { 63 CipherArray[i] = ReS_Table[CipherArray[i] >> 4][CipherArray[i] & 0x0F]; 64 } 65 66 return ret; 67 }
行位移——ShiftRows
行位移操做最爲簡單,它是用來將輸入數據做爲一個4·4的字節矩陣進行處理的,而後將這個矩陣的字節進行位置上的置換。ShiftRows子層屬於AES手動的擴散層,目的是將單個位上的變換擴散到影響整個狀態當,從而達到雪崩效應。在加密時行位移處理與解密時的處理相反,咱們這裏將解密時的處理稱做逆行位移。它之因此稱做行位移,是由於它只在4·4矩陣的行間進行操做,每行4字節的數據。
加密時:保持矩陣的第一行不變,第二行向左移動8Bit(一個字節)、第三行向左移動2個字節、第四行向左移動3個字節。
解密時:保持矩陣的第一行不變,第二行向右移動8Bit(一個字節)、第三行向右移動2個字節、第四行向右移動3個字節。
正向行位移圖解:
1 //這裏將char二維數組強制轉換成int一維數組處理int ShiftRows(unsigned int *PlainArray) 2 { 3 int ret = 0; 4 5 //第一行 不移位 6 //PlainArray[0] = PlainArray[0]; 7 8 //第二行 左移8Bit 9 PlainArray[1] = (PlainArray[1] >> 8) | (PlainArray[1] << 24); 10 11 //第三行 左移16Bit 12 PlainArray[2] = (PlainArray[2] >> 16) | (PlainArray[2] << 16); 13 14 //第四行 左移24Bit 15 PlainArray[3] = (PlainArray[3] >> 24) | (PlainArray[3] << 8); 16 17 return ret; 18 }
逆向行位移圖解:
1 //這裏將char二維數組強制轉換成int一維數組處理 2 int ReShiftRows(unsigned int *CipherArray) 3 { 4 int ret = 0; 5 6 //第一行 不移位 7 //CipherArray[0] = CipherArray[0]; 8 9 //第二行 右移8Bit 10 CipherArray[1] = (CipherArray[1] << 8) | (CipherArray[1] >> 24); 11 12 //第三行 右移16Bit 13 CipherArray[2] = (CipherArray[2] << 16) | (CipherArray[2] >> 16); 14 15 //第四行 右移24Bit 16 CipherArray[3] = (CipherArray[3] << 24) | (CipherArray[3] >> 8); 17 18 return ret; 19 }
列混淆——MixColumn
列混淆子層是AES算法中最爲複雜的部分,屬於擴散層,列混淆操做是AES算法中主要的擴散元素,它混淆了輸入矩陣的每一列,使輸入的每一個字節都會影響到4個輸出字節。行位移子層和列混淆子層的組合使得通過三輪處理之後,矩陣的每一個字節都依賴於16個明文字節成可能。其中包含了矩陣乘法、伽羅瓦域內加法和乘法的相關知識。
在加密的正向列混淆中,咱們要將輸入的4·4矩陣左乘一個給定的4·4矩陣。而它們之間的加法、乘法都在擴展域GF(28)中進行,因此也就能夠將這一個步驟分紅兩個部分進行講解:
1) 正向列混淆
正向列混淆的原理圖以下:
根據矩陣的乘法可知,在列混淆的過程當中,每一個字節對應的值只與該列的4個值有關係。此處的乘法和加法都是定義在GF(28)上的,須要注意以下幾點:
1) 將某個字節所對應的值乘以2,其結果就是將該值的二進制位左移一位,若是原始值的最高位爲1,則還須要將移位後的結果異或00011011;
2) 乘法對加法知足分配率,例如:07·S0,0=(01⊕02⊕04)·S0,0= S0,0⊕(02·S0,0)(04·S0,0)
3) 此處的矩陣乘法與通常意義上矩陣的乘法有所不一樣,各個值在相加時使用的是模28加法(異或運算)。
下面舉一個例子,假設某一列的值以下圖,運算過程以下:
在計算02與C9的乘積時,因爲C9對應最左邊的比特爲1,所以須要將C9左移一位後的值與(0001 1011)求異或。同理能夠求出另外幾個值。
2) 逆向列混淆
逆向列混淆的原理圖以下:
因爲:
說明兩個矩陣互逆,通過一次逆向列混淆後便可恢復原文。
1 //列混淆左乘矩陣 2 const unsigned char MixArray[4][4] = 3 { 4 0x02, 0x03, 0x01, 0x01, 5 0x01, 0x02, 0x03, 0x01, 6 0x01, 0x01, 0x02, 0x03, 7 0x03, 0x01, 0x01, 0x02 8 }; 9 10 int MixColum(unsigned char(*PlainArray)[4]) 11 { 12 int ret = 0; 13 //定義變量 14 unsigned char ArrayTemp[4][4]; 15 16 //初始化變量 17 memcpy(ArrayTemp, PlainArray, 16); 18 19 //矩陣乘法 4*4 20 for (int i = 0; i < 4; i++) 21 { 22 for (int j = 0; j < 4; j++) 23 { 24 PlainArray[i][j] = 25 MixArray[i][0] * ArrayTemp[0][j] + 26 MixArray[i][1] * ArrayTemp[1][j] + 27 MixArray[i][2] * ArrayTemp[2][j] + 28 MixArray[i][3] * ArrayTemp[3][j]; 29 } 30 } 31 32 return ret; 33 }
咱們發如今矩陣乘法中,出現了加法和乘法運算,咱們前面也提到過在擴展域中加法操做等同於異或運算,而乘法操做須要一個特殊的方式進行處理,因而咱們就先把代碼中的加號換成異或符號,而後將伽羅瓦域的乘法定義成一個有兩個參數的函數,並讓他返回最後計算結果。因而列混淆的代碼就會變成下面的樣子:
1 const unsigned char MixArray[4][4] = 2 { 3 0x02, 0x03, 0x01, 0x01, 4 0x01, 0x02, 0x03, 0x01, 5 0x01, 0x01, 0x02, 0x03, 6 0x03, 0x01, 0x01, 0x02 7 }; 8 9 int MixColum(unsigned char(*PlainArray)[4]) 10 { 11 int ret = 0; 12 //定義變量 13 unsigned char ArrayTemp[4][4]; 14 15 //初始化變量 16 memcpy(ArrayTemp, PlainArray, 16); 17 18 //矩陣乘法 4*4 19 for (int i = 0; i < 4; i++) 20 { 21 for (int j = 0; j < 4; j++) 22 { 23 PlainArray[i][j] = 24 GaloisMultiplication(MixArray[i][0], ArrayTemp[0][j]) ^ 25 GaloisMultiplication(MixArray[i][1], ArrayTemp[1][j]) ^ 26 GaloisMultiplication(MixArray[i][2], ArrayTemp[2][j]) ^ 27 GaloisMultiplication(MixArray[i][3], ArrayTemp[3][j]); 28 } 29 } 30 return ret; 31 }
接下來咱們就只用處理伽羅瓦域乘法相關處理了,因爲前面介紹過相關概念,因此代碼就不在此進行講解了,你們能夠參考下方的代碼註釋進行理解:
1 /////////////////////////////////////////////////////////////// 2 //功能: 伽羅瓦域內的乘法運算 GF(128) 3 //參數: Num_L 輸入的左參數 4 // Num_R 輸入的右參數 5 //返回值:計算結果 6 char GaloisMultiplication(unsigned char Num_L, unsigned char Num_R) 7 { 8 //定義變量 9 unsigned char Result = 0; //伽羅瓦域內乘法計算的結果 10 11 while (Num_L) 12 { 13 //若是Num_L最低位是1就異或Num_R,至關於加上Num_R * 1 14 if (Num_L & 0x01) 15 { 16 Result ^= Num_R; 17 } 18 19 //Num_L右移一位,至關於除以2 20 Num_L = Num_L >> 1; 21 22 //若是Num_R最高位爲1 23 if (Num_R & 0x80) 24 { 25 //左移一位至關於乘二 26 Num_R = Num_R << 1; //注:這裏會丟失最高位,可是不用擔憂 27 28 Num_R ^= 0x1B; //計算伽羅瓦域內除法Num_R = Num_R / (x^8(恰好丟失最高位) + x^4 + x^3 + x^1 + 1) 29 } 30 else 31 { 32 //左移一位至關於乘二 33 Num_R = Num_R << 1; 34 } 35 } 36 return Result; 37 }
在解密的逆向列混淆中與正向列混淆的不一樣之處在於使用的左乘矩陣不一樣,它與正向列混淆的左乘矩陣互爲逆矩陣,也就是說,數據矩陣同時左乘這兩個矩陣後,數據矩陣不會發生任何變化。
正向列混淆處理
逆向列混淆
加解密驗證
加密部分講解完畢,最後應該注意要將密文結果從矩陣形式還原成字符串形式輸出!
AES密鑰擴展
子密鑰的生成是以列爲單位進行的,一列是32Bit,四列組成子密鑰共128Bit。生成子密鑰的數量比AES算法的輪數多一個,由於第一個密鑰加法層進行密鑰漂白時也須要子密鑰。密鑰漂白是指在AES的輸入盒輸出中都使用的子密鑰的XOR加法。子密鑰在圖中都存儲在W[0]、W[1]、...、W[43]的擴展密鑰數組之中。k1-k16表示原始密鑰對應的字節,而圖中子密鑰k0與原始子密鑰相同。在生成的擴展密鑰中W的下標若是是4的倍數時(從零開始)須要對異或的參數進行G函數處理。擴展密鑰生成有關公式以下:
1 1<= i <= 10 2 1<= j <= 3 3 w[4i] = W[4(i-1)] + G(W[4i-1]); 4 w[4i+j] = W[4(i-1)+j] + W[4i-1+j];
函數G()首先將4個輸入字節進行翻轉,並執行一個按字節的S盒代換,最後用第一個字節與輪系數Rcon進行異或運算。輪系數是一個有10個元素的一維數組,一個元素1個字節。G()函數存在的目的有兩個,一是增長密鑰編排中的非線性;二是消除AES中的對稱性。這兩種屬性都是抵抗某些分組密碼攻擊必要的。
1 //用於密鑰擴展 Rcon[0]做爲填充,沒有實際用途 2 const unsigned int Rcon[11] = { 0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36 }; 3 4 5 int Key_S_Substitution(unsigned char(*ExtendKeyArray)[44], unsigned int nCol) 6 { 7 int ret = 0; 8 9 for (int i = 0; i < 4; i++) 10 { 11 ExtendKeyArray[i][nCol] = S_Table[(ExtendKeyArray[i][nCol]) >> 4][(ExtendKeyArray[i][nCol]) & 0x0F]; 12 } 13 14 return ret; 15 } 16 17 18 int G_Function(unsigned char(*ExtendKeyArray)[44], unsigned int nCol) 19 { 20 int ret = 0; 21 22 //一、將擴展密鑰矩陣的nCol-1列複製到nCol列上,並將nCol列第一行的元素移動到最後一行,其餘行數上移一行 23 for (int i = 0; i < 4; i++) 24 { 25 ExtendKeyArray[i][nCol] = ExtendKeyArray[(i + 1) % 4][nCol - 1]; 26 } 27 28 //二、將nCol列進行S盒替換 29 Key_S_Substitution(ExtendKeyArray, nCol); 30 31 //三、將該列第一行元素與Rcon進行異或運算 32 ExtendKeyArray[0][nCol] ^= Rcon[nCol / 4]; 33 34 return ret; 35 } 36 37 38 int CalculateExtendKeyArray(const unsigned char(*PasswordArray)[4], unsigned char(*ExtendKeyArray)[44]) 39 { 40 int ret = 0; 41 42 //一、將密鑰數組放入前四列擴展密鑰組 43 for (int i = 0; i < 16; i++) 44 { 45 ExtendKeyArray[i & 0x03][i >> 2] = PasswordArray[i & 0x03][i >> 2]; 46 } 47 48 //二、計算擴展矩陣的後四十列 49 for (int i = 1; i < 11; i++) //進行十輪循環 50 { 51 //(1)若是列號是4的倍數,這執行G函數 不然將nCol-1列複製到nCol列上 52 G_Function(ExtendKeyArray, 4*i); 53 54 //(2)每一輪中,各列進行異或運算 55 // 列號是4的倍數 56 for (int k = 0; k < 4; k++)//行號 57 { 58 ExtendKeyArray[k][4 * i] = ExtendKeyArray[k][4 * i] ^ ExtendKeyArray[k][4 * (i - 1)]; 59 } 60 61 // 其餘三列 62 for (int j = 1; j < 4; j++)//每一輪的列號 63 { 64 for (int k = 0; k < 4; k++)//行號 65 { 66 ExtendKeyArray[k][4 * i + j] = ExtendKeyArray[k][4 * i + j - 1] ^ ExtendKeyArray[k][4 * (i - 1) + j]; 67 } 68 } 69 } 70 71 return ret; 72 }
AES解密流程圖
至此,AES算法基礎部分介紹完畢!
歐幾里得算法:
兩個正整數r0和r1的gcd表示爲gcd(r0, r1),它指的是能夠被r0和r1同時整除的最大正整數,例如gcd(21, 6)=3。對與較小的整數而言gcd就是將兩個整數進行因式分解,並找出最大的公因子。
例:r0=84,r1=30,因式分解:r0=2·2·3·7;r1=2·3·5;gcd的結果就是:gcd(30,84)=2·3=6。
gcd(r0,r1)=gcd(r0-r1,r1)其中假設r0>r1,而且兩個數均是正整數。證實:gcd(r0,r1)=g,因爲g能夠同時被r0、r1整除,則能夠記做r0=g·x、r1=g·y,其中x>y,而且x和y互爲素數。因此獲得:gcd(r0,r1)=gcd(r0-r1,r1)=gcd(g·(x-y),g·y)=g=gcd(ri,0)。
例:r0=973,r1=301,gcd的計算方式爲:
1 //輸入兩個正整數r0>r1,輸出計算結果 2 int gcd(int r0, int r1) 3 { 4 int r=0; 5 while(r1 != 0) 6 { 7 r = r0 % r1; 8 r0 = r1; 9 r1 = r; 10 } 11 return r0; 12 }
擴展歐幾里得算法:
擴展歐幾里得算法主要的應用不是爲例計算最大公因子,它的在密碼學中主要的做用是爲了計算乘法逆元,乘法逆元在公鑰密碼學中佔有着舉足輕重的地位。固然,除了擴展歐幾里得算法(EEA)除了能夠計算gcd,它還能夠計算如下形式的線性組合:
其中s和t都表示整型係數。關於如何計算這兩個係數的推到過程這裏就不介紹了,咱們只給出最後的公式結論:
介紹完這些公式,咱們來看看乘法的逆元是怎麼計算的吧。假設咱們要計算r1 mod r0的逆元,其中r1 < r0。咱們前面提到過乘法的逆元計算公式爲a*b=1 mod p,b就是a mod p的乘法逆元,也就是gcd(p, a)=1的狀況。下才存在乘法逆元。則s·r0+t·r1=1=gcd(r0,r1),將這個等式執行模r0計算可得:
例:計算12 mod 67,12的逆元,即gcd(67,12)=1
注:一般狀況下不須要計算係數S,並且實際中通常也用不上它,另外結果t多是一個負數,這種狀況下就必須把t加是r0讓人的結果爲正,由於t=(t+r0) mod r0。
1 int EEA(int r0, int r1) 2 { 3 int mod = r0; 4 int r = 0; 5 int t0 = 0; 6 int t1 = 1; 7 int t = t1; 8 int q = 0; 9 10 //0不存在乘法逆元 11 if (r1 == 0) 12 { 13 return 0; 14 } 15 16 while (r1 != 1) 17 { 18 q = r0 / r1; 19 20 r = r0 - q * r1; 21 22 t = t0 - q * t1; 23 24 r0 = r1; 25 r1 = r; 26 t0 = t1; 27 t1 = t; 28 } 29 30 //結果爲負數 31 if (t < 0) 32 { 33 t = t + mod; 34 } 35 36 return t; 37 }
若是要想計算伽羅瓦域內乘法的逆元,函數的輸入r0就是GF(2^8)的不可約多項式p(x),r1就是域元素a(x),而後經過EEA計算多項式t(x)獲得a(x)的乘法逆元。只不過在上方給出的EEA代碼略有不一樣,由於在伽羅瓦域中多項式都是在GF(2)上進行加減運算的,也就是說上面的加號和減號都要換成異或運算符,同時乘法和除法也有要進行適當的調整,轉變成多項式乘法和除法。不然結果會出現誤差。
伽羅瓦域的擴展歐幾里得算法:
1 //獲取最高位 2 int GetHighestPosition(unsigned short Number) 3 { 4 int i = 0; 5 while (Number) 6 { 7 i++; 8 Number = Number >> 1; 9 } 10 return i; 11 } 12 13 14 //GF(2^8)的多項式除法 15 unsigned char Division(unsigned short Num_L, unsigned short Num_R, unsigned short *Remainder) 16 { 17 unsigned short r0 = 0; 18 unsigned char q = 0; 19 int bitCount = 0; 20 21 r0 = Num_L; 22 23 bitCount = GetHighestPosition(r0) - GetHighestPosition(Num_R); 24 while (bitCount >= 0) 25 { 26 q = q | (1 << bitCount); 27 r0 = r0 ^ (Num_R << bitCount); 28 bitCount = GetHighestPosition(r0) - GetHighestPosition(Num_R); 29 } 30 *Remainder = r0; 31 return q; 32 } 33 34 35 36 //GF(2^8)多項式乘法 37 short Multiplication(unsigned char Num_L, unsigned char Num_R) 38 { 39 //定義變量 40 unsigned short Result = 0; //伽羅瓦域內乘法計算的結果 41 42 for (int i = 0; i < 8; i++) 43 { 44 Result ^= ((Num_L >> i) & 0x01) * (Num_R << i); 45 } 46 47 return Result; 48 } 49 50 51 int EEA_V2(int r0, int r1) 52 { 53 int mod = r0; 54 int r = 0; 55 int t0 = 0; 56 int t1 = 1; 57 int t = t1; 58 int q = 0; 59 60 if (r1 == 0) 61 { 62 return 0; 63 } 64 65 while (r1 != 1) 66 { 67 //q = r0 / r1; 68 q = Division(r0, r1, &r); 69 70 r = r0 ^ Multiplication(q, r1); 71 72 t = t0 ^ Multiplication(q, t1); 73 74 r0 = r1; 75 r1 = r; 76 t0 = t1; 77 t1 = t; 78 } 79 80 if (t < 0) 81 { 82 t = t ^ mod; 83 } 84 85 return t; 86 }
生成S盒的過程:
AES的S盒具備很是強的代數結構,它是通過兩個步驟計算而來的:
咱們已經瞭解的逆元的計算過程,接下來只剩下了仿射映射過程了。
S盒的仿射映射:
仿射映射這個名詞聽起來有點高深莫測的感受,不過在個人理解上,它就是一個計算過程。S盒的仿射映射也比較簡單,主要就是運用到了矩陣乘法,不過這個矩陣是Bit矩陣。先上一下計算方法:
注意仿射映射全部的計算都是基於GF(2)上的。咱們從計算過程上發現,輸入數據A的逆元B在仿射映射中被展開成了一個8·1的矩陣,最上方是LSB,而後左乘了一個8·8的Bit矩陣,後加上了0x63展開的8·1矩陣,因爲是基於GF(2)的,因此須要進行mod 2操做,最終的結果纔是輸出數據C。可能有些同窗仍是看不懂這張圖,那咱們就以輸入數據爲A=0x7爲例,手動計算一下這個結果C:
一、計算乘法逆元:
將如下兩個參數帶入EEA_V2()函數能夠獲得A的逆元:
最終結果爲:B=EEA(A)=0xD1
二、仿射映射(重點):
咱們將上述結果B拆成Bit帶入第二個矩陣得:
1 c[0] = (1*1) ^ (0*0) ^ (0*0) ^ (0*0) ^ (1*1) ^ (1*0) ^ (1*1) ^ (1*1) ^ 1 = 1 2 c[1] = (1*1) ^ (1*0) ^ (0*0) ^ (0*0) ^ (0*1) ^ (1*0) ^ (1*1) ^ (1*1) ^ 1 = 0 3 c[2] = (1*1) ^ (1*0) ^ (1*0) ^ (0*0) ^ (0*1) ^ (0*0) ^ (1*1) ^ (1*1) ^ 0 = 1 4 c[3] = (1*1) ^ (1*0) ^ (1*0) ^ (1*0) ^ (0*1) ^ (0*0) ^ (0*1) ^ (1*1) ^ 0 = 0 5 c[4] = (1*1) ^ (1*0) ^ (1*0) ^ (1*0) ^ (1*1) ^ (0*0) ^ (0*1) ^ (0*1) ^ 0 = 0 6 c[5] = (0*1) ^ (1*0) ^ (1*0) ^ (1*0) ^ (1*1) ^ (1*0) ^ (0*1) ^ (0*1) ^ 1 = 0 7 c[6] = (0*1) ^ (0*0) ^ (1*0) ^ (1*0) ^ (1*1) ^ (1*0) ^ (1*1) ^ (0*1) ^ 1 = 1 8 c[7] = (0*1) ^ (0*0) ^ (0*0) ^ (1*0) ^ (1*1) ^ (1*0) ^ (1*1) ^ (1*1) ^ 0 = 1
最後獲得的仿射映射的結果爲:C=0xC5
查看S盒,驗證結果正確!
1 unsigned char ByteImage(int imput) 2 { 3 unsigned char Result = 0; 4 5 for (int i = 0; i < 8; i++) 6 { 7 Result ^= (((imput >> i) & 1) ^ ((imput >> ((i + 4) % 8)) & 1) ^ ((imput >> ((i + 5) % 8)) & 1) ^ ((imput >> ((i + 6) % 8)) & 1) ^ ((imput >> ((i + 7) % 8)) & 1)) << i; 8 } 9 10 Result = Result ^ 0x63; 11 12 return Result; 13 }
生成逆S盒的過程:
能夠發現逆S盒是先進行逆仿射映射,而後才計算乘法逆元的,這也是與S盒生成的不一樣之處,而逆仿射映射與仿射映射的結構是相同的,只不過8·8Bit矩陣的數值不一樣,最後異或的那個數字不是0x63而是0xA0。
附件:AES算法演示動畫