0%

AES加密算法7-加密模式

AES加密的C语言实现了基本密码块的加密,这里有两个问题,一是分组加密的一组必须是4x4个字节,这导致被加密的文件大小可能不是4x4的整数倍,这个时候就需要额外填充字符;二是将大块文件分组加密,会导致相同的明文有相同的密文,所以需采取不同的加密模式.本篇是AES加密算法的完结篇.

1. AES的padding问题

首先说一下,在AES加密与解密的过程中如果需要加密的数据不是16的倍数的时候,需要对原来的数据做padding操作;padding分为以下几种,不同的padding对加密与解密有影响,所以要保证padding的方式是一致的。

  • ANSI X.923
    在ANSI X.923的方式下,先是填充00,最后一个字节填充padded的字节个数。
    例子: | DD DD DD DD DD DD DD DD | DD DD DD 00 00 00 00 05 |
  • ISO 10126
    在ISO 10126的方式下,先是填充随机值,最后一个字节填充padded的字节个数。
    例子: | DD DD DD DD DD DD DD DD | DD DD DD 95 81 28 A7 05 |
  • PKCS7
    在PKCS7的方式下,如果一共需要padded多少个字节,所有填充的地方都填充这个值。
    例子: | DD DD DD DD DD DD DD DD | DD DD DD 05 05 05 05 05 |
  • ISO/IEC 7816-4
    在ISO/IEC 7816-4方式下,第一个填充的字节是80,后面的都填充00。
    例子: | DD DD DD DD DD DD DD DD | DD DD DD 80 00 00 00 00 |
  • Zero padding
    在Zero padding方式下,每一个需要填充的字节都填00。
    例子: | DD DD DD DD DD DD DD DD | DD DD DD 00 00 00 00 00 |

2. AES的加密模式

  • 1.电码本模式(Electronic Codebook Book (ECB));
  • 2.密码分组链接模式(Cipher Block Chaining (CBC));
  • 3.计算器模式(Counter (CTR));
  • 4.密码反馈模式(Cipher FeedBack (CFB));
  • 5.输出反馈模式(Output FeedBack (OFB))。

3. 我的实现

现在实现使用的padding方式是PKCS7,加密模式使用了ECB模式。

3.1 加密的API接口,具体实现在前面的笔记有记录
1
2
3
4
// aes api
void key_expansion(uint8_t *key, uint8_t *w);
void cipher(uint8_t *in, uint8_t *out, uint8_t *w);
void inv_cipher(uint8_t *in, uint8_t *out, uint8_t *w);
3.2 加密处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
std::ifstream is (filename, std::ifstream::binary);
std::ofstream os (filename + ".bin", std::ifstream::binary);
if (is) {
// get length of file:
is.seekg (0, is.end);
int length = is.tellg();
is.seekg (0, is.beg);
uint8_t in[16] = {0};
uint8_t out[16] = {0}; // 128

while(length > 0) {
// 也可以一次读取到缓冲区
is.read ((char*)in,(length>=16?16:length));
if (!is) break;
// padding实现PKCS7
if(length < 16) {
int num = 16 - length;
for (int i= length;i<16;i++) {
in[i] = num;
}
}
// 分组加密
cipher(in /* in */, out /* out */, w /* expanded key */);
// 会写到文件
os.write((char*)out, sizeof(out));
// 这里处理正好分组满足的情况补一个分组
if (length == 16) {
for(int i=0;i<16;i++) in[i] = 16;
cipher(in,out,w);
os.write((char*)out, sizeof(out));
}
// 未读修正
length = length - is.gcount();
}

is.close();
os.close();
}
3. 代码实现上的若干问题
  • 密钥扩展问题,128位加密是16字节(16*8),256位加密是32字节,每一轮的加密轮数也不一样,分别是10轮和14轮。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    uint8_t key[KEY_LEN] = {0};
    strncpy((char*)&key, password.c_str(),KEY_LEN);
    switch (sizeof(key)) {
    default:
    case 16: Nk = 4; Nr = 10; break;
    case 24: Nk = 6; Nr = 12; break;
    case 32: Nk = 8; Nr = 14; break;
    }

    uint8_t* w = (uint8_t*)malloc(Nb*(Nr+1)*4);
    key_expansion(key, w);
  • 性能优化问题,目前intel处理器上,硬件实现了AESENC/AESENCLAST指令;golang的源码实现中,就使用的是这个指令,如果是日常应用,又不担心有硬件后门,可以使用golang版本的aes加密;这里把源码贴出来。你可以理解为AESENC指令实现了cipher函数的功能。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
     // crypto/aes/asm_amd64.s(line:8)
    TEXT ·encryptBlockAsm(SB),NOSPLIT,$0
    MOVQ nr+0(FP), CX
    MOVQ xk+8(FP), AX
    MOVQ dst+16(FP), DX
    MOVQ src+24(FP), BX
    MOVUPS 0(AX), X1
    MOVUPS 0(BX), X0 // 读取Block到X0寄存器
    ADDQ $16, AX
    PXOR X1, X0
    SUBQ $12, CX
    JE Lenc196
    JB Lenc128
    Lenc256:
    MOVUPS 0(AX), X1 // 加密16字节
    AESENC X1, X0
    MOVUPS 16(AX), X1 //加密16字节
    AESENC X1, X0
    ADDQ $32, AX

项目工程文件点击下载