《现代密码学基础》又在讲解经典DES算法了,我打算用 C++ 写了一个。本文介绍了 DES 算法加密的大致步骤和整体流程。

一、DES 算法介绍

1.介绍

DES 算法为密码体制中的对称密码体制,⼜被称为美国数据加密标准。DES 是⼀个分组加密算法,典型的DES64位为分组对数据加密,加密和解密⽤的是同⼀个算法。

DES 使用 56 位的密钥和 64 位的明文块进行加密。DES 算法的分组大小是 64 位,因此,如果需要加密的明文长度不足 64 位,需要进行填充;如果明文长度超过 64 位,则需要使用分组模式进行分组加密。

2.流程

1.png

  • 1.初始置换(IP 置换):将输入的 64 位明文块进行置换和重新排列,生成新的 64 位数据块。

  • 2.加密轮次:DES 加密算法共有 16 个轮次,每个轮次都包括四个步骤:

    • 2.1. 将 64 位数据块分为左右两个 32 位块。

    • 2.2. 右侧 32 位块作为输入,经过扩展、异或、置换等操作生成一个 48 位的数据块。这个 48 位的数据块被称为“轮密钥”,它是根据加密算法的主密钥生成的子密钥。

    • 2.3. 将左侧 32 位块和轮密钥进行异或运算,结果作为新的右侧 32 位块。

    • 2.4. 将右侧 32 位块与原来的左侧 32 位块进行连接,生成一个新的 64 位数据块,作为下一轮的输入。

  • 3.末置换(FP 置换):在最后一个轮次完成后,将经过加密的数据块进行置换和重新排列,得到加密后的 64 位密文。

  • 4.逆置换:在经过 16 轮次计算后,DES 会对最后的结果进行最后一次置换。即为最后的输出结果

总的来说,DES 加密的过程就是通过一系列置换、异或、扩展等运算,将明文分成若干个小块,然后根据主密钥生成一系列的轮密钥,利用轮密钥对每个小块进行加密,最终将加密结果重新组合成一个整体,得到密文。

二、DES 算法详解

注意这里的 DES 用的许多函数都是固定的,公开的函数(因此并没有密码含义),即已知条件,没有密码含义,下面会加粗提心!!!

1.初始置换(Initial Permutation,IP 置换)

IP 置换是将输入的 64 位明文块进行置换和重新排列,生成新的 64 位数据块。

目的:增加加密的混乱程度,使明文中的每一位都能够对后面的加密过程产生影响,提高加密强度。

我们将把 64 位的顺序按下表中规定的顺序放置,图中的数字是在 64 位明文中每个比特的索引位置。注意,在 DES 中,这个置放规则是固定的

1
2
3
4
5
6
7
8
9
10
// 初始置换
int IP[] = { 58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7 };

即将原来位于第 58 个位置的数据放在第 1 个位置,原来位于第 50 个位置的元素放在第 2 个位置,第 42 个放在第 3 个,34->4 以此类推…

初始置换的逆置换(Final Permutation,FP 置换)是将加密后的数据块进行置换和重新排列,得到最终的加密结果,与初始置换相对应。

1
2
3
4
5
6
7
8
9
// 逆置换表
int IP_1[] = {40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25};

2.加密轮次

初始置换完成后,明文被划分成了相同长度(32 位)的左右两部分,记作 L0,R0。接下来就会进行 16 个轮次的加密了。
我们从单独一个轮次来看。首先把目光聚焦在 R0 这里。
4-C.png

右半部分 R0 会作为下一轮次的左半部分 L1 的输入。以此类推,重复 16 轮运算。所以,上面描述的过程可以用以下公式表述。

因为 R0 只有 32 位,R0 会补位到 48 位和本轮次生成的 48 位 K0(马上讲 K0 的生成)输入到 F 轮函数中去。F 函数的输出结果为 32 位,结果 F(R0,K0)会和 L0 进行异或运算作为下一轮次右半部分 R1 的输入。

$$
R_i = F_i(R_{i-1}) ⊕ L_{i-1}
$$

$$
L_i = R_{i-1}
$$

3.f 函数详细过程(R0)

3.png

密码函数 f(R, K)接受两个输入:32 位的数据和 48 位的子密钥。然后:

  • 通过表 E 进行扩展置换,将输入的 32 位数据扩展为 48 位;

  • 将扩展后的 48 位数据与 48 位的子密钥(ki)进行异或运算;

  • 将异或得到的 48 位数据分成 8 个 6 位的块,每一个块通过对应的一个 S 表产生一个 4 位的输出。其中,每个 S 表都是 4 行 16 列。具体的置换过程如下:把 6 位输入中的第 1 位和第 6 位取出来行成一个两位的二进制数 x ,作为 Si 表中的行数(0~3);把 6 位输入的中间 4 位构成另外一个二进制数 y,作为 Si 表的列数(0~15);查出 Si 表中 x 行 y 列所对应的整数,将该整数转换为一个 4 位的二进制数。

  • 把通过 S 表置换得到的 8 个 4 位连在一起,形成一个 32 位的数据。然后将该 32 位数据通过表 P 进行置换(称为 P-置换),置换后得到一个仍然是 32 位的结果数据,这就是 f(R, K)函数的输出。

3.1 拓展 R 到 48 位

将 32 位的 R0 右半部分进行扩展,得到一个 48 位的数据块。同样的,数据拓展也是根据一个固定的置换表。红框中就是我们要补位的数据。
15.png
由此可见,扩展过程的每一位都是根据上述的置换表从输入的 32 位数据块中提取出来的。原始数据的第 32 位被补充到了新增列的第一个,第 5 位被补充到了第二个新增列的第一个,以此类推…

1
2
3
4
5
6
7
8
9
// 扩展置换表,将 32位 扩展至 48位
int E[] = {32, 1, 2, 3, 4, 5,
4, 5, 6, 7, 8, 9,
8, 9, 10, 11, 12, 13,
12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21,
20, 21, 22, 23, 24, 25,
24, 25, 26, 27, 28, 29,
28, 29, 30, 31, 32, 1};

3.2 子密钥 K 的生成

DES 算法采用了每轮子密钥生成的方式来增加密钥的复杂性和安全性。每轮子密钥都是由主密钥(64 位)通过密钥调度算法(Key Schedule Algorithm)生成的。DES 算法的密钥调度算法可以将 64 位的主密钥分成 16 个子密钥,每个子密钥 48 位,用于每轮加密中与输入数据进行异或运算。
通过子密钥生成的流程图来看下整个过程。
2.png

  • 1、将 64 位主密钥经过置换选择 1(Permuted Choice 1 简写为 PC-1)后输出了 56 位,将其分为左右两个 28 位的数据块,分别记为 C0 和 D0。同上面我们讲过的置换规则一样,PC-1 置换函数也是一个固定的置换表,即下图左上角
    5.jpg
    从 PC-1 的置换表中可以看到,舍弃掉的 8 位数据是原始数据中每 8 位数据的最后一位,也就是我们所熟知的奇偶检验位。这 8 位被丢弃是因为它们对于密钥的安全性没有贡献,而且能够使 DES 算法的计算速度更快。
  • 2、对 C0 和 D0 进行循环左移操作。循环左移完成后生成 C1 和 D1。因此,在 16 个轮次的计算当中会得到 16 个 32 位的数据块 C1-C16 和 D1-D16。在 DES 中循环左移也有固定的规则。

对于 Ci 和 Di,若 i 为 1,2,9 或 16,则循环左移一位,否则循环左移两位。

  • 3、 对于 C1,D1,将它们经过置换选择 2(Permuted Choice 2 简写位 PC-2)后,得到 48 位的子密钥 K1,用于每轮加密中与输入数据进行异或运算。PC-2 置换的输入是由 PC-1 置换生成的 56 位的密钥,而它的输出是 48 位的子密钥。PC-2 置换将 56 位的密钥重新排列,丢弃了 8 位并选取了其中的 48 位作为子密钥。PC-2 的置换规则如下:
    5.png
    即 PC-2 置换表的第一行表示选择了输入密钥中的第 14、17、11、24、1 和 5 位,并将它们作为输出子密钥的前 6 位。以此类推

  • 4、至此,经过 PC-2 后的结果就是我们当前轮次的子密钥 K1 了。在整个 DES 加密过程中会生成 16 个 48 位子密钥 K1-K16,分别用于 DES 算法中的 16 轮加密过程,从而保证每轮加密所使用的密钥都是不同的,增加了破解的难度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 密钥置换表,将64位密钥变成56位
int PC_1[] = {57, 49, 41, 33, 25, 17, 9,
1, 58, 50, 42, 34, 26, 18,
10, 2, 59, 51, 43, 35, 27,
19, 11, 3, 60, 52, 44, 36,
63, 55, 47, 39, 31, 23, 15,
7, 62, 54, 46, 38, 30, 22,
14, 6, 61, 53, 45, 37, 29,
21, 13, 5, 28, 20, 12, 4};

// 压缩置换,将56位密钥压缩成48位子密钥
int PC_2[] = {14, 17, 11, 24, 1, 5,
3, 28, 15, 6, 21, 10,
23, 19, 12, 4, 26, 8,
16, 7, 27, 20, 13, 2,
41, 52, 31, 37, 47, 55,
30, 40, 51, 45, 33, 48,
44, 49, 39, 56, 34, 53,
46, 42, 50, 36, 29, 32};

// 每轮左移的位数
int shiftBits[] = {1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1};

3.3 当前轮次的子密钥与拓展的 48 位 R 进行异或运算

当前轮次的子密钥 Ki 与拓展的 48 位 Ri 进行异或运算。运算结果会作为接下来 S 盒替换的输入

3.4 S 盒替换(Substitution Box substitution)

S 盒替换(Substitution Box substitution)是一种在密码学中广泛使用的加密技术。它是将明文中的一组比特映射到密文中的一组比特的过程,用于增强密码的安全性。DES 中 S 盒替换用于将上一轮异或运算的 48 位结果映射到 32 位输出中去。

同样的,S 盒也是一种置换表。在 DES 的每一轮计算中 S 盒都是不一样的。这里我以第一轮计算中的 S 盒为例。从上图中我们看到,S 盒内部有 8 个 S 块,记作 S1-S8。每个 S 块都会接收 6 位字符作为输入并输出四位字符。这里我们以第一个 S 盒 S1 为例。他是一个 4*16 的置换表。
6.png
例如输入101010S1中。S1会将这六位的第一位和第六位拿出来10作为S1的行,中间四位0101拿出来作为S1的列。我们转换成十进制,此时映射到这个 S 盒的位置就是(2,5)对应 S 盒的第3行第6列(索引都从0开始数)。
所以这个输入的结果是 6,将 6 转化为二进制 110,S 盒的输出是 4 位,所以得S(101010)=0110

因此,可以看到 S 盒其实是一种非线性的加密技术,它能够抵御许多传统的密码分析攻击,如差分攻击和线性攻击。

3.5 P 盒替换

7.png
P 盒替换将 S 盒替换的 32 位输出作为输入,经过上述固定的替换表进行替换后即为最后 F 轮函数的结果。

该结果 F(R0,K0)与 L0 进行异或运算得到下一轮的右半部分 R1

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// S盒,每个S盒是4x16的置换表,6位 -> 4位
int S_BOX[8][4][16] = {
{
{14,4,13,1,2,15,11,8,3,10,6,12,5,9,0,7},
{0,15,7,4,14,2,13,1,10,6,12,11,9,5,3,8},
{4,1,14,8,13,6,2,11,15,12,9,7,3,10,5,0},
{15,12,8,2,4,9,1,7,5,11,3,14,10,0,6,13}
},
{
{15,1,8,14,6,11,3,4,9,7,2,13,12,0,5,10},
{3,13,4,7,15,2,8,14,12,0,1,10,6,9,11,5},
{0,14,7,11,10,4,13,1,5,8,12,6,9,3,2,15},
{13,8,10,1,3,15,4,2,11,6,7,12,0,5,14,9}
},
{
{10,0,9,14,6,3,15,5,1,13,12,7,11,4,2,8},
{13,7,0,9,3,4,6,10,2,8,5,14,12,11,15,1},
{13,6,4,9,8,15,3,0,11,1,2,12,5,10,14,7},
{1,10,13,0,6,9,8,7,4,15,14,3,11,5,2,12}
},
{
{7,13,14,3,0,6,9,10,1,2,8,5,11,12,4,15},
{13,8,11,5,6,15,0,3,4,7,2,12,1,10,14,9},
{10,6,9,0,12,11,7,13,15,1,3,14,5,2,8,4},
{3,15,0,6,10,1,13,8,9,4,5,11,12,7,2,14}
},
{
{2,12,4,1,7,10,11,6,8,5,3,15,13,0,14,9},
{14,11,2,12,4,7,13,1,5,0,15,10,3,9,8,6},
{4,2,1,11,10,13,7,8,15,9,12,5,6,3,0,14},
{11,8,12,7,1,14,2,13,6,15,0,9,10,4,5,3}
},
{
{12,1,10,15,9,2,6,8,0,13,3,4,14,7,5,11},
{10,15,4,2,7,12,9,5,6,1,13,14,0,11,3,8},
{9,14,15,5,2,8,12,3,7,0,4,10,1,13,11,6},
{4,3,2,12,9,5,15,10,11,14,1,7,6,0,8,13}
},
{
{4,11,2,14,15,0,8,13,3,12,9,7,5,10,6,1},
{13,0,11,7,4,9,1,10,14,3,5,12,2,15,8,6},
{1,4,11,13,12,3,7,14,10,15,6,8,0,5,9,2},
{6,11,13,8,1,4,10,7,9,5,0,15,14,2,3,12}
},
{
{13,2,8,4,6,15,11,1,10,9,3,14,5,0,12,7},
{1,15,13,8,10,3,7,4,12,5,6,11,0,14,9,2},
{7,11,4,1,9,12,14,2,0,6,10,13,15,3,5,8},
{2,1,14,7,4,10,8,13,15,12,9,0,3,5,6,11}
}
};

// P置换,32位 -> 32位
int P[] = {16, 7, 20, 21,
29, 12, 28, 17,
1, 15, 23, 26,
5, 18, 31, 10,
2, 8, 24, 14,
32, 27, 3, 9,
19, 13, 30, 6,
22, 11, 4, 25 };

4 逆置换(Inverse Permutation)

合并 L16 和 R16 得到一个 64 位的数据,再经过尾置换后得到的就是 64 位的密文。注意:要将 L16 和 R16 合并成 R16L16(即左右互换)。尾置换表 IP-1 如下:

1
2
3
4
5
6
7
8
9
// 尾置换表
int IP_1[] = {40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25};

三、C++代码实现

1、加密

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
#include <iostream>
#include <algorithm>
#include <fstream>
#include <bitset>
#include <string>

using namespace std;

bitset<64> key; // 64位密钥
bitset<48> subKey[16]; // 存放16轮子密钥

// 初始置换表
int IP[] = {58, 50, 42, 34, 26, 18, 10, 2,
60, 52, 44, 36, 28, 20, 12, 4,
62, 54, 46, 38, 30, 22, 14, 6,
64, 56, 48, 40, 32, 24, 16, 8,
57, 49, 41, 33, 25, 17, 9, 1,
59, 51, 43, 35, 27, 19, 11, 3,
61, 53, 45, 37, 29, 21, 13, 5,
63, 55, 47, 39, 31, 23, 15, 7};

// 结尾置换表
int IP_1[] = {40, 8, 48, 16, 56, 24, 64, 32,
39, 7, 47, 15, 55, 23, 63, 31,
38, 6, 46, 14, 54, 22, 62, 30,
37, 5, 45, 13, 53, 21, 61, 29,
36, 4, 44, 12, 52, 20, 60, 28,
35, 3, 43, 11, 51, 19, 59, 27,
34, 2, 42, 10, 50, 18, 58, 26,
33, 1, 41, 9, 49, 17, 57, 25};

/*------------------下面是生成密钥所用表-----------------*/

// 密钥置换表,将64位密钥变成56位
int PC_1[] = {57, 49, 41, 33, 25, 17, 9,
1, 58, 50, 42, 34, 26, 18,
10, 2, 59, 51, 43, 35, 27,
19, 11, 3, 60, 52, 44, 36,
63, 55, 47, 39, 31, 23, 15,
7, 62, 54, 46, 38, 30, 22,
14, 6, 61, 53, 45, 37, 29,
21, 13, 5, 28, 20, 12, 4};

// 压缩置换,将56位密钥压缩成48位子密钥
int PC_2[] = {14, 17, 11, 24, 1, 5,
3, 28, 15, 6, 21, 10,
23, 19, 12, 4, 26, 8,
16, 7, 27, 20, 13, 2,
41, 52, 31, 37, 47, 55,
30, 40, 51, 45, 33, 48,
44, 49, 39, 56, 34, 53,
46, 42, 50, 36, 29, 32};

// 每轮左移的位数
int shiftBits[] = {1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1};

/*------------------下面是密码函数 f 所用表-----------------*/

// 扩展置换表,将 32位 扩展至 48位
int E[] = {32, 1, 2, 3, 4, 5,
4, 5, 6, 7, 8, 9,
8, 9, 10, 11, 12, 13,
12, 13, 14, 15, 16, 17,
16, 17, 18, 19, 20, 21,
20, 21, 22, 23, 24, 25,
24, 25, 26, 27, 28, 29,
28, 29, 30, 31, 32, 1};

// S盒,每个S盒是4x16的置换表,6位 -> 4位
int S_BOX[8][4][16] = {
{
{14,4,13,1,2,15,11,8,3,10,6,12,5,9,0,7},
{0,15,7,4,14,2,13,1,10,6,12,11,9,5,3,8},
{4,1,14,8,13,6,2,11,15,12,9,7,3,10,5,0},
{15,12,8,2,4,9,1,7,5,11,3,14,10,0,6,13}
},
{
{15,1,8,14,6,11,3,4,9,7,2,13,12,0,5,10},
{3,13,4,7,15,2,8,14,12,0,1,10,6,9,11,5},
{0,14,7,11,10,4,13,1,5,8,12,6,9,3,2,15},
{13,8,10,1,3,15,4,2,11,6,7,12,0,5,14,9}
},
{
{10,0,9,14,6,3,15,5,1,13,12,7,11,4,2,8},
{13,7,0,9,3,4,6,10,2,8,5,14,12,11,15,1},
{13,6,4,9,8,15,3,0,11,1,2,12,5,10,14,7},
{1,10,13,0,6,9,8,7,4,15,14,3,11,5,2,12}
},
{
{7,13,14,3,0,6,9,10,1,2,8,5,11,12,4,15},
{13,8,11,5,6,15,0,3,4,7,2,12,1,10,14,9},
{10,6,9,0,12,11,7,13,15,1,3,14,5,2,8,4},
{3,15,0,6,10,1,13,8,9,4,5,11,12,7,2,14}
},
{
{2,12,4,1,7,10,11,6,8,5,3,15,13,0,14,9},
{14,11,2,12,4,7,13,1,5,0,15,10,3,9,8,6},
{4,2,1,11,10,13,7,8,15,9,12,5,6,3,0,14},
{11,8,12,7,1,14,2,13,6,15,0,9,10,4,5,3}
},
{
{12,1,10,15,9,2,6,8,0,13,3,4,14,7,5,11},
{10,15,4,2,7,12,9,5,6,1,13,14,0,11,3,8},
{9,14,15,5,2,8,12,3,7,0,4,10,1,13,11,6},
{4,3,2,12,9,5,15,10,11,14,1,7,6,0,8,13}
},
{
{4,11,2,14,15,0,8,13,3,12,9,7,5,10,6,1},
{13,0,11,7,4,9,1,10,14,3,5,12,2,15,8,6},
{1,4,11,13,12,3,7,14,10,15,6,8,0,5,9,2},
{6,11,13,8,1,4,10,7,9,5,0,15,14,2,3,12}
},
{
{13,2,8,4,6,15,11,1,10,9,3,14,5,0,12,7},
{1,15,13,8,10,3,7,4,12,5,6,11,0,14,9,2},
{7,11,4,1,9,12,14,2,0,6,10,13,15,3,5,8},
{2,1,14,7,4,10,8,13,15,12,9,0,3,5,6,11}
}
};

// P置换,32位 -> 32位
int P[] = {16, 7, 20, 21,
29, 12, 28, 17,
1, 15, 23, 26,
5, 18, 31, 10,
2, 8, 24, 14,
32, 27, 3, 9,
19, 13, 30, 6,
22, 11, 4, 25 };

/**********************************************************************/
/* */
/* 下面是DES算法实现 */
/* */
/**********************************************************************/

/**
* 密码函数f,接收32位数据和48位子密钥,产生一个32位的输出
*/
bitset<32> f(bitset<32> R, bitset<48> k)
{
bitset<48> expandR;
// 第一步:扩展置换,32 -> 48
for(int i=0; i<48; ++i)
expandR[47-i] = R[32-E[i]];
// 第二步:异或
expandR = expandR ^ k;
// 第三步:查找S_BOX置换表
bitset<32> output;
int x = 0;
for(int i=0; i<48; i=i+6)
{
int row = expandR[47-i]*2 + expandR[47-i-5];
int col = expandR[47-i-1]*8 + expandR[47-i-2]*4 + expandR[47-i-3]*2 + expandR[47-i-4];
int num = S_BOX[i/6][row][col];
bitset<4> binary(num);
output[31-x] = binary[3];
output[31-x-1] = binary[2];
output[31-x-2] = binary[1];
output[31-x-3] = binary[0];
x += 4;
}
// 第四步:P-置换,32 -> 32
bitset<32> tmp = output;
for(int i=0; i<32; ++i)
output[31-i] = tmp[32-P[i]];
return output;
}

/**
* 对56位密钥的前后部分进行左移
*/
bitset<28> leftShift(bitset<28> k, int shift)
{
bitset<28> tmp = k;
for(int i=27; i>=0; --i)
{
if(i-shift<0)
k[i] = tmp[i-shift+28];
else
k[i] = tmp[i-shift];
}
return k;
}

/**
* 生成16个48位的子密钥
*/
void generateKeys()
{
bitset<56> realKey; // 密钥key(64) -> 密钥realKey(56) -> 最终每一轮的密钥compressKey(48)
bitset<28> left;
bitset<28> right;
bitset<48> compressKey;
// 去掉奇偶标记位,将64位密钥变成56位
for (int i=0; i<56; ++i)
realKey[55-i] = key[64 - PC_1[i]];
// 生成子密钥,保存在 subKeys[16] 中
for(int round=0; round<16; ++round)
{
// 前28位与后28位
for(int i=28; i<56; ++i)
left[i-28] = realKey[i];
for(int i=0; i<28; ++i)
right[i] = realKey[i];
// 左移
left = leftShift(left, shiftBits[round]);
right = leftShift(right, shiftBits[round]);
// 压缩置换,由56位得到48位子密钥
for(int i=28; i<56; ++i)
realKey[i] = left[i-28];
for(int i=0; i<28; ++i)
realKey[i] = right[i];
for(int i=0; i<48; ++i)
compressKey[47-i] = realKey[56 - PC_2[i]];
subKey[round] = compressKey;
}
}

/**
* 工具函数:将char字符数组转为二进制
*/
bitset<64> charToBitset(const char s[8]) // 比如说明文或者密文都需要转成二进制
{
bitset<64> bits;
for(int i=0; i<8; ++i)
for(int j=0; j<8; ++j)
bits[i*8+j] = ((s[i]>>j) & 1);
return bits;
}

/**
* DES加密
*/
bitset<64> encrypt(bitset<64>& plain)
{
bitset<64> cipher;
bitset<64> currentBits;
bitset<32> left;
bitset<32> right;
bitset<32> newLeft;
// 第一步:初始置换IP
for(int i=0; i<64; ++i)
currentBits[63-i] = plain[64-IP[i]];
// 第二步:获取 Li 和 Ri
for(int i=32; i<64; ++i)
left[i-32] = currentBits[i];
for(int i=0; i<32; ++i)
right[i] = currentBits[i];
// 第三步:共16轮迭代
for(int round=0; round<16; ++round)
{
newLeft = right;
right = left ^ f(right,subKey[round]);
left = newLeft;
}
// 第四步:合并L16和R16,注意合并为 R16L16
for(int i=0; i<32; ++i)
cipher[i] = left[i];
for(int i=32; i<64; ++i)
cipher[i] = right[i-32];
// 第五步:结尾置换IP-1
currentBits = cipher;
for(int i=0; i<64; ++i)
cipher[63-i] = currentBits[64-IP_1[i]];
// 返回密文
return cipher;
}

/**
* DES解密
*/
bitset<64> decrypt(bitset<64>& cipher)
{
bitset<64> plain; // 解密后的明文
bitset<64> currentBits;
bitset<32> left;
bitset<32> right;
bitset<32> newLeft;
// 第一步:初始置换IP
for(int i=0; i<64; ++i)
currentBits[63-i] = cipher[64-IP[i]];
// 第二步:获取 Li 和 Ri
for(int i=32; i<64; ++i)
left[i-32] = currentBits[i];
for(int i=0; i<32; ++i)
right[i] = currentBits[i];
// 第三步:共16轮迭代(子密钥逆序应用)
for(int round=0; round<16; ++round)
{
newLeft = right;
right = left ^ f(right,subKey[15-round]);
left = newLeft;
}
// 第四步:合并L16和R16,注意合并为 R16L16
for(int i=0; i<32; ++i)
plain[i] = left[i];
for(int i=32; i<64; ++i)
plain[i] = right[i-32];
// 第五步:结尾置换IP-1
currentBits = plain;
for(int i=0; i<64; ++i)
plain[63-i] = currentBits[64-IP_1[i]];
// 返回明文
return plain;
}


/**********************************************************************/
/* 测试: */
/* 1.将一个 64 位的字符串加密, 把密文写入文件 a.txt */
/* 2.读取文件 a.txt 获得 64 位密文,解密之后再写入 b.txt */
/**********************************************************************/

int main() {
string s = "romantic";
string k = "12345678";
bitset<64> plain = charToBitset(s.c_str());
key = charToBitset(k.c_str());
// 生成16个子密钥
generateKeys();
// 密文写入 a.txt
bitset<64> cipher = encrypt(plain);
fstream file1;
file1.open("D://a.txt", ios::binary | ios::out);
file1.write((char*)&cipher,sizeof(cipher));
file1.close();

// 读文件 a.txt
bitset<64> temp;
file1.open("D://a.txt", ios::binary | ios::in);
file1.read((char*)&temp, sizeof(temp));
file1.close();

// 解密,并写入文件 b.txt
bitset<64> temp_plain = decrypt(temp);
file1.open("D://b.txt", ios::binary | ios::out);
file1.write((char*)&temp_plain,sizeof(temp_plain));
file1.close();

return 0;
}

2.解密

那么,在对 64 位的数据加解密成功以后,对文件的加解密就很简单了!只需要每次读 64 位,加密以后,将 64 位的密文写入另外一个文件……如此循环,直到文件尾。下面是对一张图片进行加密和解密的测试代码:

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
int main() {
string k = "12345678";
key = charToBitset(k.c_str());
generateKeys(); // 生成16个子密钥

// 将文件 flower.jpg 加密到 cipher.txt 中
ifstream in;
ofstream out;
in.open("D://flower.jpg", ios::binary);
out.open("D://cipher.txt", ios::binary);
bitset<64> plain;
while(in.read((char*)&plain, sizeof(plain)))
{
bitset<64> cipher = encrypt(plain);
out.write((char*)&cipher, sizeof(cipher));
plain.reset(); // 置0
}
in.close();
out.close();

// 解密 cipher.txt,并写入图片 flower1.jpg
in.open("D://cipher.txt", ios::binary);
out.open("D://flower1.jpg", ios::binary);
while(in.read((char*)&plain, sizeof(plain)))
{
bitset<64> temp = decrypt(plain);
out.write((char*)&temp, sizeof(temp));
plain.reset(); // 置0
}
in.close();
out.close();

return 0;
}

加强版:

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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
#pragma once
#include<iostream>
#include<cstdio>
#include<string>
#include<algorithm>
using namespace std;

//进行标记选择
int flag;
string k[16]; //16轮密钥存储
//PC1选位表
int pc1Table[56] =
{
57,49,41,33,25,17,9,1,
58,50,42,34,26,18,10,2,
59,51,43,35,27,19,11,3,
60,52,44,36,63,55,47,39,
31,23,15,7,62,54,46,38,
30,22,14,6,61,53,45,37,
29,21,13,5,28,20,12,4
};
//左移位数表
int loopTable[16] =
{
1,1,2,2,2,2,2,2,1,2,2,2,2,2,2,1
};
//PC2选位表
int pc2Table[48] =
{
14,17,11,24,1,5,
3,28,15,6,21,10,
23,19,12,4,26,8,
16,7,27,20,13,2,
41,52,31,37,47,55,
30,40,51,45,33,48,
44,49,39,56,34,53,
46,42,50,36,29,32
};
//置换IP表
int ipTable[64] =
{
58,50,42,34,26,18,10,2,
60,52,44,36,28,20,12,4,
62,54,46,38,30,22,14,6,
64,56,48,40,32,24,16,8,
57,49,41,33,25,17,9,1,
59,51,43,35,27,19,11,3,
61,53,45,37,29,21,13,5,
63,55,47,39,31,23,15,7
};
//E扩展置换表
int extendTable[48] =
{
32,1,2,3,4,5,
4,5,6,7,8,9,
8,9,10,11,12,13,
12,13,14,15,16,17,
16,17,18,19,20,21,
20,21,22,23,24,25,
24,25,26,27,28,29,
28,29,30,31,32,1
};
//S盒
int sBox[8][4][16] =
{
//S1
14,4,13,1,2,15,11,8,3,10,6,12,5,9,0,7,
0,15,7,4,14,2,13,1,10,6,12,11,9,5,3,8,
4,1,14,8,13,6,2,11,15,12,9,7,3,10,5,0,
15,12,8,2,4,9,1,7,5,11,3,14,10,0,6,13,
//S2
15,1,8,14,6,11,3,4,9,7,2,13,12,0,5,10,
3,13,4,7,15,2,8,14,12,0,1,10,6,9,11,5,
0,14,7,11,10,4,13,1,5,8,12,6,9,3,2,15,
13,8,10,1,3,15,4,2,11,6,7,12,0,5,14,9,
//S3
10,0,9,14,6,3,15,5,1,13,12,7,11,4,2,8,
13,7,0,9,3,4,6,10,2,8,5,14,12,11,15,1,
13,6,4,9,8,15,3,0,11,1,2,12,5,10,14,7,
1,10,13,0,6,9,8,7,4,15,14,3,11,5,2,12,
//S4
7,13,14,3,0,6,9,10,1,2,8,5,11,12,4,15,
13,8,11,5,6,15,0,3,4,7,2,12,1,10,14,9,
10,6,9,0,12,11,7,13,15,1,3,14,5,2,8,4,
3,15,0,6,10,1,13,8,9,4,5,11,12,7,2,14,
//S5
2,12,4,1,7,10,11,6,8,5,3,15,13,0,14,9,
14,11,2,12,4,7,13,1,5,0,15,10,3,9,8,6,
4,2,1,11,10,13,7,8,15,9,12,5,6,3,0,14,
11,8,12,7,1,14,2,13,6,15,0,9,10,4,5,3,
//S6
12,1,10,15,9,2,6,8,0,13,3,4,14,7,5,11,
10,15,4,2,7,12,9,5,6,1,13,14,0,11,3,8,
9,14,15,5,2,8,12,3,7,0,4,10,1,13,11,6,
4,3,2,12,9,5,15,10,11,14,1,7,6,0,8,13,
//S7
4,11,2,14,15,0,8,13,3,12,9,7,5,10,6,1,
13,0,11,7,4,9,1,10,14,3,5,12,2,15,8,6,
1,4,11,13,12,3,7,14,10,15,6,8,0,5,9,2,
6,11,13,8,1,4,10,7,9,5,0,15,14,2,3,12,
//S8
13,2,8,4,6,15,11,1,10,9,3,14,5,0,12,7,
1,15,13,8,10,3,7,4,12,5,6,11,0,14,9,2,
7,11,4,1,9,12,14,2,0,6,10,13,15,3,5,8,
2,1,14,7,4,10,8,13,15,12,9,0,3,5,6,11
};
//P换位表
int pTable[32] =
{
16,7,20,21,
29,12,28,17,
1,15,23,26,
5,18,31,10,
2,8,24,14,
32,27,3,9,
19,13,30,6,
22,11,4,25
};
//逆置换IP^-1表
int ipReverseTable[64] = {
40,8,48,16,56,24,64,32,
39,7,47,15,55,23,63,31,
38,6,46,14,54,22,62,30,
37,5,45,13,53,21,61,29,
36,4,44,12,52,20,60,28,
35,3,43,11,51,19,59,27,
34,2,42,10,50,18,58,26,
33,1,41,9,49,17,57,25
};
//将输入的东西全部看成字符
//字符转二进制
string charToBinary(char c)
{
int i, b = c, k = 0, flag = 0;
string result;
//负数就是中文字符
if (b < 0)
{
b = -b;
flag = 1;
}
//英文字符转换成ASCII的倒序,所以后面需要进行逆序
while (k < 8) //这里的8表示char是1个字节=8bit
{
if (b) //这里将ASCII里的字符转换为二进制
{
result += ((b % 2) + '0'); // 其中这里+'0',表示将数字转换为字符
b /= 2;
}
else result += '0';
k++;
}
//汉字字符处理
if (flag)//判断是否为汉字
{
for (i = 0; i < result.length(); i++) //此时因为是负数,源码、反码、补码不相等,需要置换
{
if (result[i] == '0') result[i] = '1'; // 反码:最高最不变,其它的0->1,,1->0
else result[i] = '0';
}
for (i = 0; result[i] != '0'; i++)
{
result[i] = '0'; //补码 :反码加+1
}
result[i] = '1';
}
reverse(result.begin(), result.end()); //将结果逆序,成为最终的二进制
return result;
}
//二进制转整型
int binaryToInt(string s)
{
int i, result = 0, p = 1;
for (i = s.length() - 1; i >= 0; i--)
{
result += ((s[i] - '0') * p); //数字字符转成字符
p *= 2;
}
return result;
}
//整型转二进制
string intToBinary(int i)
{
int k = 0;
string result;
while (k < 4) //此处,处理进入S盒后取出的数据转为2进制,此处最多用4bit

{
if (i)
{
result += ((i % 2) + '0');
i /= 2;
}
else result += '0';
k++;
}
reverse(result.begin(), result.end());
return result;
}
//异或运算
string xorAB(string a, string b)
{
int i;
string result;
for (i = 0; i < a.length(); i++)
{ //+'0':表示数字转化为数字字符
result += (((a[i] - '0') ^ (b[i] - '0')) + '0'); // -'0':表示数字字符转化为数字
}
return result;
}
//f函数
string f(string right, string k) //其中right 为明文的右分支R0--R16,k当前加密轮密钥
{
int i, temp;
string extendBinary, result, b0; //extendBinary用来存放E扩展32bit~48bit的内容
string b[8], row, col;
string b8, pb;
for (i = 0; i < 48; i++)
{
extendBinary += right[extendTable[i] - 1];
}
b0 = xorAB(extendBinary, k);//扩展后的内容与此轮密钥异或操作并将结果存入b0中
for (i = 0; i < 8; i++) //将b0的内容分成八份,每份六bit,为进入S盒做准备
{
b[i] = b0.substr(i * 6, 6);
}
for (i = 0; i < 8; i++)
{
//6bit的第一位和第六位作为行坐标
row = b[i].substr(0, 1) + b[i].substr(5, 1);
//6bit的第二至五位作为纵坐标
col = b[i].substr(1, 4);
//进行查表
temp = sBox[i][binaryToInt(row)][binaryToInt(col)];
//转到b8中合并----48bit压缩到32bit
b[i] = intToBinary(temp);
b8 += b[i];
}
//进行P盒置换
for (i = 0; i < 32; i++)
{
pb += b8[pTable[i] - 1];
}
//f轮函数结束,返回pb
return pb;
}
//明文/密文处理
string wen(string wenBinary[], int num)
{
int i, j;
string ipWenBinary[100]; //保存明文
string left[17], right[17], temp, result; //分为左右两个分支
for (i = 0; i < num; i++)
{
temp = ""; //一个暂存明文的字符串
//进行IP置换
for (j = 0; j < 64; j++) //明文为64bit
{
temp += wenBinary[i][ipTable[j] - 1];
}
ipWenBinary[i] = temp;
}
//进行左右分支,分为left:L0,,,right:R0
for (i = 0; i < num; i++)
{
left[0] = ipWenBinary[i].substr(0, 32);
right[0] = ipWenBinary[i].substr(32, 32);
//
for (j = 0; j < 16; j++) //进行16次循环
{
left[j + 1] = right[j]; // left: L1 = R0
//加密和解密的区别
// flag 为全局变量
if (flag == 1) // flage = 1进行加密,否则进行解密
right[j + 1] = xorAB(left[j], f(right[j], k[j]));
else
//倒着进行解密
right[j + 1] = xorAB(left[j], f(right[j], k[15 - j]));
}
temp = right[j] + left[j]; //经过16轮加密/解密,将左右32bit合并
//将加密后的密文进行最后的置换,实际上和初始置换是对称的~!
//每块的加密结果都和在result中,加密可以直接输出比特流
// 进行IP的逆置换
for (j = 0; j < 64; j++)
{
result += temp[ipReverseTable[j] - 1];
}
}
//解密结果输出的是字符
if (flag == 2)
{
string ch;
for (i = 0; i < num * 8; i++) //一个模块是8个bit
{
ch += binaryToInt(result.substr(8 * i, 8)); //每8bit进行一次二进制转整型
}
result = ch;
}
return result;
}
//密钥处理
void miyao()
{
int i, j;
string miyao, miyaoBinary, pc1MiyaoBinary;
string c[17], d[17], temp, pc2Temp;
cout << "请输入密钥:";
while (cin >> miyao)
{
if (miyao.length() < 9) break;
else cout << "密钥不能超过8位,请重新输入:";
}
for (i = 0; i < miyao.length(); i++)
{
miyaoBinary += charToBinary(miyao[i]);
}
//密钥长度不足64bit,补'0'
//64位中,只有56位参与运算,其中8位为校验位
while (miyaoBinary.length() % 64 != 0)
{
miyaoBinary += '0';
}
//从64bit密钥中依据PC-1盒子取出56bit
for (i = 0; i < 56; i++)
{
pc1MiyaoBinary += miyaoBinary[pc1Table[i] - 1];
}
//56bit分成左右两部分
// 左右两部分都为28bit
c[0] = pc1MiyaoBinary.substr(0, 28);
d[0] = pc1MiyaoBinary.substr(28, 28);
产生16轮加密需要的密钥,存入全局变量k[]中
for (i = 1; i <= 16; i++)
{
//根据循环移位表,确定生成该轮密钥移位数目
c[i] = c[i - 1].substr(loopTable[i - 1], 28 - loopTable[i - 1]) + c[i - 1].substr(0, loopTable[i - 1]);
d[i] = d[i - 1].substr(loopTable[i - 1], 28 - loopTable[i - 1]) + d[i - 1].substr(0, loopTable[i - 1]);
//移位后将其合并
temp = c[i] + d[i];
pc2Temp = "";
// 通过PC2成为48bit密钥
for (j = 0; j < 48; j++)
{
pc2Temp += temp[pc2Table[j] - 1];
}
k[i - 1] = pc2Temp;//从1`16
}
}
// 输出函数
void DES()
{
int i, j, num;
string wenString, wenBinary[100], temp;
while (1)
{
cout << "----------------------------------------------" << endl;
cout << "***** 请选择所需功能: *****" << endl;
cout << "***** 1. 加密 *****" << endl;
cout << "***** 2. 解密 *****" << endl;
cout << "***** 0.退出程序 *****"<< endl;
cout << "----------------------------------------------" << endl;
cin >> flag;
if (!flag)
break;
else if ((flag != 1) && (flag != 2))
{
cout << "输入不合法,请重新输入!" << endl << endl;
continue;
}
num = 0;
miyao();
getchar();
switch (flag)
{
case 1:
cout << "请输入明文:";
getline(cin, wenString);//输入明文
//将明文转成二进制
for (i = 0; i < wenString.length(); i++)
{
temp += charToBinary(wenString[i]);
//字符每满8bit为一组,最后一组可以不满8bit,后面会补0
if (((i + 1) % 8 == 0) || (((i + 1) % 8 != 0) && (i == wenString.length() - 1)))
{
wenBinary[num++] = temp;
temp = "";
}
}
//最后一组不满64位就补零,补的零,一定是八的整数倍,二进制00000000为null空字符,不输出
while (wenBinary[num - 1].length() % 64 != 0)
{
wenBinary[num - 1] += '0';
}
cout << "加密结果为(二进制):" << wen(wenBinary, num) << endl << endl;
break;
case 2:
cout << "请输入密文(二进制):";
cin >> wenString;
for (i = 0; i * 64 < wenString.length(); i++)
{
wenBinary[num++] = wenString.substr(i * 64, 64);
}
cout << "解密结果为(字符):" << wen(wenBinary, num) << endl << endl;
break;
case 3:
exit(0);
default :
cout << "输入错误,请重新输入" << endl;
}
}
}

void menu()
{
cout << "**********************************************" << endl;
cout << "**********************************************" << endl;
cout << "***********欢迎来到DES加密测试系统************" << endl;
cout << "**********************************************" << endl;
cout << "**********************************************" << endl;
}
int main()
{
menu();
DES();
return 0;
}

四、总结

5、DES 的优缺点
优点:

安全性高:DES 算法使用密钥进行加密和解密,相同的明文使用不同的密钥加密后得到的密文是不同的。密钥越长,加密的安全性就越高。
算法简单:DES 算法的加密和解密过程非简单,基于对称加密,使用相同的 key 进行加解密。
适用广泛:DES 算法是最早也是最广泛使用的加密算法之一,被广泛应用于电子商务、电子邮件、虚拟私人网络等领域,具有广泛的适用性和可移植性。
缺点:

密钥长度较短:DES 算法使用 56 位密钥,虽然在当时足够安全,但在当前计算机的处理能力下,已经不足以保证加密的安全性,易受到暴力破解攻击。
无法抵抗差分密码分析攻击:DES 算法无法抵抗差分密码分析攻击,这种攻击可以通过比较相同明文的密文,分析加密算法的行为并推断出密钥。
比较慢:由于 DES 算法是一种分组密码算法,需要对 64 位的明文进行加密,加密速度比较慢,不适用于对大量数据进行实时加密和解密。