新的高级加密标准AES保持你的数据安全.docx
- 文档编号:23501387
- 上传时间:2023-05-17
- 格式:DOCX
- 页数:29
- 大小:86.96KB
新的高级加密标准AES保持你的数据安全.docx
《新的高级加密标准AES保持你的数据安全.docx》由会员分享,可在线阅读,更多相关《新的高级加密标准AES保持你的数据安全.docx(29页珍藏版)》请在冰豆网上搜索。
新的高级加密标准AES保持你的数据安全
新的高级加密标准(AES)保持你的数据安全1222498939
2009-4-2915:
54:
04 收藏 | 打印 | 投票(3) | 评论(0) | 阅读(3458) ◇字体:
[大 中 小]
摘要
AES(TheAdvancedEncryptionStandard)是美国国家标准与技术研究所用于加密电子数据的规范。
它被预期能成为人们公认的加密包括金融、电信和政府数字信息的方法。
本文展示了AES的概貌并解析了它使用的算法。
包括一个完整的C#实现和加密.NET数据的举例。
在读完本文后你将能用AES加密、测试基于AES的软件并能在你的系统中使用AES加密。
美国国家标准与技术研究所(NIST)在2002年5月26日建立了新的高级数据加密标准(AES)规范。
本文中我将提供一个用C#编写的的能运行的AES实现,并详细解释到底什么是AES以及编码是如何工作的。
我将向您展示如何用AES加密数据并扩展本文给出的代码来开发一个商业级质量的AES类。
我还将解释怎样把AES结合到你的软件系统中去和为什么要这么做,以及如何测试基于AES的软件。
注意本文提供的代码和基于本文的任何其它的实现都在联邦加密模块出口控制的适用范围之内(详情请参看CommercialEncryptionExportControls)。
AES是一个新的可以用于保护电子数据的加密算法。
明确地说,AES是一个迭代的、对称密钥分组的密码,它可以使用128、192和256位密钥,并且用128位(16字节)分组加密和解密数据。
与公共密钥密码使用密钥对不同,对称密钥密码使用相同的密钥加密和解密数据。
通过分组密码返回的加密数据的位数与输入数据相同。
迭代加密使用一个循环结构,在该循环中重复置换(permutations)和替换(substitutions)输入数据。
Figure1显示了AES用192位密钥对一个16位字节数据块进行加密和解密的情形。
Figure1部分数据
AES算法概述
AES算法是基于置换和代替的。
置换是数据的重新排列,而代替是用一个单元数据替换另一个。
AES使用了几种不同的技术来实现置换和替换。
为了阐明这些技术,让我们用Figure1所示的数据讨论一个具体的AES加密例子。
下面是你要加密的128位值以及它们对应的索引数组:
00112233445566778899aabbccddeeff
0123456789101112131415
192位密钥的值是:
000102030405060708090a0b0c0d0e0f1011121314151617
01234567891011121314151617181920212223
Figure2S-盒(Sbox)
当AES的构造函数(constructor)被调用时,用于加密方法的两个表被初始化。
第一个表是代替盒称为S-盒。
它是一个16×16的矩阵。
S-盒的前五行和前五列如Figure2所示。
在幕后,加密例程获取该密钥数组并用它来生成一个名为w[]的密钥调度表,Figure3所示。
Figure3密钥调度表(KeySched)
w[]最初的Nk(6)行被作为种子,用原始密钥值(0x00到0x17)。
剩余行从种子密钥来产生。
变量Nk代表以32位字为单位的种子密钥长度。
稍后我分析AES实现时你将清楚地看到w[]是怎样产生的。
关键是这里现在有许多密钥使用而不只是一个。
这些新的密钥被称为轮密钥(roundkeys)以将它们与原始种子密钥区别开来。
Figure4State(态)数组
AES加密例程开始是拷贝16字节的输入数组到一个名为 State(态)的4×4字节矩阵中。
(参见Figure4)。
AES加密算法取名为Cipher,它操作State[],其过程描述的伪代码参见Figure5。
在规范中,加密算法实现的一个预备的处理步骤被称为AddRoundKey(轮密钥加)。
AddRoundKey用密钥调度表中的前四行对State矩阵实行一个字节一个字节的异或(XOR)操作,并用轮密钥表w[c,r]异或输入State[r,c]。
举个例子,如果State矩阵的第一行保存的字节是{00,44,88,cc},第一列密钥调度表是{00,04,08,0c},那么新的State[0,2]值是用w[2,0](0x08或0x80)异或State[0,2](0x88)的结果:
10001000
00001000XOR
10000000
AES算法的主循环对State矩阵执行四个不同的操作,在规范中被称为SubBytes(字节替换)、ShiftRows(行位移变换)、MixColumns(列混合变换)和AddRoundKey。
除了每次循环AddRoundKey都被调用并使用密钥调度表的下面四行外,AddRoundKey与预备处理步骤中的AddRoundKey相同。
SubBytes例程是一个代替操作,它将State矩阵中的每个字节替换成一个由Sbox决定的新字节。
比如,如果State[0,1]的值是0x40如果你想找到它的代替者,你取State[0,1]的值(0x40)并让x等于左边的数字(4)并让y等于右边的数字(0)。
然后你用x和y作为索引进到Sbox表中寻找代替值,如Figure2所示。
ShiftRows是一个置换操作,它将State矩阵中的字节向左旋转。
Figure6示范了ShiftRows如何操作State[]。
State的第0行被向左旋转0个位置,State的第1行被向左旋转1个位置,State的第2行被向左旋转2个位置,而State的第3行被向左旋转3个位置。
Figure6对State进行ShiftRows操作
MixColumns是一个代替操作,它是理解AES算法时最具技巧(或者说是最需要动脑筋的部分)的部分。
它用State字节列的值进行数学域加和域乘的结果代替每个字节。
我将在下一节中详细解释专门的域加和域乘细节。
假设State[0,1]的值是0x09,并且列1上的其它值分别为0x60,0xe1和0x04,那么State[0,1]的新值计算如下:
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
此处加法和乘法是专门的数学域操作,而不是平常整数的加法和乘法。
SubBytes、ShiftRows、MixColumns和AddRoundKey四个操作在一个执行Nr次的循环里被调用,Nr为给定密钥大小的轮数减1。
加密算法使用的轮数要么是10,12,要么是14,这依赖于种子密钥长度是128位、192位还是256位。
在这个例子中,因为Nr等于12,则这四个操作被调用11次。
该迭代完成后,在拷贝State矩阵到输出参数前,加密算法调用SubBytes、ShiftRows和AddRoundKey后结束。
大致说来,AES加密算法的核心有四个操作。
AddRoundKey使用从种子密钥值中生成的轮密钥代替4组字节。
SubBytes替换用一个代替表替换单个字节。
ShiftRows通过旋转4字节行的4组字节进行序列置换。
MixColumns用域加和域乘的组合来替换字节。
有限域GF(28)的加法和乘法
正如你所看到的,AES加密算法使用相当简单明了的技术来代替和置换,除MixColumns例程以外。
MixColumns使用特殊的加法和乘法。
AES所用的加法和乘法是基于数学(译者注:
近世代数)的域论。
尤其是AES基于有限域GF(28)。
GF(28)由一组从0x00到0xff的256个值组成,加上加法和乘法,因此是(28)。
GF代表伽罗瓦域,以发明这一理论的数学家的名字命名。
GF(28)的一个特性是一个加法或乘法的操作的结果必须是在{0x00...0xff}这组数中。
虽然域论是相当深奥的,但GF(28)加法的最终结果却很简单。
GF(28)加法就是异或(XOR)操作。
然而,GF(28)的乘法有点繁难。
正如你稍后将在C#实现中所看到的,AES的加密和解密例程需要知道怎样只用七个常量0x01、0x02、0x03、0x09、0x0b、0x0d和0x0e来相乘。
所以我不全面介绍GF(28)的乘法,而只是针对这七种特殊情况进行说明。
在GF(28)中用0x01的乘法是特殊的;它相当于普通算术中用1做乘法并且结果也同样—任何值乘0x01等于其自身。
现在让我们看看用0x02做乘法。
和加法的情况相同,理论是深奥的,但最终结果十分简单。
只要被乘的值小于0x80,这时乘法的结果就是该值左移1比特位。
如果被乘的值大于或等于0x80,这时乘法的结果就是左移1比特位再用值0x1b异或。
它防止了“域溢出”并保持乘法的乘积在范围以内。
一旦你在GF(28)中用0x02建立了加法和乘法,你就可以用任何常量去定义乘法。
用0x03做乘法时,你可以将0x03分解为2的幂之和。
为了用0x03乘以任意字节b,因为0x03=0x02+0x01,因此:
b*0x03=b*(0x02+0x01)
=(b*0x02)+(b*0x01)
这是可以行得通的,因为你知道如何用0x02和0x01相乘和相加,同哩,用0x0d去乘以任意字节b可以这样做:
b*0x0d=b*(0x08+0x04+0x01)
=(b*0x08)+(b*0x04)+(b*0x01)
=(b*0x02*0x02*0x02)+(b*0x02*0x02)+(b*0x01)
在加解密算法中,AESMixColumns例程的其它乘法遵循大体相同的模式,如下所示:
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)
总之,在GF(28)中,加法是异或操作。
其乘法将分解成加法和用0x02做的乘法,而用0x02做的乘法是一个有条件的左移1比特位。
AES规范中包括大量有关GF(28)操作的附加信息。
密钥扩展
AES加密和解密算法使用了一个由种子密钥字节数组生成的密钥调度表。
AES规范中称之为密钥扩展例程(KeyExpansion)。
从本质上讲,从一个原始密钥中生成多重密钥以代替使用单个密钥大大增加了比特位的扩散。
虽然不是无法抵御的困难,但理解KeyExpansion仍是AES算法中的一个难点。
KeyExpansion例程高级伪代码如下所示:
KeyExpansion(byte[]key,byte[][4]w)
{
copytheseedkeyintothefirstrowsofw
foreachremainingrowofw
{
usetwoofthepreviousrowstocreateanewrow
}
}
“用前面两行来产生一个新行”(“usetwoofthepreviousrowstocreateanewrow”)的例程用到了两个子例程,RotWord和SubWord以及一个名为“Rcon”的常数表(作为“轮常数”)。
让我们先来逐个看一下这三东西,然后再回到整个KeyExpansion的讨论中来。
RotWord例程很简单。
它接受一个4个字节的数组并将它们向左旋转一个位置。
因为轮调度表w[]有四列,RotWord将w[]的1行左旋。
注意KeyExpansion使用的这个RotWord函数与加密算法使用的 ShiftRows(行位移变换)例程非常相似,只是它处理的是单行密钥调度w[],而不是整个加密状态表State[]。
SubWord例程使用替换表Sbox对一给定的一行密钥调度表w[]进行逐字节替换。
KeyExpansion操作中的替换实际上就像在加密算法中的替换一样。
被代替的输入字节被分成(x,y)对,它被当作进入替换表Sbox的索引。
举例来说,0x27的代替结果是x=2和y=7,并且Sbox[2,7]返回0xcc。
KeyExpansion例程使用一个被称为轮常数表的数组Rcon[]。
这些常数都是4个字节,每一个与密钥调度表的某一行相匹配。
AES的KeyExpansion例程需要11个轮常数。
你可以在Figure7中看到这些常数清单。
每个轮常数的最左边的字节是GF(28)域中2的幂次方。
它的另一个表示方法是其每个值是前一个值乘上0x02,正如前一部分讨论GF(28)乘法时所描述的那样。
注意0x80×0x02=0x1b是0x80左移1个比特位后紧接着与0x1b进行异或,如前所述。
现在让我们更进一步看看KeyExpansion内幕中的循环。
这里所用的伪码比以前更为详细,这个循环是:
for(row=Nk;row<(4*Nr+1);++row)
{
temp=w[row-1]
if(row%Nk==0)
temp=SubWord(RotWord(temp))xorRcon[row/Nk]
elseif(Nk==8androw%Nk==4)
temp=SubWord(temp)
w[row]=w[row-Nk]xortemp
}
先不要去看if子句,你将看到密钥调度表w[]的每一行都是前面一行与行Nk异或的结果(4,6,或8取决于密钥的长度)。
if条件的第一部分用SubWord、RotWord以及与轮常数的异或修改密钥调度表的每个第4、第6或第8行,取决于是否密钥的长度是128、192或256位。
这个条件的第二部分将修改行12、20和28等等——对于256位密钥而言——每一个第8行都将添加密钥调度额外的可变性。
让我们用本文开头所举的例子来考察KeyExpansion是如何开始的。
种子密钥是192-bit/6-word值:
000102030405060708090a0b0c0d0e0f1011121314151617
密钥调度字节表w[]的维数是4列并且Nb×(Nr+1)等于4×(12+1),或52行。
KeyExpansion将种子密钥的值拷贝到密钥调度字节表w[]的第一行。
因为我的种子密钥是192位(24字节),并且w[]表总是4列,在这种情况下KeyExapansion将种子密钥拷贝到w[]的前面6行。
现在让我们看看KeyExapansion例程是如何填充密钥调度表其余部分的。
在我的例子里,第一个被计算的行是第6行,因为第0-5行已被种子密钥的值填上了:
temp=w[row-1]=14151617
条件(row%Nk==0)为真,因此首先RotWord子程序被应用:
temp=15161714
这时SubWord被应用:
temp=5947f0fa
用Rcon[row/Nk]=Rcon[6/6]=01000000进行异或:
temp=5847f0fa
这时用w[row-Nk]=w[6-6]=00010203异或,产生了下面结果:
w[6]=5846f2f9
密钥调度表w[]中其余所有行来重复这个过程本身。
总而言之,AES加密和解密的一个重要部分就是从最初的种子密钥中生成多重轮密钥。
这个KeyExapansion算法生成一个密钥调度并以某种方式进行替代和置换,在这种方式中,加密和解密算法极其相似。
用C#编写AES类构造函数
现在我已研究了构成AES加密算法的各个成分,我将用C#来实现它。
官方的AES算法规范包含在联邦信息处理标准出版物197(FederalInformationProcessingStandardsPublication197)中。
我决定尽可能贴切地以它作为我的实现的基础,但是我很快发现这个规范更是一个理论文献而非一个实现的向导。
为了将这个官方规范作为资源来使用,我使用的变量名与标准出版物中所用的相同。
(即便它们是那么晦涩,如“Nr”和“W”)。
我的设计使用9个数据成员和一个枚举类型,如下所示:
publicenumKeySize{Bits128,Bits192,Bits256};
privateintNb;
privateintNk;
privateintNr;
privatebyte[]key;
privatebyte[,]Sbox;
privatebyte[,]iSbox;
privatebyte[,]w;
privatebyte[,]Rcon;
privatebyte[,]State;
因为密钥长度只能是128位、192位或256位比特,它是非常适于用枚举类型:
publicenumKeySize{Bits128,Bits192,Bits256};
该规范文档一般用字节作为基本储存单元而不是用4字节的字作为两个重要数据成员的长度。
这两个成员Nb和Nk代表以字为单位的块长以及以字为单位的密钥长度。
Nr代表轮数。
块长度总是16字节(或这说是128位,即为AES的4个字),因此它可以被声明为一个常量。
密钥长度依照枚举参数KeySize的值被赋值为4、6或8。
AES算法强调通过大量轮数来增加加密数据的复杂性。
轮数是10、12或14中的任意一个并且是基于密码分析学理论的。
它直接取决于密钥长度。
当设计一个类接口时,我喜欢向后来做。
我设想从应用程序中调用构造函数和方法。
使用这个办法,我决定象下面这样来实例化一个AES对象:
Aesa=newAes(thekeysize,theseedkey)
我调用的加密和解密例程如下:
a.Cipher(plainText,cipherText);
a.InvCipher(cipherText,decipheredText);
我选择少许笨拙的方法来命名Cipher和InvCipher,因为它们是用在AES规范文档中的。
这里是AES类构造函数的代码为:
publicAes(KeySizekeySize,byte[]keyBytes)
{
SetNbNkNr(keySize);
this.key=newbyte[this.Nk*4];
keyBytes.CopyTo(this.key,0);
BuildSbox();
BuildInvSbox();
BuildRcon();
KeyExpansion();
}
该构造函数首先调用一个辅助方法 SetNbNkNr给Nb、Nk和Nr赋值,如Figure8所示。
如果考虑到效率,你可能将这些代码直接放入构造函数以避免方法调用的开销。
接下来,你必须将传入构造函数的字节拷贝到类域变量中。
密钥用其它的类域声明,并且用如下方法获得它的值:
this.key=newbyte[this.Nk*4];
keyBytes.CopyTo(this.key,0);
我决定在构造函数中调用私有辅助方法BuildSbox和BuildInvSbox来初始化替换表Sbox[]和iSbox[]。
现在密钥扩展例程、Cipher方法和InvCipher方法各自都需要Sbox[]和iSbox[],因此我本来可以在Cipher和InvCipher两个方法中初始化Sbox[]并调用KeyExpansion方法,但是将它们放入构造函数会代码结构更加清晰。
在Figure9中sBox[]被填充。
填充iSbox[]代码类似。
为了可读性对代码进行了结构化处理。
正如后面你将看到的,还有另外一个可供选择的令人惊讶的方法为Sbox和iSbox表提供值。
在构造函数中声明密钥调度表w[]、轮常数表Rcon[]和状态矩阵State[],并用私有辅助方法来给Rcon[]和w[]赋值在我看来似乎是组织它们的最好办法,但那主要还是个风格问题。
置换轮常数表Rcon的赋值代码参见Figure7。
回想一下,GF(28)中,Rcon[]每一行左边的字节都2的幂,因此这个表可用下面的方法建立:
newVal=prevVal*0x02;
AES构造函数在建立完密钥调度表w[]后结束,而w[]是在KeyExpansion方法中完成的(参见Figure10)。
其代码相当简单。
规范文档使用一个假设的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];
两个字节相互的异或操作在这个代码中频频发生。
它
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 高级 加密 标准 AES 保持 数据 安全