前言

上一篇文章讲了 DES ECB 加解密。

由于 ECB 模式是分组加密,且每组之间相互独立,所以就存在一个风险,可以调换分组之间的顺序。

为了解决这个问题,需要添加初始化向量,它的作用是用来与第一个明文块进行异或操作,然后将结果送入加密函数。

后续的每个明文块都与前一个块的密文进行异或后再加密。

这样再改动顺序就不能还原最开始的明文了。

CBC 工作原理

初始化

  • 密钥:确保有一个64位的密钥(实际上只有56位用于加密)
  • 初始化向量(IV):需要一个随机生成的8字节IV。IV不需要保密,但必须对于每次加密操作都是唯一的。IV的作用是确保即使相同的明文块用相同的密钥加密,也会产生不同的密文。

填充

为了使输入数据长度成为块大小(8字节)的整数倍,通常使用PKCS#7填充方法。如果最后一个数据块不足8字节,则添加填充字节,每个字节的值等于所添加的字节数。

加密

  • 第一个块:将第一个明文块与IV进行异或操作,然后将结果送入DES加密函数得到第一个密文块。
  • 后续块:对于每个后续的明文块,先将其与前一个块的密文进行异或操作,再送入DES加密函数。这确保了每个明文块都依赖于之前所有块的加密结果,从而增强了安全性。

上代码

 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
#include <iostream>
#include <iomanip>
#include <openssl/des.h>
using namespace std;

string plain = "cbc 加密";
unsigned char encrypted[1024] = {0};
DES_key_schedule schedule;
const_DES_cblock key = {'1', '2', '3', '4', '5', '6', '7', '8'};
DES_set_key_unchecked(&key, &schedule);
DES_cblock ivec = {'1', '2', '3', '4', '5', '6', '7', '8'};
int len = plain.length();
size_t padding = 8 - (len % 8); // 计算 paading 长度
plain.append(padding, padding); // PKCS#7 填充

DES_ncbc_encrypt(reinterpret_cast<const unsigned char *>(plain.data()), encrypted, plain.length(), &schedule, &ivec, DES_ENCRYPT);


cout << "加密:" << endl;
for (int i = 0; i < plain.length(); ++i) {
    cout << hex << setw(2) << setfill('0') << (int)encrypted[i];
}
cout << endl;

cout << "解密:" << endl;

unsigned char output[1024];
DES_cblock ivec_dec = {'1', '2', '3', '4', '5', '6', '7', '8'};
DES_ncbc_encrypt(encrypted, output, plain.length(), &schedule, &ivec_dec, DES_DECRYPT);
int padding_len = output[plain.length() - 1];

for (int i = 0; i < plain.length() - padding_len; ++i) {
    cout << output[i];
}
1
2
3
4
加密:
65460bd385393067888da811f27f4849
解密:
cbc 加密