DES加密算法

简介

DES 算法是最早的对称加密算法,虽然已经被 AES 所取代了,但是在安卓逆向的过程中还是有可能会遇到这个算法,所以对它有一定了解还是必要的

基本参数:

  • 分组大小:64 位(8 字节)
  • 密钥长度:56 位有效密钥(实际 64 位,含 8 位奇偶校验)
  • 迭代轮数:16 轮
  • 工作模式:ECB,CBC,CFB,OFB 等 DES 分成两部分,一部分是明文的处理,另一部分是密钥的编排

第一部分:密钥的编排(子密钥生成)

密钥编排是指从 64 位的主密钥生成 16 个 48 位主密钥的过程,使用官方测试向量:

  • 明文:0x0123456789ABCDEF(64 位)
  • 密钥:0x133457799BBCDFF1(64 位,含奇偶校验)
  • 期望密文:0x85E813540F0AB405(64 位)

密钥初始置换

目的:去除 8 个奇偶校验位,将 64 位密钥压缩成 56 位有效密钥
PC-1 置换表(56 个位置):

1
2
3
4
5
6
7
8
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

逐位计算 PC-1 置换
主密钥的二进制展开:

1
2
00010011 00110100 01010111 01111001 
10011011 10111100 11011111 11110001

按 PC-1 表逐位提取:

  • 新第 1 位<=原 57 位,第 57 位在第 8 个字节的第一位(57/8=7 余 1)
    • 第 8 个字节 11110001 的第一位是 1
  • 新第 2 位<=原 49 位,第 49 位在第 7 个字节的第一位(49/8=6 余 1)
    • 第 7 个字节 11011111 的第一位是 1
  • 新第 3 位<=原 41 位,第 41 位在第 6 个字节的第一位(41/8=5 余 1)
    • 第 6 个字节 10111100 的第一位是 1
  • 以此类推,继续提取 56 位…….

PC-1 置换结果(56 位):

1
2
1111000 0110011 0010101 0101111 
0101010 1011001 1001111 0001111

分别拆分成 C0D0

  • C0=前 28 位= 1111000 0110011 0010101 0101111
  • D0=后 28 位= 0101010 1011001 1001111 0001111

循环左移生成 16 对

左移规则表:

轮次12345678910111213141516
左移1122222212222221

先计算一下前两轮:
第一轮:左移 1 位

  • C0= 1111000 0110011 0010101 0101111
  • 循环左移 1 位: 1110000 1100110 0101010 1011111 即 C1
  • D0= 0101010 1011001 1001111 0001111
  • 循环左移 1 位: 1010101 0110011 0011110 0011110 即 D1 第二轮:左移 1 位
  • C1= 1110000 1100110 0101010 1011111
  • 循环左移 1 位: 1100001 1001100 1010101 0111111 即 C2
  • D1= 1010101 0110011 0011110 0011110
  • 循环左移 1 位: 0101010 1100110 0111100 0111101 即 D2 继续左移得到 16 对……(按照左移规则)

PC-2 置换生成 16 个子密钥

PC-2 置换表:(56 位=>48 位)

1
2
3
4
5
6
7
8
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

生成 K1 子密钥:

  • 合并 C1 和 D1=56 位
  • 按 PC-2 选择 48 位
    • 新第 1 位<-原 14 位
    • 新第 2 位<-原 17 位
    • 以此类推……
  • 得到了 K1= 000110 110000 001011 101111 111111 000111 000001 110010 16 个子密钥的生成(十六进制)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
K₁  = 0x1B02EFFC7072
K₂  = 0x79AED9DBC9E5  
K₃  = 0x55FC8A42CF99
K₄  = 0x72A9F92972A9
K₅  = 0x6D114CC2685F
K₆  = 0x5BA61D20C3B6
K₇  = 0x4B7DC64A3F4A
K₈  = 0x27A7D8495E4A
K₉  = 0x42B1D6A3F4A7
K₁₀ = 0x7D8495E4A27A
K₁₁ = 0x1D6A3F4A742B
K₁₂ = 0xD8495E4A27A7
K₁₃ = 0xD6A3F4A742B1
K₁₄ = 0x8495E4A27A7D
K₁₅ = 0x6A3F4A742B1D
K₁₆ = 0x495E4A27A7D8

至此,16 个子密钥生成完毕

第二部分:明文的处理

初始置换(IP 置换)

明文: 0x0123456789ABCDEF
二进制明文展开:

1
2
3
4
5
6
7
8
字节1: 00000001 (0x01)
字节2: 00100011 (0x23)  
字节3: 01000101 (0x45)
字节4: 01100111 (0x67)
字节5: 10001001 (0x89)
字节6: 10101011 (0xAB)
字节7: 11001101 (0xCD)
字节8: 11101111 (0xEF)

IP 置换表(64 位重排)

1
2
3
4
5
6
7
8
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

IP 置换计算示例:

  • 新第 1 位<-原第 58 位,第 58 位在第 8 个字节的第二位(58/8=7 余 2)
    • 第 8 个字节: 11101111 第二位是 1
  • 新第 2 位<-原第50 位,第 50 位在第 7 个字节的第二位(50/8=6 余 2)
    • 第 7 个字节: 11001101 第二位是 1
  • ……继续 64 位

IP 置换结果:

1
2
11001100 00000000 11001100 11111111
11110000 10101010 11110000 10101010

16 进制: 0xCC00CCFFF0AAF0AA
分别拆分成 L0 和 R0:

  • L0=左 32 位=0xCC00CCFF= 11001100 00000000 11001100 11111111
  • R0=右 32 位=0xF0AAF0AA= 11110000 10101010 11110000 10101010

16 轮运算

轮函数 f(R,K) 的完整计算过程:
步骤 1:扩展置换(32 位=>48 位) 因为下面需要与子密钥进行异或,子密钥是 48 位,所以需要进行密钥扩展
E 扩展表:

1
2
3
4
5
6
7
8
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

R0 的位级扩展:
R0= 11110000 10101010 11110000 10101010 (32 位)
按 E 表逐位映射:

  • 新第 1 位<=原第 32 位:R0 第 32 位是 0
  • 新第 2 位<=原第 1 位:R0 第 1 位是 1
  • 新第 3 位<=原第 2 位:R0 第 2 位是 1
  • 新第 4 位<=原第 3 位:R0 第 3 位是 1
  • 新第 5 位<=原第 4 位:R0 第 4 位是 1
  • 新第 6 位<=原第 5 位:R0 第 5 位是 0
  • 新第 7 位<=原第 4 位:R0 第 4 位是 1(重复利用)
  • 新第 8 位<=原第 5 位:R0 第 5 位是 0
  • ……继续 48 位 E(R0)的结果就是:
1
011110 100001 010101 010101 011110 100001 010101 010101

步骤 2:与子密钥 K1 进行异或
子密钥: 0x1B02EFFC7072 (前面密钥编排得到)
二进制: 000110 110000 001011 101111 111111 000111 000001 110010
异或运算:

1
2
3
E(R₀): 011110 100001 010101 010101 011110 100001 010101 010101
K₁:    000110 110000 001011 101111 111111 000111 000001 110010
结果:  011000 010001 011110 111010 100001 100110 010100 100111

16 进制:0x6117BA86F527
步骤 3:S 盒替换 S 盒定义:

 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
S_BOX = [
    # 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]]
]

将步骤 2 得到的结果进行分组:

1
2
3
4
5
6
7
8
B1: 011000
B2: 010001
B3: 011110
B4: 111010
B5: 100001
B6: 100110
B7: 010100
B8: 100111

以 B1 为例:
行号:第 1 位和最后一位合起来:00,对应 10 进制就是 0,表示第 1 行
列号:中间 4 位:1100=12(列)即第 13 列
所以: S[0][12]=5 =>二进制 0101

1
2
3
4
5
6
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]
]

再来以 B2 来演示一组吧:
行号:第 1 位和最后 1 位合起来就是 01,对应的 10 进制就是 1,表示第 2 行
列号:中间 4 位 1000=8(列),即第 9 列
所以: S[1][8]=12 =>二进制:1100

1
2
3
4
5
6
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]
]

下面,以此内推; 一共 8 组,每组 4 位,生成 32 位
步骤 4:P 盒置换(32 位=>32 位)
P 盒表:

1
2
3
4
5
6
7
8
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

重新排列之后得到 32 位
步骤 5:计算本轮输出
L1 = R0 = 0xF0AAF0AA
R1 = L₀ xor f(R₀, K₁) = 0xCC00CCFF xor 0x5E8350FA= 0x92839C05

周而复始:搞到 16 轮之后得到结果:

1
2
L16: 01000011 01000010 00110010 00110100
R16: 00001010 01001100 11011001 10010101

第三部分:末置换

R16+L16= 00001010 01001100 11011001 10010101 01000011 01000010 00110010 00110100
最后通过 PC_1 置换表置换:

1
2
3
4
5
6
7
8
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]

得到结果:

1
0x85E813540F0AB405

代码实现

  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
from typing import List

# IP置换表
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]

# 逆IP置换表
IP_INV = [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]

# 扩展置换E表
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]

# P盒置换表
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]

# PC-1置换表(密钥初始置换)
PC1 = [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]

# PC-2置换表(子密钥生成)
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]

# 左移位数表
SHIFT_SCHEDULE = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]

# S盒定义(8个)
S_BOX = [
    # 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]]
]

def str_to_bits(text: str, size: int) -> List[int]:
    """将字符串转换为位列表"""
    bits = []
    for i in range(size):
        byte = i // 8
        bit = 7 - (i % 8)
        if byte < len(text):
            bits.append((ord(text[byte]) >> bit) & 1)
        else:
            bits.append(0)
    return bits

def hex_to_bits(hex_str: str, size: int) -> List[int]:
    """将十六进制字符串转换为位列表"""
    bits = []
    hex_str = hex_str.replace(" ", "").replace("0x", "").zfill(size//4)
    for char in hex_str:
        val = int(char, 16)
        for i in range(3, -1, -1):
            bits.append((val >> i) & 1)
    return bits[:size]

def bits_to_hex(bits: List[int]) -> str:
    """将位列表转换为十六进制字符串"""
    hex_str = ""
    for i in range(0, len(bits), 8):
        byte = 0
        for j in range(8):
            if i+j < len(bits):
                byte = (byte << 1) | bits[i+j]
        hex_str += f"{byte:02X}"
    return hex_str.lower()

def permute(bits: List[int], table: List[int]) -> List[int]:
    """按置换表进行置换"""
    return [bits[table[i]-1] for i in range(len(table))]

def left_shift(bits: List[int], n: int) -> List[int]:
    """循环左移"""
    return bits[n:] + bits[:n]

def generate_subkeys(key_bits: List[int]) -> List[List[int]]:
    """生成16个子密钥"""
    # PC-1置换
    key_pc1 = permute(key_bits, PC1)
    
    # 分成左右两部分
    C = key_pc1[:28]
    D = key_pc1[28:]
    
    subkeys = []
    for round_num in range(16):
        # 循环左移
        shift = SHIFT_SCHEDULE[round_num]
        C = left_shift(C, shift)
        D = left_shift(D, shift)
        
        # PC-2置换生成子密钥
        combined = C + D
        subkey = permute(combined, PC2)
        subkeys.append(subkey)
    
    return subkeys

def f_function(R: List[int], K: List[int]) -> List[int]:
    """f函数"""
    # 扩展置换E
    R_expanded = permute(R, E)
    
    # 与子密钥异或
    xor_result = [R_expanded[i] ^ K[i] for i in range(48)]
    
    # S盒替换
    sbox_output = []
    for i in range(8):
        # 取6位
        block = xor_result[i*6:(i+1)*6]
        row = (block[0] << 1) | block[5]
        col = (block[1] << 3) | (block[2] << 2) | (block[3] << 1) | block[4]
        val = S_BOX[i][row][col]
        # 转换为4位
        sbox_output.extend([(val >> 3) & 1, (val >> 2) & 1, (val >> 1) & 1, val & 1])
    
    # P盒置换
    return permute(sbox_output, P)

def des_encrypt(plaintext_bits: List[int], key_bits: List[int]) -> List[int]:
    """DES加密"""
    # 初始置换IP
    bits = permute(plaintext_bits, IP)
    
    # 生成子密钥
    subkeys = generate_subkeys(key_bits)
    
    # 分成左右两部分
    L = bits[:32]
    R = bits[32:]
    
    # 16轮迭代
    for i in range(16):
        L_next = R[:]
        f_result = f_function(R, subkeys[i])
        R_next = [L[j] ^ f_result[j] for j in range(32)]
        L, R = L_next, R_next
    
    # 最后交换(算法中先交换再末置换)
    combined = R + L
    
    # 末置换IP^-1
    ciphertext = permute(combined, IP_INV)
    
    return ciphertext

# 测试
if __name__ == "__main__":
    plaintext_hex = "0123456789ABCDEF"
    key_hex = "133457799BBCDFF1"
    
    # 转换为位列表
    plaintext_bits = hex_to_bits(plaintext_hex, 64)
    key_bits = hex_to_bits(key_hex, 64)
    
    print(f"明文: {plaintext_hex}")
    print(f"密钥: {key_hex}")
    
    # 加密
    ciphertext_bits = des_encrypt(plaintext_bits, key_bits)
    ciphertext_hex = bits_to_hex(ciphertext_bits)
    
    print(f"密文: {ciphertext_hex}")