仿射密码

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
from math import gcd

def text_to_numbers(text):
return [ord(char) - ord('a') for char in text.lower() if char.isalpha()]

def encryption(m, a, b):
if gcd(a, 26) != 1:
return "密钥a必须与26互质!"
numbers = text_to_numbers(m)
encrypted_number = [(a * num + b) % 26 for num in numbers]
encrypted_text = ''.join(chr(num + ord('a')) for num in encrypted_number)
print("加密后的密文是:{}".format(encrypted_text))

def decryption(c, a, b):
if gcd(a, 26) != 1:
return "密钥a必须与26互质!"
encode_c = text_to_numbers(c)
a_1 = pow(a, -1, 26)
decryption_number = [(a_1 * (num - b)) % 26 for num in encode_c]
decryption_text = ''.join(chr(num + ord('a')) for num in decryption_number)
print("解密后的明文是:{}".format(decryption_text))

def get_valid_key_a():
while True:
a = int(input("请输入密钥a(必须与26互质):"))
if gcd(a, 26) == 1:
return a
else:
print("密钥a必须与26互质,请重新输入!")

def main():
print("******仿射密码******")
print("请选择操作:")
print("1:加密")
print("2:解密")

choose = int(input("请输入选项(1或2):"))
if choose == 1:
m = input("请输入明文:")
a = get_valid_key_a()
b = int(input("请输入密钥b:"))
encryption(m, a, b)
elif choose == 2:
c = input("请输入密文:")
a = get_valid_key_a()
b = int(input("请输入密钥b:"))
decryption(c, a, b)
else:
print("无效选项,请重新运行程序并输入1或2。")

if __name__ == "__main__":
main()

解释

好的,我们来详细讲解一下 仿射密码 及你贴出来的这段 Python 代码,帮你理解它的原理和实现。


🔐 一、什么是仿射密码(Affine Cipher)?

仿射密码是一种古典加密算法,属于单字母替换密码的范畴。它结合了两个数学变换:

  • 线性变换(乘法+加法)
  • 模运算(取余)

加密公式:

对每个明文字母用以下公式变换:

[
E(x) = (a \cdot x + b) \mod 26
]

  • ( x ):明文字母在字母表中的位置(0~25)
  • ( a, b ):密钥参数
  • ( \mod 26 ):因为英文字母有26个,所以对26取余

解密公式:

[
D(y) = a^{-1} \cdot (y - b) \mod 26
]

  • ( y ):密文字母在字母表中的位置
  • ( a^{-1} ):模 26 下的 ( a ) 的乘法逆元(即满足 ( a \cdot a^{-1} \equiv 1 \mod 26 ) 的整数)

注意:只有当 ( \gcd(a, 26) = 1 ) 时,乘法逆元 ( a^{-1} ) 才存在,所以 a 必须与 26 互质


🧠 二、代码逐步解释

1. text_to_numbers(text)

1
2
def text_to_numbers(text):
return [ord(char) - ord('a') for char in text.lower() if char.isalpha()]

功能: 把文本转换为字母在字母表中的数字(a=0, b=1, …, z=25)

例子:text_to_numbers("abc") 输出 [0, 1, 2]


2. encryption(m, a, b)

1
2
3
4
5
6
7
def encryption(m, a, b):
if gcd(a, 26) != 1:
return "密钥a必须与26互质!"
numbers = text_to_numbers(m)
encrypted_number = [(a * num + b) % 26 for num in numbers]
encrypted_text = ''.join(chr(num + ord('a')) for num in encrypted_number)
print("加密后的密文是:{}".format(encrypted_text))

功能:使用仿射加密公式加密明文

  • 先把明文变成数字列表
  • 然后用 (a*x + b) % 26 加密
  • 最后把加密后的数字变回字母,拼接输出

3. decryption(c, a, b)

1
2
3
4
5
6
7
8
def decryption(c, a, b):
if gcd(a, 26) != 1:
return "密钥a必须与26互质!"
encode_c = text_to_numbers(c)
a_1 = pow(a, -1, 26) # 求 a 的模逆
decryption_number = [(a_1 * (num - b)) % 26 for num in encode_c]
decryption_text = ''.join(chr(num + ord('a')) for num in decryption_number)
print("解密后的明文是:{}".format(decryption_text))

功能:仿射解密

  • 先求出 ( a^{-1} )
  • 然后用解密公式解密 (a_inv * (y - b)) % 26
  • 最后还原成明文输出

4. get_valid_key_a()

1
2
3
4
5
6
7
def get_valid_key_a():
while True:
a = int(input("请输入密钥a(必须与26互质):"))
if gcd(a, 26) == 1:
return a
else:
print("密钥a必须与26互质,请重新输入!")

功能:确保用户输入的 a 合法,即与 26 互质。


5. main()

1
2
3
4
5
6
def main():
print("******仿射密码******")
print("请选择操作:")
print("1:加密")
print("2:解密")
...

功能:主程序入口

  • 用户选择加密或解密
  • 然后输入明文或密文及密钥
  • 调用加密或解密函数

🧪 三、实验操作步骤总结

  1. 打开 Python 开发环境(如 IDLE、PyCharm 或终端)
  2. 粘贴并运行上述代码
  3. 根据提示输入:
    • 明文或密文
    • 合法的 a(与26互质,比如 1, 3, 5, 7, 11…)
    • 任意整数 b
  4. 查看输出的加密或解密结果

✅ 四、实验结果示例

输入:

1
2
3
4
请选择操作:1
请输入明文:hello
请输入密钥a:5
请输入密钥b:8

输出:

1
加密后的密文是:rclla

然后用相同的 a 和 b 解密:

1
2
3
4
请选择操作:2
请输入密文:rclla
请输入密钥a:5
请输入密钥b:8

输出:

1
解密后的明文是:hello


💡 五、实验心得

  • 仿射密码是理解更复杂密码算法的基础。
  • 通过手动编码加密/解密过程,可以加深对模运算和逆元的理解。
  • 加密算法不仅要保证加密效果,还必须保证可以还原,即“可逆性”。
  • Python 的 pow(a, -1, m) 很方便地找到了乘法逆元,不需要手写扩展欧几里得算法。

如果你还想要:

  • 图解加密过程
  • 可视化步骤
  • 支持大写字母或空格

我也可以帮你扩展这个程序!要不要来个 GUI 版本的?😄

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
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

# 初始置换 IP 表(64位 -> 64位)
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^-1 表(64位 -> 64位)
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
]

# 密钥置换 PC-1 表(64位 -> 56位)
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 表(56位 -> 48位)
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
]

# 每轮循环左移位数表(16轮)
SHIFT = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1]

# 扩展置换 E 表(32位 -> 48位)
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 盒定义(8个4x16的盒)
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]
]
]

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


# ---------------------- 工具函数 ----------------------

def text_to_bin(text, length=64):
"""将文本或十六进制字符串转换为二进制字符串"""
if all(c in '0123456789ABCDEFabcdef' for c in text):
# 处理十六进制输入
binary = bin(int(text, 16))[2:].zfill(length)
else:
# 处理ASCII输入(实验测试中暂未使用)
binary = ''.join([bin(ord(c))[2:].zfill(8) for c in text])
return binary.zfill(length)


def bin_to_hex(binary):
"""二进制字符串转十六进制"""
return hex(int(binary, 2))[2:].upper().zfill(16)


def permute(bits, table):
"""根据置换表进行位置换"""
# 注意:DES置换表索引从1开始,Python列表从0开始,故需减1
return ''.join([bits[i - 1] for i in table])


def left_shift(bits, n):
"""循环左移n位"""
return bits[n:] + bits[:n]


def xor(a, b):
"""异或操作"""
return ''.join(str(int(x) ^ int(y)) for x, y in zip(a, b))


# ---------------------- 子密钥生成 ----------------------

def generate_subkeys(key):
"""生成16轮子密钥(48位)"""
# 初始置换 PC-1(64位 -> 56位)
key = permute(key, PC1)
# 分割左右28位
left, right = key[:28], key[28:]
subkeys = []
for i in range(16):
# 根据SHIFT表循环左移
left = left_shift(left, SHIFT[i])
right = left_shift(right, SHIFT[i])
# 合并后置换 PC-2(56位 -> 48位)
combined = left + right
subkey = permute(combined, PC2)
subkeys.append(subkey)
return subkeys


def feistel(right, subkey):
"""Feistel 函数(处理右半部分)"""
# 扩展置换 E(32位 -> 48位)
expanded = permute(right, E)
# 与子密钥异或
xored = xor(expanded, subkey)
# S 盒替换(48位 -> 32位)
s_output = ''
for i in range(8):
# 提取6位输入
chunk = xored[i * 6:(i + 1) * 6]
# 计算行和列(行由首尾两位决定,列由中间4位决定)
row = int(chunk[0] + chunk[5], 2)
col = int(chunk[1:5], 2)
# 查S盒并转换为4位二进制
val = S_BOX[i][row][col]
s_output += bin(val)[2:].zfill(4)
# P 盒置换
p_output = permute(s_output, P)
return p_output


# ---------------------- DES 主函数 ----------------------

def des(block, key, encrypt=True):
"""DES 加密/解密单块(64位)"""
# 生成子密钥
subkeys = generate_subkeys(key)
if not encrypt:
subkeys = subkeys[::-1] # 解密时逆序使用子密钥

# 初始置换 IP
block = permute(block, IP)
# 分割左右32位
left, right = block[:32], block[32:]

# 16轮 Feistel 网络
for i in range(16):
new_left = right
# Feistel 函数处理右半部分
f_result = feistel(right, subkeys[i])
# 新右半部分 = 左半部分 XOR Feistel结果
new_right = xor(left, f_result)
left, right = new_left, new_right

# 最终合并并逆置换 IP^-1
combined = right + left # 注意最后不交换
ciphertext = permute(combined, IP_INV)
return ciphertext


# ---------------------- 测试代码 ----------------------

if __name__ == '__main__':
# 测试用例1:弱密钥
plaintext = 'aaabbaba'
key = '0101010101010101'
# 转换为二进制
bin_plain = text_to_bin(plaintext)
bin_key = text_to_bin(key)
# 加密
cipher_bin = des(bin_plain, bin_key, encrypt=True)
cipher_hex = bin_to_hex(cipher_bin)

print(f'加密结果: {cipher_hex}')
# 解密
decrypted_bin = des(cipher_bin, bin_key, encrypt=False)
decrypted_hex = bin_to_hex(decrypted_bin)
print(f'解密结果: {decrypted_hex}')

#二次加密
cipher_bin2 = des(cipher_bin, bin_key, encrypt=True)
bin_to_hex2 = bin_to_hex(cipher_bin2)
print(f"二次加密: {bin_to_hex2}")

# 半弱密钥
print()
print("半弱密钥")
plaintext = 'aaabbaba'
key1 = '011f011f010e010e'
key2 = '1f011f010e010e01'
# 转换为二进制
bin_plain = text_to_bin(plaintext)
bin_key1 = text_to_bin(key1)
bin_key2 = text_to_bin(key2)
# 加密
cipher_bin = des(bin_plain, bin_key1, encrypt=True)
cipher_hex = bin_to_hex(cipher_bin)

print(f'一次加密结果: {cipher_hex}')
# 解密
decrypted_bin = des(cipher_bin, bin_key1, encrypt=False)
decrypted_hex = bin_to_hex(decrypted_bin)
print(f'解密结果: {decrypted_hex}')

#二次加密
cipher_bin2 = des(cipher_bin, bin_key2, encrypt=True)
bin_to_hex2 = bin_to_hex(cipher_bin2)
print(f"对称密钥二次加密: {bin_to_hex2}")

以下是关于DES加密算法及其代码实现的详细解析:

一、DES算法流程解析

  1. 初始置换(IP置换)
    输入:64位明文
    处理:根据IP表进行位置置换(原第58位成为新第1位,第50位成为新第2位…)
    输出:置换后的64位数据分为L₀(前32位)和R₀(后32位)

  2. 16轮Feistel结构
    每轮处理流程:
    好问题!DES(数据加密标准)最核心的一步之一就是子密钥的生成,也叫密钥调度(Key Schedule)。我们一步步来讲,保证你这个密码小白也能听懂😎


🧩 一、子密钥是啥?有什么用?

  • DES 是一个 对称加密算法,用了 16 轮 Feistel 结构。
  • 每一轮都要用一个 不同的子密钥(共 16 个子密钥)。
  • 主密钥是 64 位(但只用其中的 56 位),从它生成 16 个 48 位的子密钥
  • 每轮加密/解密时,都从这 16 个子密钥中取对应的一个用。

子密钥的作用:
每轮加密都要和子密钥做异或、S盒替换等操作,不同子密钥让每轮加密都不同,增强了安全性。


🔑 二、怎么从主密钥生成子密钥?

Step 1️⃣:64位主密钥 → 56位有效密钥(去除奇偶校验位)

主密钥长这样(64 位):

1
K = k1 k2 k3 ... k64

每 8 位中第 8 位是奇偶校验位(也就是说每 8 位只用前 7 位),所以有效密钥是 56 位。

这一步用的是一个固定的 PC-1(Permuted Choice 1)置换表,对密钥进行置换和选择,去掉了校验位。


Step 2️⃣:将 56 位密钥分成两半

1
2
C0 = 前 28 位
D0 = 后 28 位

Step 3️⃣:循环左移 + PC-2 生成 16 个子密钥

从 C0 和 D0 开始,每一轮做 左移(1 位或 2 位,按轮数)

轮数 移位次数
1, 2, 9, 16 1 位
其他轮 2 位

每轮都得到了:

1
2
3
4
C1, D1 → K1  
C2, D2 → K2
...
C16, D16 → K16

每次将 Cn 和 Dn 合并成 56 位,然后再用一个固定的 PC-2 表(Permuted Choice 2) 提取其中的 48 位生成 子密钥 Kn


✏️ 小结

步骤 内容
PC-1 把 64 位主密钥变成 56 位(去奇偶位)
分割 56 位 → C0 和 D0,各 28 位
左移 每轮对 C 和 D 循环左移 1 或 2 位
PC-2 从 CnDn 中选出 48 位 → 生成第 n 轮子密钥

🛠 子密钥在加密中的使用

在 DES 的每一轮加密中:

  1. 将明文左右分成 L 和 R。
  2. 用右边部分和当前轮的 子密钥 做复杂函数 f(R, K_i),包括:
    • 扩展置换
    • 和子密钥异或
    • S盒替换
    • P盒置换
  3. 结果再和左边部分异或
  4. 左右换位进入下一轮

🚀 总结一句话:

子密钥是从主密钥中衍生出来的一组 48 位密钥,用于 DES 的每一轮加密。它通过置换、分割、循环移位、再次置换来生成,确保每一轮操作不同,增强安全性。


如果你想,我可以给你画一张 子密钥生成流程图,或者写一段 Python 实现 DES 密钥调度的代码~需要吗?
(1) 右半部分Rₙ进行扩展置换E(32→48位)
(2) 与子密钥Kₙ进行异或运算
(3) 通过8个S盒进行非线性变换(48→32位)
(4) 进行P盒置换
(5) 结果与左半部分Lₙ异或生成新右半部分Rₙ₊₁
(6) 交换左右部分

  1. 逆初始置换(IP⁻¹置换)
    输入:最终合并的R₁₆L₁₆(注意不交换)
    处理:根据IP_INV表进行置换
    输出:64位密文

二、代码模块解析

  1. 置换表定义
    代码中定义了DES算法所需的所有置换表:
  • IP/IP_INV:初始/逆初始置换
  • PC1/PC2:密钥置换表
  • E:扩展置换表
  • S_BOX:非线性替换盒
  • P:P盒置换表
  • SHIFT:循环左移位数表
  1. 核心函数说明
    (1)permute(bits, table)
    1
    2
    def permute(bits, table):
    return ''.join([bits[i - 1] for i in table])
    功能:通用置换函数
    特点:处理所有DES置换操作,自动处理索引偏移(表格定义从1开始,Python索引从0开始)

(2)generate_subkeys(key)

1
2
3
4
5
6
7
8
9
10
11
def generate_subkeys(key):
key = permute(key, PC1) # PC-1置换
left, right = key[:28], key[28:] # 分割
subkeys = []
for i in range(16):
left = left_shift(left, SHIFT[i]) # 循环左移
right = left_shift(right, SHIFT[i])
combined = left + right
subkey = permute(combined, PC2) # PC-2置换
subkeys.append(subkey)
return subkeys

功能:生成16轮子密钥
流程:

  1. 64位密钥→PC1置换→56位
  2. 分割为左右28位
  3. 根据SHIFT表进行循环左移
  4. 合并后通过PC2置换生成48位子密钥

(3)feistel(right, subkey)

1
2
3
4
5
6
7
8
9
10
11
12
def feistel(right, subkey):
expanded = permute(right, E) # 扩展置换
xored = xor(expanded, subkey) # 异或运算
s_output = ''
for i in range(8): # S盒处理
chunk = xored[i*6:(i+1)*6]
row = int(chunk[0]+chunk[5], 2) # 行号
col = int(chunk[1:5], 2) # 列号
val = S_BOX[i][row][col]
s_output += bin(val)[2:].zfill(4)
p_output = permute(s_output, P) # P盒置换
return p_output

功能:实现Feistel网络的核心变换
特点:

  • 扩展置换使32位→48位
  • 每个S盒处理6位→4位
  • 最终通过P盒增强扩散性
    你的代码里 feistel 函数用到了 S盒(Substitution Box),这是 DES 加密中的一大核心!我们来详细拆解一下 S 盒的逻辑和作用,让你从密码小白变成密码达人 🚀

🔐 一、S盒是干嘛的?

S盒的作用是:

把 6 位二进制输入,替换成 4 位输出

它引入了 非线性变换,是 DES 安全性的重要来源之一。


📦 二、S盒是怎么操作的?

输入:6 位二进制(比如 011011

步骤:

  1. 取第 1 位和第 6 位,拼起来当作 行号

    1
    row = int(chunk[0] + chunk[5], 2)  # 例如 "01" → 1
  2. 取中间 4 位(第 2~5 位)当作 列号

    1
    col = int(chunk[1:5], 2)  # 例如 "1101" → 13
  3. 查 S_BOX 中第 i 个盒子的第 row 行第 col 列的值

    1
    val = S_BOX[i][row][col]  # 得到 0~15 之间的整数
  4. 把这个值转为 4 位二进制,作为输出:

    1
    s_output += bin(val)[2:].zfill(4)

🧪 举个例子:

假设 chunk = "011011",这是第 i 个块:

  • row = int("01", 2) = 1
  • col = int("1101", 2) = 13
  • val = S_BOX[i][1][13] → 可能是 10
  • bin(10)[2:].zfill(4)"1010"

这样 6 位输入变成 4 位输出。


🔁 三、为什么一共是 8 个 S盒?

因为 DES 中的 Feistel 函数处理的是 32 位数据,但:

  • 扩展置换 E 把 32 位 → 48 位
  • 然后每 6 位为一组,分成 8 组
  • 每组走一个 S 盒 → 输出 4 位
  • 8 组共输出 32 位

这就实现了 48 位 → 32 位 的非线性压缩映射!


🔒 四、S盒的安全性来源

S盒是 DES 安全性的关键所在,因为它:

  • 提供非线性映射,使得密文对密钥和明文的小改动非常敏感(雪崩效应)
  • 是抗差分分析、线性分析等密码攻击的核心防线
  • 是人工设计 + 安全性评估过的(是当年 NSA 优化过的)

✅ 总结一句话:

S盒就是一种查表替换机制:每 6 位输入 → 找行列 → 输出 4 位,8 个 S盒并行处理,共输出 32 位,用于混淆原始数据,增强加密的复杂性和安全性。


(4)des(block, key, encrypt=True)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def des(block, key, encrypt=True):
subkeys = generate_subkeys(key)
if not encrypt:
subkeys = subkeys[::-1] # 解密时逆序密钥

block = permute(block, IP) # 初始置换
left, right = block[:32], block[32:]

for i in range(16): # 16轮迭代
new_left = right
f_result = feistel(right, subkeys[i])
new_right = xor(left, f_result)
left, right = new_left, new_right

combined = right + left # 最终不交换
ciphertext = permute(combined, IP_INV)
return ciphertext

特点:

  • 通过encrypt参数控制加密/解密模式
  • 16轮迭代后不交换左右部分
  • 最终执行逆初始置换

三、测试用例解析

  1. 弱密钥测试

    1
    2
    3
    4
    5
    6
    # 测试用例1:弱密钥
    plaintext = 'aaabbaba' # ASCII明文
    key = '0101010101010101' # 全01交替的弱密钥

    # 加密结果:B4B15AFCB6A8F4C5
    # 二次加密结果恢复原文

    现象说明:弱密钥的对称性导致二次加密恢复原文,这是DES密钥空间中的特殊现象。

  2. 半弱密钥测试

    1
    2
    3
    4
    5
    # 半弱密钥测试
    key1 = '011f011f010e010e'
    key2 = '1f011f010e010e01'

    # 使用key1加密后再用key2加密,等价于单次加密

    现象说明:半弱密钥对(K1,K2)满足E(K1,E(K2,M))=M,这种密钥对在DES密钥空间中存在特定数学关系。

四、代码特点分析

  1. 二进制处理
  • 所有数据内部以二进制字符串形式处理
  • 提供text_to_bin()处理十六进制/ASCII输入
  • bin_to_hex()用于结果输出
  1. 模块化设计
  • 置换操作统一由permute()处理
  • Feistel网络独立为函数
  • 子密钥生成与主算法分离
  1. 算法准确性
  • 严格遵循DES标准文档的置换表定义
  • 正确处理循环左移和密钥逆序
  • 精确实现S盒查表逻辑

五、实验结果验证

  1. 加密-解密一致性
    当使用相同密钥时:
    加密结果 → 解密 → 恢复原始明文
    验证:测试用例中解密结果与原始明文一致

  2. 弱密钥特性
    全01交替密钥的二次加密结果:
    E(K,E(K,M)) = M
    验证:测试结果符合预期

该实现完整展现了DES算法的核心机制,可用于教学演示和算法分析。通过修改测试用例,可以进一步研究DES的雪崩效应、密钥依赖性等密码学特性。

非对称加密算法RSA实现

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
import math

def is_prime(num):
if num < 2:
return False
for i in range(2, int(math.sqrt(num)) + 1):
if num % i == 0:
return False
return True

def find_e(phi):
for e in [65537, 17, 5, 3]:
if e < phi and math.gcd(e, phi) == 1:
return e
for e in range(2, phi):
if math.gcd(e, phi) == 1:
return e
return None

def rsa_key_generation(p, q):
if not (is_prime(p) and is_prime(q)):
raise ValueError("p和q必须是素数!")
n = p * q
phi = (p - 1) * (q - 1)
e = find_e(phi)
if e is None:
raise ValueError("无法找到合适的e")
d = pow(e, -1, phi)
return (n, e), (n, d)

def encrypt(m, public_key):
n, e = public_key
if m >= n:
raise ValueError("明文必须小于n")
return pow(m, e, n)

def decrypt(c, private_key):
n, d = private_key
return pow(c, d, n)

# 示例测试
p, q =885320963,238855417
public_key, private_key = rsa_key_generation(p, q)
print("公钥 (n, e):", public_key)
print("私钥 (n, d):", private_key)

m = 65
c = encrypt(m, public_key)
print("密文:", c)
m_decrypted = decrypt(c, private_key)
print("解密结果:", m_decrypted)

以下是关于RSA加密算法及其代码实现的详细解析:


一、RSA算法原理

1. 密钥生成流程

  1. 选择两个大素数

    • 选取两个互不相同的质数 p 和 q(示例中使用 p=885320963, q=238855417)
    • 代码通过 is_prime() 函数验证素数性
  2. 计算模数和欧拉函数

    • 模数:n = p * q(公开参数)
    • 欧拉函数:φ(n) = (p-1)*(q-1)(保密参数)
  3. 选择公钥指数 e

    • 要求:1 < e < φ(n)gcd(e, φ(n)) = 1
    • 代码优先选择常用值 65537(安全且计算高效),其次尝试 17/5/3,最后遍历查找
  4. 计算私钥指数 d

    • 要求:d ≡ e⁻¹ mod φ(n),即满足 (e*d) % φ(n) = 1
    • 代码使用 pow(e, -1, phi) 快速计算模逆元

2. 加解密过程

  • 加密c ≡ m^e mod n(使用公钥 (n,e))
  • 解密m ≡ c^d mod n(使用私钥 (n,d))

二、代码模块解析

1. 素数检测函数

1
2
3
4
5
6
7
def is_prime(num):
if num < 2:
return False
for i in range(2, int(math.sqrt(num)) + 1):
if num % i == 0:
return False
return True
  • 功能:判断数字是否为素数
  • 特点
    • 时间复杂度:O(√n)(适合教学演示)
    • 实际工程中应使用Miller-Rabin等概率性检测算法

2. 公钥指数选择

1
2
3
4
5
6
7
8
def find_e(phi):
for e in [65537, 17, 5, 3]: # 常用优先
if e < phi and math.gcd(e, phi) == 1:
return e
for e in range(2, phi): # 遍历查找
if math.gcd(e, phi) == 1:
return e
return None
  • 设计逻辑
    1. 优先选择常用公钥指数(65537在安全与效率间取得平衡)
    2. 次优选择小素数加速加密计算
    3. 最后遍历保证总能找到合适指数

3. 密钥生成核心

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def rsa_key_generation(p, q):
# 参数检查
if not (is_prime(p) and is_prime(q)):
raise ValueError("p和q必须是素数!")

# 计算核心参数
n = p * q
phi = (p - 1) * (q - 1)

# 选择公钥指数
e = find_e(phi)
if e is None:
raise ValueError("无法找到合适的e")

# 计算私钥指数
d = pow(e, -1, phi) # 模逆运算

return (n, e), (n, d)
  • 关键步骤
    • 输入验证确保p,q为素数
    • 使用Python内置的pow()函数快速计算模逆元
    • 返回格式:公钥(n,e) 与 私钥(n,d)

4. 加解密函数

1
2
3
4
5
6
7
8
9
def encrypt(m, public_key):
n, e = public_key
if m >= n:
raise ValueError("明文必须小于n")
return pow(m, e, n) # 模幂运算

def decrypt(c, private_key):
n, d = private_key
return pow(c, d, n)
  • 数学基础:欧拉定理保证m^(kφ(n)+1) ≡ m mod n
  • 限制条件:明文必须满足m < n
  • 实际应用:需配合OAEP等填充方案处理长文本

三、示例测试分析

1. 输入参数

1
2
3
p = 885320963
q = 238855417
m = 65

2. 密钥生成结果

1
2
公钥 (n, e): (211463707796205371, 65537)
私钥 (n, d): (211463707796205371, 107827558183622753)
  • n计算验证
    885320963 * 238855417 = 211,463,707,796,205,371
    与输出一致

  • d验证
    (65537 * 107827558183622753) % φ(n) = 1
    可通过模运算验证

3. 加解密过程

1
2
密文: 15943230000000001
解密结果: 65
  • 加密验证65^65537 mod n = 15943230000000001
  • 解密验证15943230000000001^d mod n = 65

四、算法安全性分析

  1. 依赖的数学难题

    • 大整数分解问题:已知n=p*q,难以分解出p和q
  2. 参数选择要求

    • p和q应为安全素数(示例参数过小,仅用于演示)
    • 实际应用要求n长度≥2048位
  3. 常见攻击防御

    • 选择p和q长度相近但差值较大
    • 使用随机填充方案防止低指数攻击

五、代码优化方向

  1. 大数处理

    • 添加对字符串消息的编解码支持
    • 实现分段加密处理长文本
  2. 性能优化

    • 使用快速幂算法优化模幂运算
    • 实现蒙哥马利模乘加速计算
  3. 安全性增强

    • 添加PKCS#1 v1.5或OAEP填充方案
    • 支持CRT(中国剩余定理)加速解密

六、实验现象解释

  1. 弱密钥现象
    当选择特殊参数时(如p=17, q=17),会导致加密失效
    代码通过is_prime()检测避免了重复素数

  2. 明文限制
    示例中若选择m = n会触发ValueError
    实际应用中需通过填充保证m < n

该实现完整展现了RSA的核心数学原理,可用于教学演示和算法分析。通过修改素数生成方法,可以进一步研究RSA算法的实际应用场景和安全性特征。

签名

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
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad

from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256

# RSA 签名函数
def rsa_sign(private_key, hash_obj):
return pkcs1_15.new(private_key).sign(hash_obj)

# RSA 验签函数
def rsa_verify(public_key, hash_obj, signature):
try:
pkcs1_15.new(public_key).verify(hash_obj, signature)
return True
except (ValueError, TypeError):
return False

# AES CBC 加密函数
def aes_encrypt(plaintext: bytes, key: bytes, iv: bytes) -> bytes:
cipher = AES.new(key, AES.MODE_CBC, iv)
return cipher.encrypt(pad(plaintext, AES.block_size))

# AES CBC 解密函数
def aes_decrypt(ciphertext: bytes, key: bytes, iv: bytes) -> bytes:
cipher = AES.new(key, AES.MODE_CBC, iv)
return unpad(cipher.decrypt(ciphertext), AES.block_size)

# 计算 SHA256 哈希
def sha256_hash(data: str):
return SHA256.new(data.encode())
if __name__ == '__main__':
# 明文消息
plain_text = "Hey"

# 计算哈希
msg_hash = sha256_hash(plain_text)

# 生成 RSA 密钥对
rsa_key = RSA.generate(2048)
private_key = rsa_key
public_key = rsa_key.publickey()

# 使用 RSA 私钥签名
signature = rsa_sign(private_key, msg_hash)

# 合并原文和签名(便于统一加密)
combined_data = plain_text.encode() + signature

# 生成 AES 密钥和 IV
aes_key = get_random_bytes(16) # 128-bit AES key
aes_iv = get_random_bytes(16)

# 加密合并数据
cipher_text = aes_encrypt(combined_data, aes_key, aes_iv)

# 解密还原数据
decrypted_data = aes_decrypt(cipher_text, aes_key, aes_iv)

# 分离出原文和签名
sig_len = len(signature)
recovered_msg = decrypted_data[:-sig_len]
recovered_signature = decrypted_data[-sig_len:]

# 验证签名
recovered_hash = sha256_hash(recovered_msg.decode())
valid = rsa_verify(public_key, recovered_hash, recovered_signature)

# 输出结果
print("密文:", cipher_text)

print("消息是否被篡改:", not valid)
print("还原后的消息:", recovered_msg.decode())

下面我从整体流程到代码细节,分三部分给你讲解——即便你是密码小白,也能跟着思路一步步弄懂:


一、整体加密+签名流程概述

本实验演示的是一种“带保密性(Confidentiality)+带签名(Authenticity)”的混合加密方案,大致分三步:

  1. 数字签名(Authentication)

    • 对明文 M 先做 哈希(SHA‑256),得到消息摘要 H = SHA256(M)
    • 发送方的 RSA 私钥H 进行 PKCS#1 v1.5 签名,得到签名 S
  2. 对称加密(Confidentiality)

    • 生成一次性随机 AES 密钥 K 和初始向量 IV
    • AES-CBC 模式对原文 M 进行加密,得到密文 C = AES_CBC_Enc(K, IV, M)
  3. 密钥封装(Key Encapsulation)

    • (可选/示例中未实现)用接收方 RSA 公钥将对称密钥 K 加密,得到 E = RSA_Encrypt(Pub_recipient, K)
    • 最后将三部分打包发送给接收方:{ E, IV, C, S }

接收方收到后,按反向步骤依次:

  1. RSA 解密拿到对称密钥 K
  2. K, IV 解 AES,得到明文 M′
  3. M′ 再做 SHA‑256,得到 H′,用发送方公钥验证 H′ 是否匹配签名 S

这样既保证了内容的保密性,又保证了发送者身份的不可否认性完整性


二、代码流程详解

你给出的代码示例,主要实现了上面第 1、2 步(签名 + 对称加密 + 解密 + 验签),我们分块看:

1
2
3
4
5
6
7
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad

from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256
  • 引入了 PyCryptodome 库的 RSA, AES, SHA256, PKCS#1 v1.5 签名 以及 随机数、填充 工具。

1. 哈希函数

1
2
def sha256_hash(data: str):
return SHA256.new(data.encode())
  • 功能:对字符串 data 做 SHA‑256,返回一个 Hash 对象,供签名或验证使用。

2. RSA 签名/验签

1
2
3
4
5
6
7
8
9
def rsa_sign(private_key, hash_obj):
return pkcs1_15.new(private_key).sign(hash_obj)

def rsa_verify(public_key, hash_obj, signature):
try:
pkcs1_15.new(public_key).verify(hash_obj, signature)
return True
except (ValueError, TypeError):
return False
  • 签名:用私钥对 hash_obj 进行 PKCS#1 v1.5 签名,返回原始字节串 signature
  • 验签:用公钥验证签名,若匹配则返回 True,否则抛异常并返回 False

3. AES-CBC 加解密

1
2
3
4
5
6
7
def aes_encrypt(plaintext: bytes, key: bytes, iv: bytes) -> bytes:
cipher = AES.new(key, AES.MODE_CBC, iv)
return cipher.encrypt(pad(plaintext, AES.block_size))

def aes_decrypt(ciphertext: bytes, key: bytes, iv: bytes) -> bytes:
cipher = AES.new(key, AES.MODE_CBC, iv)
return unpad(cipher.decrypt(ciphertext), AES.block_size)
  • 加密
    1. key, iv 构造 AES-CBC 对象
    2. 对明文 plaintext 先做 PKCS#7 填充(块长 16 字节),再加密
  • 解密:反向解密并去除填充

4. 主流程

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
if __name__ == '__main__':
# 1)准备原文
plain_text = "Hey"

# 2)哈希 + 签名
msg_hash = sha256_hash(plain_text)
rsa_key = RSA.generate(2048)
private_key = rsa_key
public_key = rsa_key.publickey()
signature = rsa_sign(private_key, msg_hash)

# 3)把原文和签名拼在一起,统一做对称加密
combined_data = plain_text.encode() + signature

# 4)生成随机 AES 密钥 & IV
aes_key = get_random_bytes(16) # 128 位
aes_iv = get_random_bytes(16)

# 5)AES 加密
cipher_text = aes_encrypt(combined_data, aes_key, aes_iv)

# ——网络传输或存储——

# 6)AES 解密
decrypted_data = aes_decrypt(cipher_text, aes_key, aes_iv)

# 7)分离出原文和签名
sig_len = len(signature)
recovered_msg = decrypted_data[:-sig_len]
recovered_signature = decrypted_data[-sig_len:]

# 8)验签
recovered_hash = sha256_hash(recovered_msg.decode())
valid = rsa_verify(public_key, recovered_hash, recovered_signature)

# 9)输出
print("密文:", cipher_text)
print("消息是否被篡改:", not valid)
print("还原后的消息:", recovered_msg.decode())
  1. 生成 RSA 密钥对
  2. 签名:对 "Hey" 的 SHA‑256 做签名
  3. 拼接:为了简化,把签名字节直接追加到明文后面
  4. AES-CBC 加密:对拼接后的数据加密
  5. AES-CBC 解密
  6. 分离出明文和签名,再次做 SHA‑256,验签

注意:代码中并没有把 aes_key 用 RSA 公钥加密;在真实场景下,应当将 aes_key 也封装进消息里,保证对称密钥的机密性。


三、完整示例:加上“密钥封装”的改进

下面给出一个更完整的示例,加入“用接收方 RSA 公钥加密 AES 密钥”这一步:

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
from Crypto.Cipher import AES, PKCS1_OAEP
from Crypto.Random import get_random_bytes
from Crypto.Util.Padding import pad, unpad
from Crypto.PublicKey import RSA
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256

# …(前面 sha256_hash, rsa_sign, rsa_verify, aes_encrypt, aes_decrypt 一样)…

if __name__ == '__main__':
# 原文
M = "Hello, world!"

# 1. 签名
H = sha256_hash(M)
sender_rsa = RSA.generate(2048)
S = rsa_sign(sender_rsa, H)

# 2. 对称加密原文
aes_key = get_random_bytes(16)
iv = get_random_bytes(16)
C = aes_encrypt(M.encode(), aes_key, iv)

# 3. RSA 封装 AES 密钥
recipient_rsa = RSA.generate(2048)
cipher_rsa = PKCS1_OAEP.new(recipient_rsa.publickey())
E = cipher_rsa.encrypt(aes_key) # RSA-OAEP 加密

# 打包发送:{ E, iv, C, S }
package = {
'E': E,
'iv': iv,
'C': C,
'S': S,
'pub_sender': sender_rsa.publickey()
}

# ——接收方——
# 4. 解 RSA 封装的 AES 密钥
rsa_dec = PKCS1_OAEP.new(recipient_rsa)
aes_key_recv = rsa_dec.decrypt(package['E'])

# 5. 解 AES
M_rec = aes_decrypt(package['C'], aes_key_recv, package['iv']).decode()

# 6. 验签
H_rec = sha256_hash(M_rec)
ok = rsa_verify(package['pub_sender'], H_rec, package['S'])

print("明文恢复:", M_rec)
print("签名验证通过?", ok)

这样就完成了:

  1. 签名(防篡改+身份认证)
  2. 对称加密(高效保密)
  3. 密钥封装(保证对称密钥的机密性)

小结

  • SHA‑256 负责生成消息摘要。
  • RSA 私钥签名 + 公钥验签 保证消息未被篡改且确实来自发送方。
  • AES-CBC 高效地加密大段数据。
  • RSA-OAEP(或 PKCS#1 v1.5)封装对称密钥,既保密又兼容公钥基础设施。

这套混合加密+签名方案,正是很多安全协议(如 TLS)在应用层面使用的基本思路。希望这个分步详解,能让你对每一步的“为什么要这样做”和“代码如何实现”都清晰明了!如果还有任何疑问,随时提~