AES密钥扩展函数-字符版

2,114 阅读6分钟

概述

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-128AES-256AES-256
密钥长度128192256
AES计算轮次101214
轮次密钥数111315
密钥字数组长度445260
轮次计算密钥字数468
扩展轮次1087

其他如块大小(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,但应该没有什么区别(连续异或操作):

256expansion.png