概述
AES加密算法中,密钥扩展(expansion)函数是一个重要的组成部分,也称为AES Key Schedule(AES密钥计划)。
AES算法标准有三种类型,分别是AES-128,AES-192和AES-256,显然是根据密钥的长度(单位是bit,如果是字节的话,分别是16,24和32)来确定的。以AES-256为例,我们将其按照4个字节作为一组,可以分成8组,每组包括4个字节,称之为Word(字),这样这个初始密钥就是一个字的数组,记作W0...W7。
无论何种AES类型,其使用的块的大小都是16个字节,对应密钥操作就需要使用4个字的密钥。AES每一轮计算,都需要使用四个不同的密钥字,我们称为轮密钥。不同的AES类型,规定计算的轮次分别为10,12和14轮,加上初始轮,所需要的字密钥总数量转换为字就是44,52和60个。
所以,AES的密钥计划,就是根据不同的AES类型的要求,从初始密钥出发,按照算法约定,生成加密所需要的所有轮次密钥的过程,最终会产出一个约定数量的字的数组。密钥扩展也是一轮一轮进行的(和AES轮次无关),所以,要扩展出足够的密钥,需要计算的轮次分别为10,8和7((密钥总数-4)/初始密钥长度)。
至此,AES三种类型的所有设定如下:
| 项目\类型 | AES-128 | AES-256 | AES-256 |
|---|---|---|---|
| 密钥长度 | 128 | 192 | 256 |
| AES计算轮次 | 10 | 12 | 14 |
| 轮次密钥数 | 11 | 13 | 15 |
| 密钥字数组长度 | 44 | 52 | 60 |
| 轮次计算密钥字数 | 4 | 6 | 8 |
| 扩展轮次 | 10 | 8 | 7 |
其他如块大小(128bit)、sbox、轮次常量(rc),和相关的计算方式,所有类型的AES都是相同的。
设计和原理
AES密钥扩展算法,就是一个基于初始密钥,求解密钥字数组的过程,其实用一个公式就可以表达(来自Wiki百科),这个文字版的表述虽然初期稍微觉得不太好理解,但这个表述用于指导编码,是非常合适和方便的:
┌ Ki if i < N
Wi= | W{i-N} (+) SubWord(W{i-1}) if i >= N, N == 8, i % 4 = 0
| W{i-N} (+) G(W{i-1}) if i >= N, i % N = 0
└ W{i-N} (+) W(i-1) otherwise
其中:
RCi = 2**(i-1) mod (i < 8 ? 0 : 0x11B)
RC = [01,02,04,08,10,20,40,80,1B,36]
S(i) = sbox[i]
SubWord([b0,b1,b2,b3]) = [S(b0),S(b1),S(b2),S(b3)]
RotWord([b0,b1,b2,b3]) = [b3,b0,b1,b2]
RCWord(r) = [RC[r],0,0,0]
G(w) = SubWord(RotWord(w)) (+) RCWord(w)
各要素释义如下:
- N:初始和轮次计算密钥的字数,分别为4,6,8
- R: 密钥扩展计算轮次(不同于AES轮次)
- Ki,K0,K1....K{N-1}: 原始密钥字数组
- Wi,W0,W1,....W{4R-1}: 扩展后的字数组,最终结果
- RotWord: 字位移函数
- S: sbox替换函数,sbox结构同AES算法
- SubWord: 字替换函数
- G: g函数,其实是先RotWord后SubWord的复合计算
- RCWord: 轮次字常量计算,因为要对位计算,需要有轮次常量生成字,实际上就是只用第一个字节
- RC: 轮次常量数组,也是通过一个公式计算出来的,这里 mod 是GF求余
- (+): 字元素对位异或计算,或者两字节异或计算
简单说明,其实这个计算,就是分了几种情况:
-
开始的N个原始密钥字,就是初始密钥字,W的前N个成员
-
对于AES-256,密钥字数组长度为8的时候,需要在每轮第4个字和前一个字异或之前,需要增加一次S替换操作
-
对于每扩展轮计算的第一个元素,需要从三个字的异或计算得到:
1 上轮的第一个字(W{i-N}),对应位置
2 本元素的前一个元素(W{i-1}),也就是上轮最后一个字,使用G函数计算,先RotWord,然后SubWord
3 本轮轮次常量RCi(在Word操作中,只影响到第一个字节,其余位都是0)
-
其他情况(轮次中计算),上轮对位字和本轮前一个字的异或操作
上面公式的写法,是为了方便编码的代码写法,并不是真正的执行顺序。真正的执行顺序是,先初始化前N个元素,然后第一轮开始,计算本轮第一个元素(涉及上一轮第一个字、最后一个字的G函数和轮次常量字),然后第二个元素来自上一轮的对位元素和本轮前一个元素,依次进行,如果是AES-256,需要在第5字生成前,对第4字多做一次S替换操作,...直到本轮结束。然后下一轮开始,直到所有需要的密钥字都生成为止。
由于密钥扩展的轮次,和AES加密的轮次,无法一一对应,所以一般情况下,会先把所有子密钥都计算出来放在一个数组中,以备AES轮次使用,不用在需要的时候临时计算,在一般的场景中,这些预计算的密钥占用空间很小,不要在意这个问题。
示例代码和说明
基于前面叙述,笔者自行实现的示例代码如下:
这里,轮次常量rc和sbox,虽然都是常数,但也可以通过计算得到,这里的实现参考了网上的内容。
文中的代码基于上述原理编写,可以运行,主要用于学习和试验,笔者还没来得及和正确的计算进行完整验证(除了rc和sbox),仅作为参考,读者如果发现有问题,可以和笔者联系。
前面的原理讨论,包括很多参考的文章和材料中,密钥扩展算法都是以Word作为处理的单位,可能是为了表示和讨论方便。但笔者在示例代码编写过程中,觉得其实直接用字节处理起来更加方便,不用做多余的转换动作,所以并没有明确的使用Word。
再补充一张AES-256密钥扩展原理图说明,可以看到,只扩展到了60个字,图中,G函数包括了rc,但应该没有什么区别(连续异或操作):