AES的软件优化

实验环境

  • 编程语言:C语言
  • 编辑器(含调试):VsCode
  • 操作系统:Windows 10

AES的查表法与SSE2优化

一、算法原理

1、查表法简述

常规的AES实现中,每轮要经历四种运算:字节代替、行移位、列混淆和轮密钥加。如果严格遵照AES算法实现,无疑每轮加密会具有较大的运算量,因此寻找一种能够合并多个运算过程的算法是十分必要的。查表法通过查表的方式,采取空间效率换取时间效率的方式,通过建立多个查找表实行查找操作合并字节代替、移位运算和有限域运算,优点是加速了每轮的加密效率,缺点是增大了内存的开销。

下面对加密过程为例说明建立查找表的过程。

假设状态数组aes128 cbc 算法说明 aes算法在线_aes128 cbc 算法说明。经过字节代替和行移位后,可得到新的state矩阵aes128 cbc 算法说明 aes算法在线_网络安全_02。进行列混淆的过程相当于state右乘一个列混淆矩阵aes128 cbc 算法说明 aes算法在线_c语言_03。可以将矩阵乘法的结果拆分来看,如下所示:
aes128 cbc 算法说明 aes算法在线_c语言_04
由此可见,对于这三步运算的合并需要建立四个表:aes128 cbc 算法说明 aes算法在线_vscode_05aes128 cbc 算法说明 aes算法在线_c语言_06aes128 cbc 算法说明 aes算法在线_c语言_07aes128 cbc 算法说明 aes算法在线_aes128 cbc 算法说明_08。这些表的输入输出格式为8比特输入-32比特输出。

2、SSE2简述

SSE2指令集(Streaming SIMD Extensions 2)是Intel公司在SSE指令集的基础上发展起来的。SSE2使用了128位的存储单元,对于加密分组为128比特的AES来说是比较契合的。SSE2的128位存储单元能够存储16个8位整数,或8个16位整数,或4个32位整数,或2个64位整数(这里只讨论整数,不讨论浮点数)。SSE2的一条指令能够同时操作存储单元内的多数据,因此比传统的多数据运算更快。

二、算法流程

对于查表法的实现,生成查找表的过程是比较繁琐的。下面以加密过程为例进行说明。

首先,根据AES的S盒和有限域乘法生成加密所需的经过有限域乘法处理的长度为256的盒:(01*S(x)、02*S(X)、03*S(X))

BoxGen(SBOX, n)
{
    set Box[256];
    for i from 0 to 255
        set Table[i] = GF-multiply(SBOX[i], n);
    return Box;
}

然后根据查表法需要的四个表进行生成:

TableGen(S_01, S_02, S_03)	//S_01表示S盒经过乘01处理的盒
{
    set Table1[256], Table2[256], Table3[256], Table4[256];
    // 符号||表示将二者的16进制字串连接在一起形成新的整数
    for i from 0 to 255
    {
        set Table1[i] = S_02[i]||S_01[i]||S_01[i]||S_03[i];
        set Table2[i] = S_03[i]||S_02[i]||S_01[i]||S_01[i];
        set Table3[i] = S_01[i]||S_03[i]||S_02[i]||S_01[i];
        set Table4[i] = S_01[i]||S_01[i]||S_03[i]||S_02[i];
    }
    return Table1, Table2, Table3, Table4;
}

以上步骤都是准备工作,生成的查找表作为全局变量写入C程序。

加密部分可以分为三部分:多轮加密前的轮密钥加操作、每轮的加密操作和最后一轮的加密操作。轮密钥加操作与正常的轮密钥加无异。下面对每轮的加密操作和最后一轮的加密操作进行说明。

每轮的加密操作可以通过“查表—异或—轮密钥加”的方式进行。根据算法原理一节的讨论可知,每个字的生成都是由4个表的输出异或得出。因此每轮的加密操作的伪代码如下:

ssma(state, roundkey)	//ssma: subbyte, shift, mixcolumn, addroundkey
{
	copy state into s;
	set state[0~3] = Table1[s[0]] ^ Table2[s[5]] ^ Table3[s[10]] ^ Table4[s[15]];
	set state[4~7] = Table1[s[4]] ^ Table2[s[9]] ^ Table3[s[14]] ^ Table4[s[3]];
	set state[8~11] = Table1[s[8]] ^ Table2[s[13]] ^ Table3[s[2]] ^ Table4[s[7]];
	set state[12~15] = Table1[s[12]] ^ Table2[s[1]] ^ Table3[s[6]] ^ Table4[s[11]];
	set state = state ^ roundkey
	return;
}

最后一轮的加密操作由于没有列混淆,因此只需要查找S盒即可,算法伪代码如下:

ssa(state, roundkey)	//ssa: subbyte, shift, addroundkey
{
	copy state into s
	set state[0] = s[0];	set state[1] = s[5];
	set state[2] = s[10];	set state[3] = s[15];
	set state[4] = s[4];	set state[5] = s[9];
	set state[6] = s[14];	set state[7] = s[3];
	set state[8] = s[8];	set state[9] = s[13];
	set state[10] = s[2];	set state[11] = s[7];
	set state[12] = s[12];	set state[13] = s[1];
	set state[14] = s[6];	set state[15] = s[11];
	return;
}

加密的主函数为:

aes(state)
{
    set state = state ^ roundkey[0];
    for i from 1 to 9
    	ssma(state, roundkey[i]);
    ssa(state, roundkey[10])
    return;
}

三、关于AES优化

1、SSE2的使用

查找表建立之后,影响AES加密速度的主要因素就是如何使用查找表。AES的加密算法中存在许多可以并行处理的数据运算,因此查找表结合SIMD会拥有更好的性能。SIMD中的SSE2存储单元的存储能力为128位,正好等于AES的加密分组长度,因此SSE2是很适合用来对AES进行优化的。

优化AES的过程中,使用最多的SSE2函数是set指令、xor指令和load指令。由于SSE2存储单元不支持对其内某一整数进行操作,因此对__m128i类型(SSE2中存放整数的数据类型)数据的读和写需要额外的操作。读取__m128i数据可采用uint8 *s=(uint8 *)&state将__m128i类型的state“拷贝”给s(这里的“拷贝”意思是将uint8类型的指针指向这块区域)。写入__m128i可用_mm_setr_epi~函数(“~”根据需求而定,可以是8、16、32、64)。对于uint8数组存入__m128i变量可采用__m128 state=_mm_loadu_si128((__m128i *)m)的方式。

2、指针的使用

除了这些可使用的优化资源外,还需要对代码的总体风格进行调整。对于C语言来说,指针是一把双刃剑。使用指针可以提高代码的运行效率,但是也可能出现一系列问题(指针越界、野指针等)。在函数调用中,如果将数组或其他较为复杂的数据类型的变量作为参数传入,无疑会严重影响程序的执行速度。因此,在AES优化的过程中,尽可能地合适地使用指针会很大程度上提高程序的运行效率。例如如下函数的定义和使用:

void ssma(__m128i *state, int round);
void ssa(__m128i *state);
ssma(&state, 9);
ssa(&state);
3、对解密算法的设计

对于解密来说,由于对解密算法向加密算法靠近的调整,轮密钥加的过程中使用的轮密钥是需要进行列混淆的。如果将其放入解密主函数或其调用的子函数中显然会影响解密速度。因此可以在密钥生成的时候将1~9号轮密钥进行列混淆,这样可以使得解密算法的运算量与加密算法的运算量基本相等,从而加快加密过程。

4、其他优化

另外,需要避免不必要的函数调用及其他代码结构。实际上,如果AES的主函数内填写所有加密代码无疑是高效的,因为其内没有任何库函数外的函数调用。但是这显然不符合代码编写的模块化思想, 不便于调试和维护。原本的加密主函数中是采用for循环对加密轮数进行计数的,但是这个for循环在优化的过程中被放弃,因为这是一个不必要的开销。最后加密的主函数呈现如下:

__m128i encrypt(uint8 *m)
{
    __m128i state = _mm_loadu_si128((__m128i *)m);
    state = _mm_xor_si128(state, KEY[0]);
    ssma(&state, 1);
    ssma(&state, 2);
    ssma(&state, 3);
    ssma(&state, 4);
    ssma(&state, 5);
    ssma(&state, 6);
    ssma(&state, 7);
    ssma(&state, 8);
    ssma(&state, 9);
    ssa(&state);
    return state;
}

由测试结果计算,AES的加解密速度可以达到:(r代表运行次数)
aes128 cbc 算法说明 aes算法在线_网络安全_09