文章目录

  • 1. Introduction
  • 3. Notation and Conventions
  • 3.2 Bytes
  • 3.4 State
  • 4. Mathematical Preliminaries
  • 4.1 Addition
  • 4.2 Multiplication
  • 4.2.1 Multiplication by x-xtime()
  • 4.3 Polynomials with Coefficients in GF(2^8^)
  • 5. Algorithm Specification
  • 5.1 Cipher
  • 5.1.1 SubBytes()Transformation
  • 5.1.2 ShiftRows() Transformation
  • 5.1.3 MixColumns() Transformation
  • 5.1.4 AddRoundKey() Transformation
  • 5.2 Key Expansion
  • 5.3 Inverse Cipher
  • 5.3.5 Equivalent Inverse Cipher
  • 实现
  • 参考资料


1. Introduction

标准为FIPS-197 2001,1997-2000年期间通过竞赛形式选用Daemen和Rijmen两位参选者的算法Rijndael。

其它参选算法:

  • MARS, by IBM company
  • RC6, by RSA company
  • Serpent, by Anderson, Biham, Knudsen
  • Twofish, by Counterpane company

AES简介:

  • 分组长度:128 bits == 16 bytes.
  • 密钥长度:
  • AES-128, 128 bits == 16 bytes
  • AES-192, 192 bits == 24 bytes
  • AES-256, 256 bits == 32 bytes

原Rijndael算法的分组长度和密钥长度则是支持[128 bits, 256 bits, step = 32 bits]

3. Notation and Conventions

3.2 Bytes

在AES算法中,字节可表示为有限域(finite field)上的多项式(polynomial)。
cryptoPP CBC aes分组长度可变吗 aes算法分组长度_多项式

输入的比特流在字节中以大端序存储。

3.4 State

AES的操作对象是一个状态(State)二维数组(以字节为单位):

  • 4行,下标记为r;
  • Nb列,Nb * Rows == Block Length,所以Nb取值为16 / 4 == 4,下标记为c;

输入字节和输出结果都是按列进行存储的:

state[r, c] = input[r + 4c];
out[r + 4c] = s[r, c];

在AES中可以把每一列看作一个大端序4字节整型(uint32_t)进行处理,所以Nb既可以理解为列数,也可以理解为word数。

4. Mathematical Preliminaries

Finite field elements can be added and multiplied.

4.1 Addition

其实就是异或操作。

0x57 ^ 0x83 == 0xd4

cryptoPP CBC aes分组长度可变吗 aes算法分组长度_分组长度_02

4.2 Multiplication

In the polynomial representation, multiplication in GF(28) (denoted by •) corresponds with the multiplication of polynomials modulo an irreducible polynomial(不可约多项式) of degree 8. A polynomial is irreducible if its only divisors(因子) are one and itself.

AES中的不可约多项式如下:
cryptoPP CBC aes分组长度可变吗 aes算法分组长度_分组长度_03
也可记为0x011B(==0b100011011)。

The modular reduction by m(x) ensures that the result will be a binary polynomial of degree less than 8, and thus can be represented by a byte.

至于有限域求乘法逆元,则需要用到扩展欧几里得算法(the extended Euclidean algorithm)。

4.2.1 Multiplication by x-xtime()

将字节的多项式乘以x,则变成了如下公式:
cryptoPP CBC aes分组长度可变吗 aes算法分组长度_密码学_04
乘以x,在代码中其实就是左移一位。

若b71,则与AES中的不可约多项式m(x)异或;若b70,则上式就是最简形式。这种操作在AES中叫做xtime()。

以0x57乘以所有高次幂多项式为例:

a = 0x57
mx = 0x011b
print("0x57 * 0x1 == 0x57")
for i in range(1, 8):
    a = a << 1;
    print("0x57 * %s == xtime(%s)" %(hex(2**i), hex(a>>1)), end="")
    if(a & 0x0100):
        print(" == %s ^ %s->%s" % (hex(a), hex(mx), hex(a^mx)))
        a = a ^ mx
    else:
        print(" == %s" % hex(a))
# 0x57 * 0x1 == 0x57
# 0x57 * 0x2 == xtime(0x57) == 0xae
# 0x57 * 0x4 == xtime(0xae) == 0x15c ^ 0x11b->0x47
# 0x57 * 0x8 == xtime(0x47) == 0x8e
# 0x57 * 0x10 == xtime(0x8e) == 0x11c ^ 0x11b->0x7
# 0x57 * 0x20 == xtime(0x7) == 0xe
# 0x57 * 0x40 == xtime(0xe) == 0x1c
# 0x57 * 0x80 == xtime(0x1c) == 0x38

0x57乘以0x13的结果可以用结合律计算:

0x57 * 0x13 
= 0x57 * (0x01 ^ 0x02 ^ 0x10)
= 0x57 ^ 0xae ^ 0x07 
= 0xfe

换成c语言实现:

#include <stdio.h>
#include <stdint.h>

uint8_t xtime(uint16_t a,  uint8_t b)
{
	static const uint16_t mx = 0x011B;
	uint16_t nRet = 0;
	int i = 0;
	for (i = 0; i < 8; ++i)
	{
		if (b & 0x01)
		{
			nRet ^= a;
		}
		b >>= 1;
		a <<= 1;
		if (a & 0x0100)	// x^8
		{
			a ^= mx;
		}
	}
	return nRet & 0xFF;
}

int main()
{
	uint8_t a = xtime(0x57, 0x13);
	printf("%x\n", a);	// fe
	return 0;
}

4.3 Polynomials with Coefficients in GF(28)

有限域也适用于字(word,4字节整型)操作。
cryptoPP CBC aes分组长度可变吗 aes算法分组长度_分组长度_05
其中乘法c(x)无法表示为4字节整型,在AES中需要取余操作:
cryptoPP CBC aes分组长度可变吗 aes算法分组长度_密码学_06
计算的结果为d(x):
cryptoPP CBC aes分组长度可变吗 aes算法分组长度_密码学_07

如果a(x)是一个固定的多项式,则可以写成矩阵(matrix)形式:

cryptoPP CBC aes分组长度可变吗 aes算法分组长度_多项式_08

x4+1 is not an irreducible polynomial over GF(28), multiplication by a fixed four-term polynomial is not necessarily invertible.

乘法运算,一共256*256种情况,可以提前将运算结果硬编码为数组,前提是确保所运行的平台有足够的空间。

5. Algorithm Specification

重申几个常量和变量:

  • 分组长度:128 bits == 16 bytes == Nb words(4).
  • 密钥长度:
  • AES-128, 128 bits == 16 bytes == Nk words(4)
  • AES-192, 192 bits == 24 bytes == Nk words(6)
  • AES-256, 256 bits == 32 bytes == Nk words(8)
  • 轮数:
  • AES-128, Nr==10
  • AES-192, Nr==12
  • AES-256, Nr==14

加密和解密的轮函数(round function)流程可以简述为4步:

  1. byte substitution using a substitution table (S-box)
  2. shifting rows of the State array by different offsets
  3. mixing the data within each column of the State array
  4. adding a Round Key to the State.

5.1 Cipher

官方伪代码:

Cipher(byte in[4*Nb], byte out[4*Nb], word w[Nb*(Nr+1)])
begin
    byte state[4,Nb]
    state = in
    AddRoundKey(state, w[0, Nb-1]) // See Sec. 5.1.4
    for round = 1 step 1 to Nr–1
        SubBytes(state) // See Sec. 5.1.1
        ShiftRows(state) // See Sec. 5.1.2
        MixColumns(state) // See Sec. 5.1.3
        AddRoundKey(state, w[round*Nb, (round+1)*Nb-1])
    end for
    SubBytes(state)
    ShiftRows(state)
    AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1])
    out = state
end

5.1.1 SubBytes()Transformation

通过一个16*16的S-box(substitution table),进行字节替换。

State数组元素的高4位为行索引,低4位为列索引。

static const uint8_t sbox[16][16] = {
{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}
};

5.1.2 ShiftRows() Transformation

每行以字节为单位循环左移:

  • 第1行不变
  • 第2行循环左移1字节
  • 第3行循环左移2字节
  • 第4行循环左移3字节

5.1.3 MixColumns() Transformation

以列为单位,把每列看成一个在GF(28)域上的多项式,计算公式如下:

S'(x) = a(x) * s(x) mod (x^4+1)

其中a(x)为固定的多项式:
cryptoPP CBC aes分组长度可变吗 aes算法分组长度_多项式_09
然后将a(x)套用到4.3的公式就行了。

5.1.4 AddRoundKey() Transformation

将State中每列4字节(即一个word)与轮密钥(Round Key)相加(异或)。对应轮密钥下标如下:

round*Nb+c
0 <= round <= Nr
0 <= c <= Nb

Each Round Key consists of Nb words from the key schedule (described in Sec.5.2).

5.2 Key Expansion

The AES algorithm takes the Cipher Key, K, and performs a Key Expansion routine to generate a key schedule.

伪代码:

KeyExpansion(byte key[4*Nk], word w[Nb*(Nr+1)], Nk)
// Nb*(Nr+1) == 
// 4 * 11
// 4 * 13
// 4 * 15
begin
    word temp
    i = 0
    while (i < Nk)	// Nk == 4/6/8 words
        w[i] = word(key[4*i], key[4*i+1], key[4*i+2], key[4*i+3])
        i = i+1
    end while
    i = Nk
	while (i < Nb * (Nr+1))
        temp = w[i-1]
        if (i mod Nk = 0)
        	temp = SubWord(RotWord(temp)) xor Rcon[i/Nk]
        else if (Nk > 6 and i mod Nk = 4)
        	temp = SubWord(temp)
        end if
        w[i] = w[i-Nk] xor temp
        i = i + 1
    end while
end

SubWord()将一个word根据S-Box进行替换。

RotWord()将一个word循环左移1字节。

Rcon[i], The round constant word array, contains the values given by [xi-1,{00},{00},{00}], as discussed in Sec. 4.2. i starts at 1.

a = 0x01
mx = 0x011b
listRcon = []
for i in range(15):
    # print("0x57 * %s == %s" %(hex(i+1), hex(a)) )
    if(a & 0x0100):
        a = a ^ mx
        # print("^ mx->%s" % hex(a))
    listRcon.append(a << 24)
    a = a << 1;

for i in listRcon:
    print("0x%08x" % i, end=",")
# 0x01000000,0x02000000,0x04000000,0x08000000,0x10000000,
# 0x20000000,0x40000000,0x80000000,0x1b000000,0x36000000,
# 0x6c000000,0xd8000000,0xab000000,0x4d000000,0x9a000000,

原文中Rcon下标从1开始,所以为代码中的Rcon[i/Nk]在实现时应该改成Rcon[(i-1)/Nk]

5.3 Inverse Cipher

解密流程官方伪代码:

InvCipher(byte in[4*Nb], byte out[4*Nb], word w[Nb*(Nr+1)])
begin
    byte state[4,Nb]
    state = in
    AddRoundKey(state, w[Nr*Nb, (Nr+1)*Nb-1]) // See Sec. 5.1.4
    for round = Nr-1 step -1 downto 1
        InvShiftRows(state) // See Sec. 5.3.1
        InvSubBytes(state) // See Sec. 5.3.2
        AddRoundKey(state, w[round*Nb, (round+1)*Nb-1])
        InvMixColumns(state) // See Sec. 5.3.3
    end for
    InvShiftRows(state)
    InvSubBytes(state)
    AddRoundKey(state, w[0, Nb-1])
    out = state
end

InvShiftRows()是ShiftRows()的逆过程。

InvSubBytes()依赖于inverse S-box(逆替换表)。

static const uint8_t aes_invsbox[16][16] = {
{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}
};

InvMixColumns()依赖于a(x)的逆多项式a-1(x)
cryptoPP CBC aes分组长度可变吗 aes算法分组长度_密码学_10

5.3.5 Equivalent Inverse Cipher

上述解密流程的置换顺序和加密有些不同,但使用了同样的key schedules。

通过修改key schedules,也可以用加密中的置换流程实现解密,并且效率更高。

EqInvCipher(byte in[4*Nb], byte out[4*Nb], word dw[Nb*(Nr+1)])
begin
   byte state[4,Nb]
   state = in
   AddRoundKey(state, dw[Nr*Nb, (Nr+1)*Nb-1])
   for round = Nr-1 step -1 downto 1
      InvSubBytes(state)
      InvShiftRows(state)
      InvMixColumns(state)
      AddRoundKey(state, dw[round*Nb, (round+1)*Nb-1])
   end for
   InvSubBytes(state)
   InvShiftRows(state)
   AddRoundKey(state, dw[0, Nb-1])
   out = state
end

新的密钥扩展流程(Key Expansion routine):

for i = 0 step 1 to (Nr+1)*Nb-1
dw[i] = w[i]
end for
for round = 1 step 1 to Nr-1
   InvMixColumns(dw[round*Nb, (round+1)*Nb-1]) // note change of type
   // two-dimensional array of bytes
   // -->
   // one-dimensional array of words
end for

实现

https://github.com/C0deStarr/CryptoImp/tree/main/Cipher/BlockCipher

  • aes.h
  • aes.c
  • gf_mul.h
  • gf_mul.c