AES加密(1):基本AES算法
本系列长期更新,全部更新完后会同步到知乎专栏
AES算法,一般指Rijndeal算法
简介
AES原本指的是一套标准FIPS 197,而AES算法一般指分组大小为128bits
的Rijndeal算法,由比利时学者Joan Daemen和Vincent Rijmen提出。
AES与Rijndeal的区别
AES仅指分段为128位的Rijndeal算法,两种算法对比如下:
(Nr表示循环轮数,Nb表示分组大小,Nk表示密钥长度,Nb和Nk单位都是32bits
)
Nr | Nb=4 (AES) | Nb=6 | Nb=8 |
---|---|---|---|
Nk=4 | 10 (AES-128) | 12 | 14 |
Nk=6 | 12 (AES-192) | 12 | 14 |
Nk=8 | 14 (AES-256) | 14 | 14 |
加密
总体流程
图片来自链接,这张图是AES-128的流程,AES-192和AES-256除了加密轮数和密钥长度以外都是一样的
AES加密的整个过程是在一个4×4的字节矩阵上运作的,这个字节矩阵称作state
。这个字节矩阵是由当前明文块处理得到的,简而言之就是把当前的16个字节按照4个字节一行排列成矩阵,比如说
1 | 0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0c,0x0d,0x0e,0x0f |
处理后得到的矩阵就是
1 | 0x00,0x01,0x02,0x03, |
加密前要先进行KeyExpansion
,将原始的密钥扩展得到扩展密钥,原始密钥同样要做与上面一样的变换得到矩阵才能进行运算
然后是开始加密,按照上图中顺序调用如下四个轮函数:
AddRoundKey
:轮密钥加运算,将当前的ByteSub
:字节变换 (S盒变换)ShiftRows
:行变换MixColumns
:列变换
4个轮函数都是在伽罗瓦域$GF(256)$上进行的。伽罗瓦域 (Galois Field) 是一个满足特定规则的集合,其中元素可以进行加减乘除,且运算结果也都是这个集合的元素,具体细节可以。
接下来分析四个轮函数
轮密钥加 / AddRoundKey
这个就是简单的把当前状态 (state) 与扩展密钥进行按位异或,代码如下:
1 | void aes::AddRoundKey(word *ExpandedKey, int i) { |
注意:AddRoundKey所异或的扩展密钥与当前加密轮数有关。扩展密钥是一个word数组,每个word有32bit,也就是说每个word能分解为4个byte,而异或轮密钥的时候需要把当前要异或的一组轮密钥(共4个word)分解为16个byte再进行异或。扩展密钥的长度是Nb*(Nr+1),每次AddRoundKey需要使用当前轮数乘上Nb开始的4个word长的轮密钥。
字节变换 / ByteSub
这一步就是将state
中每一个字节替换为S_box
中的对应字节。S_box
是一个有256个元素的一维数组,直接查找当前字节所对应的新的字节并替换即可。
那肯定有人会问:S_box
是怎么来的?
很显然这个S_box
不是随随便便来的一个数组。这是通过计算得来的,当然我们直接把他看作一个常量数组即可。
S_box是怎么来的?
本段数学内容较多,可以直接跳到下一条分割线继续阅读。
首先我们要知道什么是$GF(256)$域,具体可以参考我的这篇文章。
(接下来默认你已经明白$GF(256)$和它上面的运算了)
首先我们求出当前字节在$GF(256)$上的乘法逆元 (相当于实数域上倒数的概念) ,如果当前字节是0x00
则不变。我们把得到的这个数设为 $x$ 并把它以多项式的形式表示成如下形式:
接着我们有一个8位二进制数 $y$ ,同样以上面的方式表示,并且满足:
这个 $y$ 就是这个字节在S_box
中所对应的值。
注意这里顺序是反的,高位在下低位在上,并且都是以2进制形式表示的
附上S_box
数组:
1 | const byte S_Box[256] = { |
行变换 / ShiftRow
这个就比较好理解了,就是把每行左环移,第一行不变,第二行环移1位,第三行环移2位,第三行环移3位,代码如下:
1 | void aes::ShiftRow() { |
列混合 / MixColumn
这是整个AES加密流程中最复杂的一步,同时要应用到之前在S盒变换里提到过的GF(256)域,如果真的想要理解这一步的话建议先去仔细了解一下GF(256)再来继续阅读
我们定义一个多项式 $c(x)=’03’x^3+’01’x^2+’01’x+’02’$ ,带单引号的数表示16进制数。然后我们定义:
或者说
得到了一个新的列。
但是实际上我们很少会直接用$GF(256)$上的乘法来计算这个。由于AES的整个加密和解密过程只需要用到256*6
个GFMul
的值,因此我们可以直接用查表的方式加速计算。
代码:
1 | void aes::MixColumn() { |
密钥扩展 / KeyExpansion
万事俱备,只欠东风。4个轮函数已经全部齐了,现在只差一步——密钥扩展。
扩展密钥是一个长为Nb*(Nr+1)
的word
数组,一个word
相当于8个byte
。对于AES-128和AES-192,代码如下:
1 | void aes128::KeyExpansion() { |
而对于AES-256则多了一步:
1 | void aes256::KeyExpansion() { |
其中出现了几个东西:RotByte
,SubByte
和Rcon
。
RotByte
RotByte
函数是将这个word
中的四个byte
左环移一位,代码如下:
1 | word aes::RotByte(crypto::word in) { |
这个比较好理解,就不多说了。
SubByte
SubByte
是对这个word
里的每一个byte
进行S盒变换,代码也很简单:
1 | word aes::SubByte(crypto::word in) { |
Rcon
这个就比较复杂了,仍然要用到$GF(256)$相关内容(当然我们也可以把它看作常数数组)
首先我们有一个数组RC
,其中:
这里的所有运算都是在$GF(256)$上进行的。
而Rcon[i]=toWord(Rc[i],0x00,0x00,0x00)
。
整个数组如下:
1 | const word Rcon[16] = {0x00000000, 0x01000000, 0x02000000, 0x04000000, |
解密
解密过程与加密过程刚好相反。这里只放几个关键数据:
逆S盒
1 | const byte Inv_S_Box[256] = { |
逆列变换
1 | void aes::InvMixColumn() { |
逆行变换
1 | void aes::InvShiftRow() { |
轮密钥加和密钥扩展
加密和解密过程中轮密钥加和密钥扩展是完全一样的,不需要另外写新的代码。
参考资料: