实验目的
通过用AES算法对实际的数据进行加密和解密来深刻了解AES的运行原理。
实验原理
AES算法本质上是一种对称分组密码体制,采用代替/置换网络,每轮由三 层组成:线性混合层确保多轮之上的高度扩散,非线性层由16个S盒并置起到混淆的作用,密钥加密层将子密钥异或到中间状态。Rijndael是一个迭代分组密码,其分组长度和密钥长度都是可变的,只是为了满足AES的要求才限定处理的分组大小为128位,而密钥长度为128位、192位或256位,相应的迭代轮数N,为10轮、12轮、14轮。AES汇聚了安全性能、效率、可实现性、灵活性等优点。最大的优点是可以给出算法的最佳差分特征的概率,并分析算法抵抗差分密码分析及线性密码分析的能力。其实现的加密流程图如图-1所示。
加密的主要过程包括:对明文状态的一次密钥加,N_r-1轮轮加密和末尾轮轮加密,最后得到密文。其中N_r-1轮轮加密每一轮有四个部件,包括字节代换部件ByteSub、行移位变换ShiftRow、列混合变换NixColumn和一个密钥加AddRoundKey部件,末尾轮加密和前面轮加密类似,只是少了一个列混合变换NixColumn部件。
AES算法流程
实验过程
总体设计
在本次实验中,密钥、明文、密文遍码均为string类型,将输入的128bit的数据分为四个部分,每32bit一组,变为十六进制的话就是8个十六进制数(4个字节)一组,所以一组明文(密文)需要用string[4]数组记录。本程序中的明文用全局变量string ming[4]记录,密文用全局变量string ci[4]记录,密钥由于扩展后会得到44个,所以用string k[44]记录。
程序流程
密钥扩展
密钥扩展流程
密钥扩展过程说明:
将初始密钥以列为主,转化为4个32 bits的字,分别记为w[0...3];
按照如下方式,依次求解w[j],其中j是整数并且属于[4,43];
若j%4=0,则w[j]=w[j-4]^T(w[j-1]),否则w[j]=w[j-4]^w[j-1];
函数T由3部分组成: 字循环、字节代换和轮常量异或,这3部分的作用分别如下:
字循环:将1个字中的4个字节循环左移1个字节。即将输入字[b0, b1, b2, b3]变换成[b1,b2,b3,b0]。
字节代换:对字循环的结果使用S盒进行字节代换。
轮常量异或:将前两步的结果同轮常量Rcon[j]进行异或,其中j表示轮数。
1 2 3 4 5 6 7 string T (string a, int i) { a = left_one (a); a = key_S (a); a = key_xor (a, i); return a; }
T函数的组成部分如上述代码,接下来将对每个函数进行介绍盒分析。
字循环
在本实验中,通过讲密钥记录为string类型,然后传入以下函数实现左移:这个函数实现了左移一位,具体的实现方法是循环获取到前两位之后的字符,然后把前两位贴到最后 ,从而实现左移。
1 2 3 4 5 6 7 8 9 10 11 string left_one (string a) { string b ="" ; for (int i = 2 ; i < 8 ; i++) { b += a[i]; } b += a[0 ]; b += a[1 ]; return b; }
字节代换
分别对每个字节按S盒进行映射,每个字节中的前四位代表S盒中的行,后四位代表S盒中的列 ,通过这样的映射关系 实现字节的代换。
AES算法流程
下列代码中,ctode()函数表示将字符转化为数字 ,htos()表示将数字重新转换回字符 ,最后通过a1记录所有转化过来的字符,从而实现字节代换。
1 2 3 4 5 6 7 8 9 string key_S (string a) { string a1 = "" ; for (int i = 0 ; i < a.length (); i+=2 ) { a1 += htos (S[ctode ((a[i]))][ctode ((a[i+1 ]))]); } return a1; }
轮常量异或
将前两步的结果同轮常量Rcon[j]进行异或,其中j表示轮数。 轮常量Rcon[j]是一个字,其值见下表。
轮常量Rcon
1 2 3 4 5 6 7 8 9 10 string key_xor (string a,int n) { string b = "" ; for (int i = 0 ; i < a.length (); i++) { string c = htoc (ctode (a[i]) ^ ctode (Rcon[n][i])); b += c; } return b; }
整体代码
将上述代码整合得到Getkeys()函数,该函数的作用为将输入密钥当作参数,最后将密钥扩展后得到的44个密钥存入到全局变量k[44]中,留给之后的加密、解密函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void Getkeys (string key) { key = cin_check (key); for (int i = 0 ; i < 4 ; i++) { k[i] = "" ; for (int j = i * 8 ; j < i * 8 + 8 ; j++) { k[i] += key[j]; } } int o = 0 ; for (int i = 4 ; i < 44 ; i++) { if (i % 4 != 0 ) { k[i] = keys_xor (k[i - 4 ], k[i - 1 ]); } else { k[i] = keys_xor (k[i - 4 ], T (k[i - 1 ],o)); o++; } } }
AES明文加密
AES加密过程涉及到4种操作:字节替代 (SubBytes)、行移位 (ShiftRows)、列混淆 (MixColumns)和轮密钥加 (AddRoundKey)。解密过程分别为对应的逆操作。由于每一步操作都是可逆的,按照相反的顺序进行解密即可恢复明文。加解密中每轮的密钥分别由初始密钥扩展得到。算法中16字节的明文、密文和轮密钥都以一个4x4的矩阵表示。
字节替代
字节替代的过程和密钥扩展中S盒替代的过程相同,此处不再赘述。
行移位
行移位的功能是实现一个4x4矩阵内部字节之间的置换,但是注意这里的第一列表示按顺序输入的密钥 。行移位需要将第二行左移一位,第三行左移两位,第四行左移三位。
正向行移位
在本程序中,ming[4]输入存储下来的是按顺序获得的明文 ,所以首先需要拿到列数据并进行存储(也就是行移位中对应的行数据),然后通过在密钥左移中的letf_one()函数来实现左移。将每一行需要移位的数目记下并放入循环中,左移一位会调用一次letf_one()、左移两位会调用两次letf_one(),以此类推 实现行移位功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 string ming_l[4 ] = { "" }; for (int n = 0 ; n < 4 ; n++) { for (int i = 0 ; i < 4 ; i++) { for (int j = n * 2 ; j < n * 2 + 2 ; j++) { ming_l[n] += ming[i][j]; } } } for (int i = 0 ; i < 4 ; i++) { int j = i; while (j != 0 ) { ming_l[i] = left_one (ming_l[i]); j--; } }
列混淆
根据矩阵的乘法可知,在列混淆的过程中,每个字节对应的值只与该列的4个值有关系。
正向列混淆
此处的乘法和加法都是定义在GF(\(2^{8}\) )上的,需要注意如下几点:
所以相当于进行以下运算:
定义在GF上
首先先完成对上述GF(\(2^{8}\) )上"*"和"+"操作的实现:
首先记录传入的一组明文(32bit)和其需要相乘的数据(32bit),然后通过两个数组分别获取到传入一组数据的每个字节 ,对每个相乘数据(例如:"02""03""01""01")字节的值进行判断,通过sum来记录其二进制中是否为"01"。
若该数为"01",直接获取该字节;
若该数为其他数字,则用mult2[3]数组记录明文字节分别和"02""04""08"进行运算的值 。
然后再判断相乘数据字节的的具体数据可以由哪些字节异或得到 (例如"0C"由"04"、"08"组成),就需要将对象的mult2[3]中对应的字节进行异或。这里需要注意加密和解密过程中相乘数据字节的不同,本程序中引入了lock标志位来标识 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 string ming_xor (string a, string b) { string m[4 ] = { "" }; string f[4 ] = { "" }; for (int i = 0 ; i < 4 ; i++) { for (int j = i * 2 ; j < i * 2 + 1 ; j++) { m[i] += a[j]; m[i] += a[j + 1 ]; f[i] += b[j]; f[i] += b[j + 1 ]; } } int c[4 ]; for (int i = 0 ; i < 4 ; i++) { c[i] = stode (m[i]); int p = stode (f[i]); int sum = (p / 8 ) + (p % 8 / 4 ) + (p % 8 % 4 / 2 ); int mult2[3 ] = { 0 }; int lock = 1 ; int temp = c[i]; if (sum != 0 ) { for (int j = 0 ; j < 3 ; j++) { c[i] = (c[i] << 1 ); if (c[i] >= 256 ) { c[i] = c[i] & 0xff ; c[i] = c[i] ^ 0b00011011 ; } mult2[j] = c[i]; } if (p / 8 == 1 ) { c[i] = mult2[2 ]; lock = 0 ; } else { c[i] = mult2[0 ]; } if (p % 8 / 4 == 1 ) { c[i] ^= mult2[1 ]; } if (p % 8 % 4 / 2 == 1 && lock == 0 ) { c[i] ^= mult2[0 ]; } if (p % 2 == 1 ) { c[i] ^= temp; } } else { c[i] = temp; } } int aq = c[0 ] ^ c[1 ] ^ c[2 ] ^ c[3 ]; return htos (aq); }
在程序实现的过程中,q表示当前轮数,需要注意在最后一轮的时候不用进行列混淆 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 string Mix[4 ] = { "02030101" , "01020301" , "01010203" , "03010102" }; string ming_3[4 ] = { "" }; if (q == 10 ) { for (int n = 0 ; n < 4 ; n++) { for (int i = 0 ; i < 4 ; i++) { for (int j = n * 2 ; j < n * 2 + 2 ; j++) { ming_3[n] += ming_2[i][j]; } } } } else { for (int i = 0 ; i < 4 ; i++) { for (int j = 0 ; j < 4 ; j++) { ming_3[i] += ming_xor (ming_2[j], Mix[i]); } } }
轮密钥加
轮密钥加是将128位轮密钥Ki同状态矩阵中的数据进行逐位异或操作,其中的异或函数与在密钥扩展中的异或函数相同。
1 2 3 4 5 6 7 8 9 void AddRoundKey (int n) { int j = n * 4 ; for (int i = 0 ; i < 4 ; i++) { ming[i] = keys_xor (ming[i], k[j]); j++; } }
AES密文解密
AES解密的过程实际上就是AES加密的逆过程 ,例如在轮函数中,AES加密的某一轮的过程是字节替代->行移位->列混淆->轮密钥加,所以AES解密的对应这一轮的操作就应该是轮密钥加->逆列混淆->逆行移位->逆字节替换。
逆向行移位
行移位的逆变换是将状态矩阵中的每一行执行相反的移位操作,例如AES-128中,状态矩阵的第0行右移0字节,第1行右移1字节,第2行右移2字节,第3行右移3字节。在本程序中的实现,采用使用左移3位替换右移1位 的方法,接着使用left_one()函数,与实现行移位功能大致相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 string ming_l[4 ] = { "" }; for (int n = 0 ; n < 4 ; n++) { for (int i = 0 ; i < 4 ; i++) { for (int j = n * 2 ; j < n * 2 + 2 ; j++) { ming_l[n] += ci[i][j]; } } } for (int i = 0 ; i < 4 ; i++) { int j = 4 -i; while (j != 0 ) { ming_l[i] = left_one (ming_l[i]); j--; } }
逆字节替换
逆字节替换的过程与字节替换的过程完全相同,只不过是将S盒换成了逆S盒。
逆S盒
1 2 3 4 5 6 7 8 9 10 string key_SR (string a) { string a1 = "" ; for (int i = 0 ; i < a.length (); i += 2 ) { a1 += htos (SR[ctode ((a[i]))][ctode ((a[i + 1 ]))]); } return a1; }
轮密钥加
轮密钥加的过程与加密过程中的完全相同,但是对应的密钥与加密过程中相反 ,例如加密过程在最后一轮使用k[40...43],解密过程就需要在第一轮使用k[40...43]。
1 2 3 4 5 6 7 8 void AddRoundKeyR (int n) { int j = n * 4 ; for (int i = 0 ; i < 4 ; i++) { ci[i] = keys_xor (ci[i], k[j]); j++; }
逆向列混淆
逆向列混合变换可由下图的矩阵乘法定义:
逆向列混淆
逆向列混淆完全可以调用与正向列混淆相同的函数 ,因为当时在实现过程中考虑到了"09"、"0B"、"0D"、"0E"的有限域*和+。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 string Mix[4 ] = { "0E0B0D09" , "090E0B0D" , "0D090E0B" , "0B0D090E" }; string ming_3[4 ] = { "" }; if (q == 0 ) { for (int n = 0 ; n < 4 ; n++) { for (int i = 0 ; i < 4 ; i++) { for (int j = n * 2 ; j < n * 2 + 2 ; j++) { ming_3[n] += ming_2[i][j]; } } } } else { for (int i = 0 ; i < 4 ; i++) { for (int j = 0 ; j < 4 ; j++) { ming_3[i] += ming_xor (ming_2[j], Mix[i]); } } }
雪崩效应-改变密钥
实现过程如下:
输入明文和密钥并获得密文,存储得到的密文。
将输入密钥key的每个字符拿出 (4bit),通过将它变为二进制数实现比特位翻转 。
将翻转后的字符替换原来的字符。
重新进行AES加密,将新得到的密文记录。
使用编写函数changenum() 对比新密文和旧密文的比特位变化。
使用变量all记录这128次变化总共的比特位变化,最后除以128得到改变的平均值。
对比新密文和旧密文比特位变化的函数changenum():
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int changenum (string init, string later) { sum = 0 ; for (int i = 0 ; i < init.length (); i++) { string a = htob (init[i]); string b = htob (later[i]); for (int j = 0 ; j < a.length (); j++) { if (a[j] != b[j]) { sum++; } } } all += sum; return sum; }
整体代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 cout << "输入明文:\t" ; cin >> m; cout << "输入密钥:\t" ; cin >> key; key = cin_check (key); Getkeys (key);AES_lock (m);init_m = init_m.append (ming[0 ]).append (ming[1 ]).append (ming[2 ]).append (ming[3 ]); cout << "得到密文:\t" << init_m<<endl; all = 0 ; for (int i = 0 ; i <32 ; i++) { for (int j = 0 ; j < 4 ; j++) { string temp_key = key; cout << "改变第" << 4 *i+j+1 << "位:\t" ; string temp1 = htob (key[i]); if (temp1[j] == '0' ) { temp1[j] = '1' ; } else { temp1[j] = '0' ; } temp_key[i] = btoh (temp1); cout << temp_key ; Getkeys (temp_key); AES_lock (m); string later_m = "" ; later_m=later_m.append (ming[0 ]).append (ming[1 ]).append (ming[2 ]).append (ming[3 ]); cout << "\t得到密文:" << later_m; cout << "\t改变位数:" << changenum (init_m,later_m)<<endl; } } cout << "平均改变位数:\t" << float (all) / 128 << endl;
雪崩效应-改变明文
实现过程与改变密钥的过程大致相同。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 cout << "输入明文:\t" ; cin >> m; string mm = cin_check (m); cout << "输入密钥:\t" ; cin >> key; key = cin_check (key); Getkeys (key);AES_lock (m);init_m = "" ; init_m = init_m.append (ming[0 ]).append (ming[1 ]).append (ming[2 ]).append (ming[3 ]); cout << "得到密文:\t" << init_m << endl; all = 0 ; for (int i = 0 ; i < 32 ; i++) { for (int j = 0 ; j < 4 ; j++) { string temp_m = mm; cout << "改变第" << 4 * i + j + 1 << "位:\t" ; string temp1 = htob (mm[i]); if (temp1[j] == '0' ) { temp1[j] = '1' ; } else { temp1[j] = '0' ; } temp_m[i] = btoh (temp1); cout << temp_m; AES_lock (temp_m); string later_m = "" ; later_m = later_m.append (ming[0 ]).append (ming[1 ]).append (ming[2 ]).append (ming[3 ]); cout << "\t得到密文:" << later_m; cout << "\t改变位数:" << changenum (init_m, later_m) << endl; } } cout << "平均改变位数:\t" << float (all) / 128 << endl;
实验结果
密钥扩展
密钥扩展
AES加密
AES加密
AES解密
AES解密
雪崩效应-改变密钥
将密钥设置为128位0,这样可以能直观的看出翻转这128位的变化。
雪崩效应-改变密钥
雪崩效应-改变密钥
可以看到平均改变位为64.3125位
雪崩效应-改变明文
将明文设置为128位0,这样可以能直观的看出翻转这128位的变化。
雪崩效应-改变明文
雪崩效应-改变明文
可以看到平均改变位为63.6328位
*相关函数
输入检查
如果输入的密钥、明文、密文不足128位,会自动补0。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 string cin_check (string a) { int len = a.length (); if (len > 32 ) { return 0 ; } if (len != 32 ) { for (int i = 0 ; i < 32 - len; i++) { a = "0" + a; } } for (int i = 0 ; i < 32 ; i++) { if (a[i] >= 97 && a[i] <= 122 ) { a[i] -= 32 ; } } return a; }
进制转换
本实验中的进制转换基本都是通过map实现单字节的变换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 string htob (char h) { string b; map<char , string>map_hb; map_hb['0' ] = "0000" ; map_hb['1' ] = "0001" ; map_hb['2' ] = "0010" ; map_hb['3' ] = "0011" ; map_hb['4' ] = "0100" ; map_hb['5' ] = "0101" ; map_hb['6' ] = "0110" ; map_hb['7' ] = "0111" ; map_hb['8' ] = "1000" ; map_hb['9' ] = "1001" ; map_hb['A' ] = "1010" ; map_hb['B' ] = "1011" ; map_hb['C' ] = "1100" ; map_hb['D' ] = "1101" ; map_hb['E' ] = "1110" ; map_hb['F' ] = "1111" ; b = map_hb[h]; return b; }
字符与整型转换
本实验中的字符与整型转换基本都是通过map实现单字节的变换。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 string htos (int a) { int b = a / 16 ; int c = a % 16 ; map<int , string>mp_ic; mp_ic[0 ] = "0" ; mp_ic[1 ] = "1" ; mp_ic[2 ] = "2" ; mp_ic[3 ] = "3" ; mp_ic[4 ] = "4" ; mp_ic[5 ] = "5" ; mp_ic[6 ] = "6" ; mp_ic[7 ] = "7" ; mp_ic[8 ] = "8" ; mp_ic[9 ] = "9" ; mp_ic[10 ] = "A" ; mp_ic[11 ] = "B" ; mp_ic[12 ] = "C" ; mp_ic[13 ] = "D" ; mp_ic[14 ] = "E" ; mp_ic[15 ] = "F" ; string s = "" ; s += mp_ic[b]; s += mp_ic[c]; return s; }