AES-CMAC实验报告 -信息与通信安全

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

本科实验报告
课程名称:信息与通信安全
姓名:
选择课题:C/C++实现加解密算法及其应用(实验一)系:信电系
专业:信息与通信工程
学号:
指导教师:
年月日
目录
1.实验内容与要求 (3)
2.实验原理 (3)
2.1 AES原理 (3)
2.1.1 AES总体结构 (3)
2.1.2 AES详细结构 (4)
1.1CMAC原理 (7)
2.实验环境 (9)
4.代码实现 (9)
4.1字节代替变换 (9)
4.2 行移位变换 (11)
4.3列混淆变换 (14)
4.4 轮密钥加 (17)
4.5 AES的加密与解密的实现 (22)
4.6 CMAC代码实现 (23)
5.思考题 (27)
6.心得体会 (27)
附录(完整程序) (28)
1.实验内容与要求
(1). 复习AES原理。

(2). 用C/C++编写AES算法并调试通过。

(3). 复习CMAC原理。

(4). 在实现AES基础上,用C/C++编写CMAC算法并调试通过。

(5). 回答下列思考题。

➢AES解密算法和AES的逆算法之间有什么不同?
➢CMAC与HMAC相比,有什么优点?
2.实验原理
2.1 AES原理
2.1.1 AES总体结构
下图展示了AES加密过程的总体结构。

明文分组长度为128位即16字节密钥长度可以为16,24或32字节(在本次设计中选择16字节)。

加密和解密算法是输入是一个128位的分组。

在FIPS PUB 197中,这个分组被描述为4*4的字节方阵。

这个分组被复制到state数组,饼子啊加密或解密的各个阶段被修改。

同样,密钥也被描述为字节的方阵,并被扩展为44字的密钥字序列。

密码由N轮组成,其中轮数
依赖于密钥长度,16字节密钥是
10轮,前N-1轮由4个不同的变
换组成:字节代替,行位移,列
混淆,轮密钥加。

最后一轮包含
三个变换,而在第一轮的前面有
一个起始的单变换(轮密钥加),
可视为第0轮。

每一个变换输入
一个活多个4*4的矩阵,并输出
一个4*4的矩阵,最后一轮输出
为密文。

同样,密钥扩展函数为
N+1轮密钥,它们是互不相同的
4*4矩阵。

每一个轮密钥作为每
轮的轮密钥加变换的一种输入。

2.1.2 AES详细结构
1)AES算法未使用Feisel
结构,而是在每一轮都使
用代替和混淆将整个数据
分组作为一个单一的矩阵
处理。

2)输入的密钥被扩展为44
个32位字所组成的数组
w[i],每轮由四个不同的字
作为该轮的轮密钥。

3)由四个不同的阶段组成,
包括一个置换和三个代替。

字节代替:
用一个S盒完成分组的字
节到字节的代替。

行位移:
一个简单的置换。

列混淆:
利用GF(2^8)上的算术特
性的一个代替。

轮密钥加:
当前分组和扩展秘钥的一
部分进行按位异或。

(一)字节代替变换
如上图,字节代替变换是一个简单的查表操作。

AES定义了一个S盒,它由16*16个字节组成的矩阵,包含了8位所能表示的256个数的一个置换。

State中的每个字节按照如下的方式映射为一个新的字节,把改字节的搞4位作为行值,低四位作为列值,以这些行列值作为索引从S盒的对应位置取出元素作为输出。

而其逆向操作则是有对应的逆S盒可进行查表。

(二)行移位变换
如上图,其正向行移位变换中,state的第一行保持不变,把state的第二行循环左移一个字节,state的第三行循环左移两个字节,state的第四行循环左移四个字节。

例如:
逆向行移位变换将state中的后三行执行相反方向的一位操作即可。

(三)列混淆变换
如上图,列混淆的正向变换是对每列单独进行操作。

每列中的每个字节被映射为一个新值,此值由该列中的4个字通过函数变换得到。

这个变换可有下面基于state 的矩阵乘法表示:
乘积矩阵中的每个元素军事一行和一列中对应元素的乘积之和。

这里的乘法和假发都是定义在GF(2^n)上的。

状态中单列的列混淆变换可表示为:
其中一个例子如下:
(四)轮密钥加变换
如上图,在轮密钥加变换中,128位的state按位与128位的轮秘钥XOR。

该操作可以视为state的一列中的四个字节与轮秘钥的一个字进行列间的操作,例如:
(五)AES密钥扩展
AES密钥扩展算法的输入值是一个4个字,输出值是一个由44个字组成的移位线性数组。

输入密钥字节被复制到扩展密钥数组的前4个字。

然后每次用四个字填充扩展密钥数组余下的部分。

在扩展数组中,每一个新增的字w[i]的值依赖于w[i-1]和w[i-4]。

在4
个情形中,三个使用了异或。

对w数组中下标为4的倍数的的元素采用了更复杂的函数来计算:
(1)字循环的功能时使一个字中的四个字节的循环左移一个字节,即将输入字[B0,B1,B2,B3]变成为[B1,B2,B3,B0].
(2)字代替利用S盒对输入字中的每个字节进行字节代替。

(3)步骤1和步骤2的结果再与轮常量Rcon[j]相异或。

1.1CMAC原理
基于密码的消息认证码(CMAC)对于AES,3DES适用,它使用三个密钥:一个密钥长为K,用在密文分组链接的每一步,两个长度为n的密钥,其中k是密钥长度,n为密文分组长度。

并且两个n位的密钥可以从加密密钥导出,而不是单独提供。

首先,当消息长度是分组长度b的n倍时,我们考虑CMAC的运算情况。

对AES,b=128,对于3DES,b=64.这个消息被划分为n组,(M1,M2…,Mn)。

算法使用了k比特的加密密钥K和n比特的常数K1。

对于AES,密钥长度k为128,192和256比特,对于3DES,密钥长度为112或168比特。

CMAC按如下方式计算:
)
(])[,(.
..])[,(])[,(),(1123312211n Tlen n N n C MSB T K C M K E C C M K E C C M K E C M K E C =⊕⊕=⊕=⊕==-
其中:
T ——消息认证码,也称为tag Tlen ——T 的比特长度
MSB s (X )——比特串X 最左边的s 位
如果消息不是密文分组长度的整数倍,则最后分组的右边(低有效位)填充一个1和若干个0,使得最后的分组长度为b 。

除了使用一个不同的n 比特密钥K 2代替K 1外,与前面所述一样进行CMAC 运算。

两个n 比特的密钥由k 比特的加密密钥按如下方式导出:
x
x L X L K x
L K K E L n ••=•=•==)()0,(221
其中乘法(·)是在域GF (2^n )内进行,X 和X^2是该域的一次和二次多项式。

因此X 的二元表示为n -2个0,后跟10,X^2的二元表示为n -3个0,后跟100。

对于AES ,已获批准的分组长度为X^128 +X^7 +X^2 +X+2.。

当消息长度是分组长度的整数倍:
当消息长度不是分组长度的整数倍:
2.实验环境
装有C_Free 的笔记本
4.代码实现
首先是实现AES的各个子模块,在验证其正确以后,再进行CMAC的认证算法的实现。

4.1字节代替变换
#include<stdio.h>
static unsigned char Sbox[256] =
{//AES的S盒
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 };
static unsigned char InvSbox[256] =
{// AES的逆S盒
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d };
//字节代替变换
void Sub_Byte (unsigned char state[4][4])
{
int i,j;
for(i=0;i<4;i++)
for(j=0;j<4;j++)
state[i][j]=Sbox[state[i][j]];
}
//逆向字节代替
void Inv_Sub_Byte (unsigned char state[4][4])
{
int i,j;
for(i=0;i<4;i++)
for(j=0;j<4;j++)
state[i][j]=InvSbox[state[i][j]];
}
int main()
{
unsigned char state[4][4]={
{0xea,0x04,0x65,0x85},
{0x83,0x45,0x5d,0x96},
{0x5c,0x33,0x98,0xb0},
{0xf0,0x2d,0xad,0xc5}
};
int i,j;
//正向替换
Sub_Byte(state);
printf("正向代替结果:\n") ;
for(i=0;i<4;i++)
{
for(j=0;j<4;j++)
printf("%x ",state[i][j]);
printf("\n");
}
printf("\n");
//反向替换
Inv_Sub_Byte(state);
printf("反向代替结果:\n") ;
for(i=0;i<4;i++)
{
for(j=0;j<4;j++)
printf("%x ",state[i][j]);
printf("\n");
}
}
其测试结果如下,可见该功能可实现是正确的。

4.2 行移位变换
#include<stdio.h>
//行移位
void Row_Shift(unsigned char state[4][4])
{
int i;
unsigned char temp;
//第一行保持不变,第二行左移一位temp=state[1][0];
for(i=0;i<3;i++)
state[1][i]=state[1][i+1];
state[1][3]=temp;
//第三行左移2位
temp=state[2][0];
state[2][0]=state[2][2];
state[2][2]=temp;
temp=state[2][1];
state[2][1]=state[2][3];
state[2][3]=temp;
//第四行左移3位
temp=state[3][3];
for(i=3;i>0;i--)
state[3][i]=state[3][i-1];
state[3][0]=temp;
}
//逆向行移位
void Inv_Row_Shift(unsigned char state[4][4]) {
int i;
unsigned char temp;
//第一行保持不变,第二行右移一位temp=state[1][3];
for(i=3;i>0;i--)
state[1][i]=state[1][i-1];
state[1][0]=temp;
//第三行右移二位
temp=state[2][0];
state[2][0]=state[2][2];
state[2][2]=temp;
temp=state[2][1];
state[2][1]=state[2][3];
state[2][3]=temp;
//第四行右移三位
temp=state[3][0];
for(i=0;i<3;i++)
state[3][i]=state[3][i+1];
state[3][3]=temp;
}
int main()
{
int i,j;
unsigned char state[4][4]={
{0x87,0xf2,0x4d,0x97},
{0xec,0x6e,0x4c,0x90},
{0x4a,0xc3,0x46,0xe7},
{0x8c,0xd8,0x95,0xa6}
};
Row_Shift(state);
printf("正向行移位变换:\n") ;
for(i=0;i<4;i++)
{
for(j=0;j<4;j++)
printf("%02x ",state[i][j]);
printf("\n");
}
printf("\n");
Inv_Row_Shift(state);
printf("反向向行移位变换:\n") ;
for(i=0;i<4;i++)
{
for(j=0;j<4;j++)
printf("%02x ",state[i][j]);
printf("\n");
}
}
测试结果如下
通过比较,可知结果正确,因此该子模块实现。

4.3列混淆变换
#include<stdio.h>
//乘法处理部分
unsigned char x_time2(unsigned char state)//乘2处理{
unsigned char temp;
if (state>=0x80)
temp=(state<<1)^0x1b; //判断b7=1?
else
temp=(state<<1);
return temp;
}
unsigned char x_time3(unsigned char state)//乘3处理{
state=state^x_time2(state);
return state;
}
unsigned char x_time4(unsigned char state) //乘4处理{
state=x_time2(x_time2(state));
return state;
}
unsigned char x_time8(unsigned char state) //乘8处理
state=x_time2(x_time2(x_time2(state)));
return state;
}
unsigned char x_time9(unsigned char state)//乘9处理{
state=state^x_time8(state);
return state;
}
unsigned char x_timeB(unsigned char state)//乘B处理{
state=state^x_time2(state)^x_time8(state);
return state;
}
unsigned char x_timeD(unsigned char state)//乘D处理{
state=state^x_time4(state)^x_time8(state);
return state;
}
unsigned char x_timeE(unsigned char state)//乘E处理{
state=x_time2(state)^x_time4(state)^x_time8(state);
return state;
}
void MixColumns(unsigned char state[4][4])
{
int i,j;
unsigned char state_1[4][4];
for(i=0;i<4;i++)
for(j=0;j<4;j++)
state_1[i][j]=state[i][j];
for(j=0;j<4;j++)
state[0][j]=x_time2(state_1[0][j])^x_time3(state_1[1][j])^state_1[2][j]^state_1[3][j];
state[1][j]=state_1[0][j]^x_time2(state_1[1][j])^x_time3(state_1[2][j])^state_1[3][j];
state[2][j]=state_1[0][j]^state_1[1][j]^x_time2(state_1[2][j])^x_time3(state_1[3][j]);
state[3][j]=x_time3(state_1[0][j])^state_1[1][j]^state_1[2][j]^x_time2(state_1[3][j]);
}
}
//逆向列混淆
void Inv_MixColumns(unsigned char state[4][4])
{
int i,j;
unsigned char state_1[4][4];
for(i=0;i<4;i++)
for(j=0;j<4;j++)
state_1[i][j]=state[i][j];
for(j=0;j<4;j++)
{
state[0][j]=x_timeE(state_1[0][j])^x_timeB(state_1[1][j])^x_timeD(state_1[2][j])^x_time9(state_1 [3][j]);
state[1][j]=x_time9(state_1[0][j])^x_timeE(state_1[1][j])^x_timeB(state_1[2][j])^x_timeD(state_1 [3][j]);
state[2][j]=x_timeD(state_1[0][j])^x_time9(state_1[1][j])^x_timeE(state_1[2][j])^x_timeB(state_1 [3][j]);
state[3][j]=x_timeB(state_1[0][j])^x_timeD(state_1[1][j])^x_time9(state_1[2][j])^x_timeE(state_1 [3][j]);
}
}
int main()
{
int i,j;
unsigned char state[4][4]={
{0x87,0xf2,0x4d,0x97},
{0x6e,0x4c,0x90,0xec},
{0x46,0xe7,0x4a,0xc3},
{0xa6,0x8c,0xd8,0x95}
};
MixColumns(state);
printf("正向列混淆结果:\n");
for(i=0;i<4;i++)
{
for(j=0;j<4;j++)
printf("%x ",state[i][j]);
printf("\n");
}
printf("\n");
Inv_MixColumns(state);
printf("逆向列混淆结果:\n");
for(i=0;i<4;i++)
{
for(j=0;j<4;j++)
printf("%x ",state[i][j]);
printf("\n");
}
}
其测试结果如下:
通过比较,可知结果正确,因此该子模块实现。

4.4 轮密钥加
#include <string.h>
static unsigned char key[16];
static unsigned char ex_key[176];
static unsigned char round_key[11][16];
static unsigned char Sbox[256] =
{//AES的S盒
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 };
//秘钥扩展
void key_Expansion (unsigned char key[16])
{
int i,j,k;
unsigned char temp[4]; //用于寄存w[i-1]
unsigned char t;
unsigned char RC[10]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36};
unsigned char Rcon[10][4]={{0x01,0x0,0x0,0x0},
{0x02,0x0,0x0,0x0},
{0x04,0x0,0x0,0x0},
{0x08,0x0,0x0,0x0},
{0x10,0x0,0x0,0x0},
{0x20,0x0,0x0,0x0},
{0x40,0x0,0x0,0x0},
{0x80,0x0,0x0,0x0},
{0x1b,0x0,0x0,0x0},
{0x36,0x0,0x0,0x0},
}; //轮常量
for(i=0;i<16;i++) //将输入秘钥复制到扩展秘钥数组的前四个字
{
ex_key[i]=key[i];
}
for(i=16;i<176;i+=4) //秘钥扩展
{
for(j=0;j<4;j++)
temp[j]=ex_key[i-4+j] ; //用于寄存w[i-1]
if(i%16==0) //对于w[i]中下标为的4的倍数要使用函数g来计算{
//循环左移
t=temp[0];
for(k=0;k<3;k++)
temp[k]=temp[k+1];
temp[3]=t;
//字代替
for(k=0;k<4;k++)
temp[k]=Sbox[temp[k]];
//与轮常量相异或
for(k=0;k<4;k++)
temp[k]=temp[k]^Rcon[i/16-1][k];}
// 得到最后的w[i],w[i]=w[i-1]^w[i-4]
for(k=i;k<i+4;k++)
ex_key[k]=ex_key[k-16]^temp[k-i];
}
}
//第k轮的轮密钥加
void RoundKey(unsigned char state[4][4],int k)
{
int i,j;
for(i=0;i<16;i++)
round_key[k][i]=ex_key[16*k+i];//第k轮的轮密钥生成
for(i=0;i<4;i++)
for(j=0;j<4;j++)
state[i][j]=state[i][j]^round_key[k][4*j+i];
}
int main()
{
unsigned char key[16]={0x0f,0x15,0x71,0xc9,
0x47,0xd9,0xe8,0x59,
0x0c,0xb7,0xad,0xd6,
0xaf,0x7f,0x67,0x98};
unsigned char state[4][4]={
{0xb9,0x94,0x57,0x75},
{0xe4,0x8e,0x16,0x51},
{0x47,0x20,0x9a,0x3f},
{0xc5,0xd6,0xf5,0x3b}
};
int i,j;
key_Expansion(key);
for(i=0;i<11;i++)
{
for(j=0;j<16;j++)
round_key[i][j]=ex_key[16*i+j];//第k轮的轮密钥生成
}
for(i=0;i<11;i++)
{
printf("第%d轮密钥:\n",i);
for(j=0;j<16;j++)
{
printf("%02x ",round_key[i][j]);
if((j+1)%4==0)
printf("\n");
}
printf("\n");
}
RoundKey(state,1);
printf("第一轮结束后的state输出:\n");
for(i=0;i<4;i++)
{
for(j=0;j<4;j++)
printf("%02x ",state[i][j]);
printf("\n");
}
return 0;
}
扩展密钥数组如下:
经过比对,可知,密钥扩展是正确的。

我又选取了第一轮的轮密钥加:
测试结果如下:
经过比对,可知道轮密钥加的结果是正确的。

4.5 AES的加密与解密的实现
(程序请见附录)
参照书上给的例子:
Plaintext[16]={
0x01,0x23,0x45,0x67,
0x89,0xab,0xcd,0xef,
0xfe.0xdc,0xba,0x98,
0x76,0x54,0x32,0x10
}
key[16]={
0x0f,0x15,0x71,0xc9,
0x47,0xd9,0xe8,0x59,
0x0c,0xb7,0xad,0xd6,
0xaf,0x7f,0x67,0x98
};
其理论结果为:
测试结果如下,经过比对可知结果正确。

4.6 CMAC代码实现
//先用k=128的加密秘钥key产生两个n位的秘钥key_1,key_2
void k1_k2(unsigned char key[16],unsigned char key_1[16],unsigned char key_2[16])
{
unsigned char L[16];
unsigned char zero_n[16]={0x00};
int i;
Encryption(zero_n,key,L);//即L=E(K,0^n)
//计算k1=L*x,其中多项式为x^128+x^7+x^2+x+1
if(L[0]>=0x80) //判断最高位是否是1,若是,则需要约化,即左移一位后,再异或1000 0111=0x87
{
for(i=0;i<16;i++)
key_1[i]=L[i]<<1;//左移一位
key_1[15]=key_1[15]^0x87;
}
else
{
for(i=0;i<16;i++)
key_1[i]=L[i]<<1;
}
//产生key_2=L*x^2=key_1*x
if(key_1[0]>=0x80)
{
for(i=0;i<16;i++)
key_2[i]=key_1[i]<<1;//左移一位
key_2[15]=key_2[15]^0x87;
}
else
{
for(i=0;i<16;i++)
key_2[i]=key_1[i]<<1;
}
}
//对消息不是密文分组的整数倍,要在分组最后右边填充1及若干个0
//*last_text是最后一个不完整分组,length是最后一组的长度,out_text是填充后的输出void fill(unsigned char *last_text,int length, unsigned char out_text[16])
{
int i;
for(i=0;i<16;i++)
{
if(i<length) //将原来的数填充到高位
out_text[i]=last_text[i];
else if(i==length)
out_text[i]=0x80;
else
out_text[i]=0x00;
}
}
void AES_CMAC(unsigned char Mass[],unsigned char key[16],int length, unsigned char mac[]) {
int i,j,n,last_l;
int flag=0; //用于标记密文分组是否完整
unsigned char M_C1[16]={0x00},M_C2[16], M_C_K[16]; //用于存放中间数据
unsigned char key_1[16],key_2[16]; //分组密钥
unsigned char last[16],Cn[16];
n=(length+15)/16;//用于确定分几组
last_l=length%16;
k1_k2(key,key_1,key_2);
if(last_l==0)
flag=1; //表明是分组长度b=128的整数倍
if(flag==0)
fill(&Mass[16*(n-1)],last_l,last);
//对前n-1轮
for(i=0;i<n-1;i++)
{
for (j=0;j<16;j++)
M_C2[j]=M_C1[j]^Mass[16*i+j];
Encryption(M_C2,key,M_C1);
}
if(flag==1)
{
for(i=0;i<16;i++)
M_C_K[i]=Mass[16*(n-1)+i]^M_C1[i]^key_1[i];
Encryption(M_C_K,key,Cn);
}
else
{
for(i=0;i<16;i++)
M_C_K[i]=last[i]^M_C1[i]^key_2[i];
Encryption(M_C_K,key,Cn);
}
for(i=0;i<16;i++)
mac[i]=Cn[i];
}
int main()
{
unsigned char key[16],Mass[64],T[16];
int i,j,length;
printf("请输入消息(64byte):\n");//输入消息
for(i=0;i<64;i++)
scanf("%x",&Mass[i]);
printf("请输入密钥:\n");//输入密钥
for(i=0;i<16;i++)
scanf("%x",&key[i]);
printf("需要使用的次数repeat=:\n");
scanf("%d",&repeat);
for(i=0;i<repeat;i++)
{
printf("请输入不同的密文分组长度length:\n");
scanf("%d",&length);
printf("AES-CMAC输出结果:\n");
AES_CMAC(Mass,key,length,T);
for(i=0;i<16;i++)
printf("%x ", T[i]);
printf("\n");
}
}
测试结果如下:
根据查阅的资料,可知测试结果是正确的。

且从上述结果可知,对于不同的分组长度,输出结果相差很大。

5.思考题
1.AES解密算法和AES的逆算法之间有什么不同?
AES解密算法与其逆算法不同点在于解密算法对密文是先进行轮密钥加(对AES-128而言其扩展密钥是w[40,43])然后开始第一轮的逆向求解,而逆算法时对加密算法的求逆,即将第十轮的算法倒推回去,所以这里有区别。

这是由AES特定结构所决定的。

2.CMAC与HMAC相比,有什么优点?
CMAC使用分组密码算法,而HMAC使用散列函数。

因为HMAC是基于其所应用的hash 函数的,若hash函数的结构存在缺陷,则HMAC的安全性就会大大降低。

而CMAC的安全性采用的是分组密码算法,其安全性在于密钥的长度。

CMAC的长度可以通过Tlen的变化而变化,输出位数更加灵活。

6.心得体会
首先,刚开始由于这方面知识掌握不是很好,在看完书后又查阅了大量资料,包括一些程序。

在这样的一个基础上,对整个编程的思路有了一些认识,按照书本上的AES加密顺序,最先做的就是密钥扩展和轮密钥加。

这个部分也是难度最大的部分,主要是输入的都是字节大小的,但密钥扩展后要求是以字为单位输出的。

刚开始这个方面纠结了很久,最后为了整个程序比较清晰,还是决定全都以字节为单位,经过一定转换来得到相当于字的效果。

在完成密钥扩展且测试正确后,就开始按顺序设计,字节代替,行移位,列混淆。

其中3盒和逆S盒是直接从网上下载的,其他的就是按照书上给的公式和流程图来完成,还是比较顺利的。

其解密算法和加密算法也是大同小异的,只是在每个函数中修改一下相应的代码即可。

在加密和解密算法都写完后,就是写main函数进行测试,刚开始发现结果总是不对,经过较长时间的查错,发现还是错在轮密钥加中数组转置引起的问题。

在AES设计万仇就是设计CMAC。

关于这个程序书上涉及的也较少,但算法还是比较简单的,主要涉及产生两个子密钥和对不完整的分组填充。

由于书上没有现有的例子可以参考,因此,我是参照其他同学的例子,进行比较,验证结果是正确的。

附录(完整程序)
#include<stdio.h>
#include<math.h>
#include<string.h>
#include<stdlib.h>
static unsigned char key[16];//输入秘钥
static unsigned char ex_key[176]; //扩展密钥,其中每四个拼成的就是一个字的秘钥数组
//即w[i]=(ex_key[4*i],ex_key[4*i+1],ex_key[4*i+2],ex_key[4*i+3])
static unsigned char round_key[11][16]; //轮密钥
static unsigned char Sbox[256] =
{//AES的S盒
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 };
static unsigned char InvSbox[256] =
{// AES的逆S盒
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d };
//乘法处理部分
unsigned char x_time2(unsigned char state)//乘2处理
{
unsigned char temp;
if (state>=0x80)
temp=(state<<1)^0x1b;//判断b7=1?
else
temp=(state<<1);
return temp;
}
unsigned char x_time3(unsigned char state)//乘3处理
{
state=state^x_time2(state);
return state;
}
unsigned char x_time4(unsigned char state)//乘4处理
{
state=x_time2(x_time2(state));
return state;
}
unsigned char x_time8(unsigned char state)//乘8处理
{
state=x_time2(x_time2(x_time2(state)));
return state;
}
unsigned char x_time9(unsigned char state)//乘9处理
{
state=state^x_time8(state);
return state;
}
unsigned char x_timeB(unsigned char state)//乘B处理
{
state=state^x_time2(state)^x_time8(state);
return state;
}
unsigned char x_timeD(unsigned char state)//乘D处理
{
state=state^x_time4(state)^x_time8(state);
return state;
}
unsigned char x_timeE(unsigned char state)//乘E处理
{
state=x_time2(state)^x_time4(state)^x_time8(state);
return state;
}
//秘钥扩展
void key_Expansion (unsigned char key[16])
{
int i,j,k;
unsigned char temp[4]; //用于寄存w[i-1]
unsigned char t;
unsigned char RC[10]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x1b,0x36};
unsigned char Rcon[10][4]={{0x01,0x0,0x0,0x0},
{0x02,0x0,0x0,0x0},
{0x04,0x0,0x0,0x0},
{0x08,0x0,0x0,0x0},
{0x10,0x0,0x0,0x0},
{0x20,0x0,0x0,0x0},
{0x40,0x0,0x0,0x0},
{0x80,0x0,0x0,0x0},
{0x1b,0x0,0x0,0x0},
{0x36,0x0,0x0,0x0},
}; //轮常量
for(i=0;i<16;i++) //将输入秘钥复制到扩展秘钥数组的前四个字{
ex_key[i]=key[i];
}
for(i=16;i<176;i+=4)//秘钥扩展
{
for(j=0;j<4;j++)
temp[j]=ex_key[i-4+j] ;//用于寄存w[i-1]
if(i%16==0) //对于w[i]中下标为的4的倍数要使用函数g来计算{
//循环左移
t=temp[0];
for(k=0;k<3;k++)
temp[k]=temp[k+1];
temp[3]=t;
//字代替
for(k=0;k<4;k++)
temp[k]=Sbox[temp[k]];
//与轮常量相异或
for(k=0;k<4;k++)
temp[k]=temp[k]^Rcon[i/16-1][k];}
// 得到最后的w[i],w[i]=w[i-1]^w[i-4]
for(k=i;k<i+4;k++)
ex_key[k]=ex_key[k-16]^temp[k-i];
}
}
//第k轮的轮密钥加
void RoundKey(unsigned char state[4][4],int k)
{
int i,j;
for(i=0;i<16;i++)
round_key[k][i]=ex_key[16*k+i];//第k轮的轮密钥生成
for(i=0;i<4;i++)
for(j=0;j<4;j++)
state[i][j]=state[i][j]^round_key[k][4*j+i];
}
//字节代替变换
void Sub_Byte (unsigned char state[4][4])
{
int i,j;
for(i=0;i<4;i++)
for(j=0;j<4;j++)
state[i][j]=Sbox[state[i][j]];
}
//行移位
void Row_Shift(unsigned char state[4][4])
{
int i;
unsigned char temp;
//第一行保持不变,第二行左移一位
temp=state[1][0];
for(i=0;i<3;i++)
state[1][i]=state[1][i+1];
state[1][3]=temp;
//第三行左移二位
temp=state[2][0];
state[2][0]=state[2][2];
state[2][2]=temp;
temp=state[2][1];
state[2][1]=state[2][3];
state[2][3]=temp;
//第四行左移三位,即右移一位
temp=state[3][3];
for(i=3;i>0;i--)
state[3][i]=state[3][i-1];
state[3][0]=temp;
}
//列混淆变换
void MixColumns(unsigned char state[4][4])
{
int i,j;
unsigned char state_1[4][4];
for(i=0;i<4;i++)
for(j=0;j<4;j++)
state_1[i][j]=state[i][j];
for(j=0;j<4;j++)
{
state[0][j]=x_time2(state_1[0][j])^x_time3(state_1[1][j])^state_1[2][j]^state_1[3][j];
state[1][j]=state_1[0][j]^x_time2(state_1[1][j])^x_time3(state_1[2][j])^state_1[3][j];
state[2][j]=state_1[0][j]^state_1[1][j]^x_time2(state_1[2][j])^x_time3(state_1[3][j]);
state[3][j]=x_time3(state_1[0][j])^state_1[1][j]^state_1[2][j]^x_time2(state_1[3][j]);
}
}
//前9轮每轮有4个步骤
void Round_4(unsigned char state[4][4], int k)
{
Sub_Byte(state);
Row_Shift(state);
MixColumns(state);
RoundKey(state,k);
}
//第10轮有3个步骤
void Round_3(unsigned char state[4][4])
{
Sub_Byte(state);
Row_Shift(state);
RoundKey(state,10);
}
//加密算法,明文Plaintext,密文CipherText,密钥key
void Encryption(unsigned char Plaintext[16], unsigned char key[16], unsigned char CipherText[16])
{
int i,j;
unsigned char state[4][4];
for(i=0;i<4;i++)
for(j=0;j<4;j++)
state[j][i]=Plaintext[4*i+j];
key_Expansion(key);//密钥扩展
//初始轮秘钥加
RoundKey(state,0);
//1~10轮
for(i=1;i<10;i++)
Round_4(state,i);
Round_3(state);
//产生密文
for(i=0;i<4;i++)
for(j=0;j<4;j++)
CipherText[4*i+j]=state[j][i];
}
/*////////////////////////////解密////////////////////////////////*/
//逆向行移位
void Inv_Row_Shift(unsigned char state[4][4])
{
int i;
unsigned char temp;
//第一行保持不变,第二行右移一位
temp=state[1][3];
for(i=3;i>0;i--)
state[1][i]=state[1][i-1];
state[1][0]=temp;
//第三行右移2位
temp=state[2][0];
state[2][0]=state[2][2];
state[2][2]=temp;
temp=state[2][1];
state[2][1]=state[2][3];
state[2][3]=temp;
//第四行右移三位,即左移1位
temp=state[3][0];
for(i=0;i<3;i++)
state[3][i]=state[3][i+1];
state[3][3]=temp;
}
//逆向字节代替
void Inv_Sub_Byte (unsigned char state[4][4])
{
int i,j;
for(i=0;i<4;i++)
for(j=0;j<4;j++)
state[i][j]=InvSbox[state[i][j]];
}
//逆向列混淆
void Inv_MixColumns(unsigned char state[4][4])
{
int i,j;
unsigned char state_1[4][4];
for(i=0;i<4;i++)
for(j=0;j<4;j++)
state_1[i][j]=state[i][j];
for(j=0;j<4;j++)
{
state[0][j]=x_timeE(state_1[0][j])^x_timeB(state_1[1][j])^x_timeD(state_1[2][j])^x_time9(state_1 [3][j]);
state[1][j]=x_time9(state_1[0][j])^x_timeE(state_1[1][j])^x_timeB(state_1[2][j])^x_timeD(state_1 [3][j]);
state[2][j]=x_timeD(state_1[0][j])^x_time9(state_1[1][j])^x_timeE(state_1[2][j])^x_timeB(state_1 [3][j]);
state[3][j]=x_timeB(state_1[0][j])^x_timeD(state_1[1][j])^x_time9(state_1[2][j])^x_timeE(state_1 [3][j]);。

相关文档
最新文档