DES(Data Encryption_Standard)

DES是一种对称密钥的块加密算法。谓之 “对称密钥”,是因为加密、解密用的密钥是一样的(这不同于 RSA 等非对称密钥体系)。谓之 “块加密”,是因为这种算法把明文划分为很多个等长的块(block),对每个块进行加密,最后以某种手段拼在一起。“块加密”亦称“分组加密”。

DES概述

DES 的功能是:给定一个 64 位的明文和一个 64 位的密钥,输出一个 64 位的密文。这个密文可以用相同的密钥解密。所谓“64位的密钥”,其实里面只有56位在起作用。剩余的位可以直接丢弃,或者当作奇偶校验位。

虽然 DES 一次只能加密 8 个字节,但我们只需要把明文划分成每 8 个字节一组的块,就可以实现任意长度明文的加密。如果明文长度不是 8 个字节的倍数,还得进行填充。现在流行的填充方式是 PKCS7 / PKCS5

算法特点:分组比较短、密钥太短、密码生命周期短、运算速度较慢。

Key(密钥):为7个字节共56位,是DES算法的工作密钥(若说密钥为64位,其指的也是56位的秘钥加上8位奇偶校验位,奇偶校验位为第8,16,24,32,40,48,56,64位)
Data(数据):为8个字节64位,是要被加密或被解密的数据
Mode(模式): 为DES的工作方式,有两种:加密或解密。

DES算法框架

DES 算法是在 Feistel network (费斯妥网络)的基础上执行的。以下是 DES 算法的流程图:

可以看到整个算法分为两大部分——迭代加密(左边的 16 轮迭代操作),以及密钥调度(右边生成子密钥的算法)。

所谓密钥调度,就是从一把 64-bit 的主钥匙,得到 16 把 48-bit 的子钥匙。

每一轮迭代,都是接收一组 <font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">L, R</font>,返回 <font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">L', R'</font> ,作为下一轮迭代的 <font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">L, R</font> . 迭代过程如下:

L’=R

R’=L^F(R,subkey)

其中F函数(称为轮函数)是整个算法的核心,功能是:以一个子密钥,加密 32-bit 的信息。

密钥调度算法


首先,采用“选择置换1 (PC-1)”,从 64 位 key 里面选出 56 位。这一步属于encode,对信息安全没有帮助。PC-1 方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
# 选择置换1,输入 key 为长度为 64 的 0/1 数组
# 从64位输入密钥中选择56位,分为左右两个28位半密钥
def PC1(key):
pc1_l = [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]
pc1_r = [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]

return [key[x-1] for x in pc1_l], [key[x-1] for x in pc1_r]

经过 PC-1 之后,我们有了左、右两个半密钥,长度都是 28 位。接下来,我们每一轮把左、右半密钥左旋几位,再调用 PC-2 方法来造子密钥。框架如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 子密钥生成算法,由一个64位主密钥导出16个48位子密钥
def keyGen(key):
assert len(key) == 64

l, r = PC1(key)
off = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
res = []

for x in range(16):
l = leftRotate(l, off[x])
r = leftRotate(r, off[x])

res.append(PC2(l + r))

return res

其中, <font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">leftRotate</font> 是循环左移,实现如下:

1
2
3
4
5
# 循环左移off位
def leftRotate(a, off):
return a[off:] + a[:off]

assert leftRotate([0, 1, 0, 1, 1], 2) == [0, 1, 1, 0, 1]

PC-2 又是一个简单置换,用于从左右半密钥拼起来的 56 位密钥中,选取 48 位作为一个子密钥。实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 选择置换2
# 从56位的密钥中选取48位子密钥
def PC2(key):
assert len(key) == 56

pc2 = [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]
return [key[x-1] for x in pc2]

这样,我们就实现了密钥调度算法。执行 <font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">keyGen</font> ,即可获得 16 把子密钥,长度均为 48-bit. 不难看出,整个密钥调度的过程都是对主密钥的 encode. 生成这么多子密钥的目的,是使得加密迭代变得更加复杂、难以分析。

加密迭代

加密迭代的过程是先把信息进行一次初始置换(IP置换);再进行 16 轮迭代;最后再给 (R+L) 这个数组来一次最终置换(FP置换),即可输出作为密文。框架如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def DES(plain, key, method):
subkeys = keyGen(int2bin(key, 64))

if method == 'decrypt':
subkeys = subkeys[::-1]

m = IP(int2bin(plain, 64))

l, r = np.array(m, dtype=int).reshape(2, -1).tolist()

for i in range(16):
l, r = goRound(l, r, subkeys[i])

return bin2int(FP(r + l))

在这里, <font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">goRound</font> 是提供 <font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">L, R</font> 和本轮加密使用的子密钥,返回 <font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">L', R'</font> 以供下一轮迭代。 <font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">IP</font><font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">FP</font> 都是简单置换,对于密码安全没有任何意义,只是一个历史遗留原因。规定如下:

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
# 初始置换
def IP(a):
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]
return [a[x-1] for x in ip]
testM = [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1]
assert IP(testM) == [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0]

# 最终置换
def FP(a):
fp = [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]
return [a[x-1] for x in fp]

注意到 FP 就是 IP 的逆函数。这是为了保证“加密、解密算法几乎完全相同”。而至于 <font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">goRound</font> ,我们知道它干的事情是

L’=R

R’=L^F(R,subkey)

代码实现如下:

1
2
def goRound(l, r, subKey):
return r, binXor(l, Feistel(r, subKey))

因此,DES 的安全性在很大程度上取决于 F 函数,也就是轮函数。那么 Feistel 函数是干了什么事呢?来看下面一张流程图:

一个 32-bit 的块,经过一个扩张(Expand函数),变成 48 位,然后与子密钥异或。得到的 48-bit 的结果分为 8 组,每一组是 6-bit 的数据,丢进对应的 S 盒,输出 4-bit 的信息。把这些输出收集起来,一共是 4*8 = 32 位,做一次置换 (P 置换),得到 32-bit 的结果。这与输进来的 32-bit 信息是等长度的。

1
2
3
4
5
6
7
8
9
10
# F函数,用于处理一个半块
def Feistel(a, subKey):
assert len(a) == 32
assert len(subKey) == 48

t = binXor(Expand(a), subKey)
t = S(t)
t = P(t)

return t

Expand 算法是指定的,P 置换是一个简单置换,因此都是 encode 过程。而这个 32-bit 的半块与 subkey 的混合过程,以及 S 盒提供的强有力的混淆能力,提供了 DES 体系的核心安全性。

Expand 算法如下:

1
2
3
4
5
6
7
8
9
10
11
12
# 扩张置换,将32位的数据扩展到48位
def Expand(a):
assert len(a) == 32
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]
return [a[x-1] for x in e]

P 置换如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
# P置换
def P(a):
assert len(a) == 32

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]
return [a[x-1] for x in p]

需要注意一点:这个 P 置换是精心设计的,使得这一轮同一个 S 盒 输出的四个 bit,在下一回合的扩张之后,交由四个不同的 S 盒去处理。

接下来唯一要处理的就是 S 盒了。它一共有 8 张表,表长成下面这个样子:

扩张之后的半块与子密钥异或之后,得到了 48 位结果;这些结果分成 8 个组,然后第一组使用 S1 这张表进行变换,第二组使用 S2 进行变换……依次类推。现在我们假设第二组是 <font style="color:rgb(49, 59, 63);background-color:rgb(229, 239, 245);">101100</font> ,来看它在 S盒变换之后会得到什么结果:

由于这是第二组,故查询 S2 表。
它的首位、末尾是 10 ,故查询第三行。
它的中间 4 位是 0110 ,查表知结果是 13.
把 13 转为二进制,得到 1101 ,于是这就是输出。

代码实现:
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
# S盒变换,输入48位,输出32位
def S(a):
assert len(a) == 48

S_box = [[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]]

a = np.array(a, dtype=int).reshape(8, 6)
res = []

for i in range(8):
# 用 S_box[i] 处理6位a[i],得到4位输出
p = a[i]
r = S_box[i][bin2int([p[0], p[5], p[1], p[2], p[3], p[4]])]
res.append(int2bin(r, 4))

res = np.array(res).flatten().tolist()
assert len(res) == 32

return res

完整代码

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
from functools import reduce
import numpy as np

def pad_data(data):
""" 对输入数据进行 64-bit 块填充(PKCS7 填充) """
byte_data = data.encode('utf-8') # 转换为字节
pad_len = 8 - (len(byte_data) % 8) # 计算填充长度
byte_data += bytes([pad_len] * pad_len) # 添加填充字节
return [int.from_bytes(byte_data[i:i+8], 'big') for i in range(0, len(byte_data), 8)]

def unpad_data(data_blocks):
""" 去除填充并转换回字符串 """
byte_data = b''.join(block.to_bytes(8, 'big') for block in data_blocks)
pad_len = byte_data[-1] # 获取填充的字节数
return byte_data[:-pad_len].decode('utf-8')


# 整数转二进制数组,指定位长 n,大端序
def int2bin(a, n):
assert 0<=n and a < 2**n # 断言,确保 n 为非负数,且 a 在 n 位二进制能表示的范围内
res = np.zeros(n, dtype = int)

for x in range(n):
res[n-x-1] = a % 2
a = a // 2
return res.tolist()

assert int2bin(0x1a, 10) == [0, 0, 0, 0, 0, 1, 1, 0, 1, 0] #验证

# 二进制数组转整数,大端序 左移方式 (x * 2 + y) 处理二进制位。
def bin2int(a):
return reduce(lambda x,y: x*2+y, a)

assert bin2int([0, 0, 0, 0, 0, 1, 1, 0, 1, 0]) == 0x1a

# 循环左移off位
def leftRotate(a, off):
return a[off:] + a[:off]

assert leftRotate([0, 1, 0, 1, 1], 2) == [0, 1, 1, 0, 1]

# 异或
def binXor(a, b):
assert len(a) == len(b)
return [x^y for x, y in zip(a, b)]

assert binXor([1, 1, 0, 1], [0, 1, 1, 0]) == [1, 0, 1, 1]

# 初始置换
def IP(a):
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]
return [a[x-1] for x in ip]

testM = [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1]
assert IP(testM) == [1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0]

# 最终置换
def FP(a):
fp = [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]
return [a[x-1] for x in fp]

# 选择置换1
# 从64位输入密钥中选择56位,分为左右两个28位半密钥
def PC1(key):
pc1_l = [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]
pc1_r = [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]

return [key[x-1] for x in pc1_l], [key[x-1] for x in pc1_r]

testKey = [0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1]
testL, testR = PC1(testKey)
assert testL + testR == [1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1]

# 选择置换2
# 从56位的密钥中选取48位子密钥
def PC2(key):
assert len(key) == 56

pc2 = [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]
return [key[x-1] for x in pc2]

# 子密钥生成算法,由一个64位主密钥导出16个48位子密钥
def keyGen(key):
assert len(key) == 64

l, r = PC1(key)
off = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]
res = []

for x in range(16):
l = leftRotate(l, off[x])
r = leftRotate(r, off[x])

res.append(PC2(l + r))

return res

assert keyGen(testKey)[-1] == [1, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1]

# S盒变换,输入48位,输出32位
def S(a):
assert len(a) == 48

S_box = [[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]]

a = np.array(a, dtype=int).reshape(8, 6)
res = []

for i in range(8):
# 用 S_box[i] 处理6位a[i],得到4位输出
p = a[i]
r = S_box[i][bin2int([p[0], p[5], p[1], p[2], p[3], p[4]])]
res.append(int2bin(r, 4))

res = np.array(res).flatten().tolist()
assert len(res) == 32

return res

# 扩张置换,将32位的半块扩展到48位
def Expand(a):
assert len(a) == 32
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]
return [a[x-1] for x in e]

# P置换
def P(a):
assert len(a) == 32

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]
return [a[x-1] for x in p]

# F函数,用于处理一个半块
def Feistel(a, subKey):
assert len(a) == 32
assert len(subKey) == 48

t = binXor(Expand(a), subKey)
t = S(t)
t = P(t)

return t

def goRound(l, r, subKey):
return r, binXor(l, Feistel(r, subKey))

def DES(plain, key, method):
subkeys = keyGen(int2bin(key, 64))

if method == 'decrypt':
subkeys = subkeys[::-1]

m = IP(int2bin(plain, 64))

l, r = np.array(m, dtype=int).reshape(2, -1).tolist()

for i in range(16):
l, r = goRound(l, r, subkeys[i])

return bin2int(FP(r + l))


# print(hex(DES(0x11aabbccddeeff01, 0xcafababedeadbeaf, 'encrypt')))
# # 0x2973a7e54ec730a3
# print(hex(DES(0x2973a7e54ec730a3, 0xcafababedeadbeaf, 'decrypt')))
# # 0x11aabbccddeeff01


# ECB模式加密
def des_ecb_encrypt(plain_blocks, key):
"""
使用 DES 电子密码本(ECB)模式对明文块列表进行加密。

:param plain_blocks: list[int],64-bit 明文块列表
:param key: int,64-bit 密钥
:return: list[int],加密后的 64-bit 密文块列表
"""
return [DES(block, key, 'encrypt') for block in plain_blocks]

# ECB模式解密
def des_ecb_decrypt(cipher_blocks, key):
return [DES(block, key, 'decrypt') for block in cipher_blocks]

def get_input_blocks():
""" 让用户输入数据,并转换为 64-bit 块 """
data = input("请输入数据(可以是字符串、整数或十六进制):").strip()
if data.startswith("0x"): # 处理十六进制输入
blocks = [int(data, 16)]
elif data.isdigit(): # 处理整数输入
blocks = [int(data)]
else: # 处理字符串输入
blocks = pad_data(data)
return blocks

def main():
key = int(input("请输入 64-bit 密钥(16进制):"), 16) # 获取密钥
mode = input("请选择模式(encrypt/decrypt):").strip().lower()

if mode == 'encrypt':
plaintext_blocks = get_input_blocks()
cipher_blocks = des_ecb_encrypt(plaintext_blocks, key)
print("加密后:", [hex(c) for c in cipher_blocks])
elif mode == 'decrypt':
cipher_blocks = get_input_blocks()
decrypted_blocks = des_ecb_decrypt(cipher_blocks, key)
try:
print("解密后:", unpad_data(decrypted_blocks)) # 试图转换回字符串
except:
print("解密后:", [hex(c) for c in decrypted_blocks]) # 不是字符串时输出十六进制
else:
print("无效模式,请输入 'encrypt' 或 'decrypt'")

if __name__ == "__main__":
main()


工作模式:

  • ECB模式:每个明文块独立加密,无依赖关系。
  • CBC模式:每个块加密前与前一个密文异或,需初始向量IV。

des1.py –有每轮结果的版本

des.py