Hill加密算法(Python)-优化版

前言

​ 在上一篇博客中,我简单的实现了希尔加密算法,只支持三个小写字母的字符串。在实际中,往往要求输入的字符串很长,甚至包含一些特殊的字符串。因此在最近我又优化了一下上次的代码,支持输入任意长度的字符串,但是在解密时需要输入原始的字符串长度。

实现过程

​ 对于输入任意长度的字符串都能进行加密,主要思想是对于用户输入的字符串进行切片,如果用户输入的不足密钥阶数的整数倍,则需要填充固定的字符。在我的代码中,我使用的是固定长度的密钥矩阵(三阶),因此需要判断用户输入的字符串长度是否为3的整数倍,不满足时就向字符串转换的数字列表后面填充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
def encrypt(text_list):
# 字符串转换为列表
list_t = []
for ch in text_list:
for key, value in alphabet.items():
if ch == value:
list_t.append(key)
# 填充数字
while (len(list_t) % 3) != 0:
list_t.append(0)

# 切分列表
list_ts = []
for i in range(0, len(list_t), 3):
list_ts.append(list_t[i:i+3])
# print(f"切分列表后的结果为:{list_ts}")
# 与密钥矩阵相乘
list_n = []
for List in list_ts:
list_x = ((numpy.array(List) @ key_list) % 26).tolist()
list_n.append(list_x)

# 列表转字符串密文
list_str = ""
for its in list_n:
for it in its:
list_str += alphabet[it]
# list_str = list_str[:len(text_list)]

print(f"密文为:{list_str}")

​ 解密时首先需要用户输入待解密的字符串的长度,这一步是因为需要按照用户输入的长度来切割解密的字符串。因为在加密过程中填充了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
def decrypt(ciphertext, n):
# 字符串转数字列表
num_list = []
for i in ciphertext:
for key, value in alphabet.items():
if value == i:
num_list.append(key)
# 填充数字
while (len(num_list) % 3) != 0:
num_list.append(0)
# 切分列表
chunks = []
for i in range(0, len(num_list), 3):
chunks.append(num_list[i:i+3])
# 与解密矩阵相乘
decrypted_blocks = []
for chunk in chunks:
decrypted = ((numpy.array(chunk) @ key_list_f) % 26).tolist()
decrypted_blocks.append(decrypted)

# print(f"相乘后的解密列表为:{decrypted_blocks}")
# 转换为字符串
list_str = ""
for nums in decrypted_blocks:
for num in nums:
if num in alphabet:
list_str += alphabet[num]

list_str = list_str[:int(n)]
print(list_str)

完整代码如下:

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

# 定义一个转换字母表
alphabet = {0: 'a', 1: 'b', 2: 'c', 3: 'd', 4: 'e', 5: 'f', 6: 'g', 7: 'h', 8: 'i', 9: 'j', 10: 'k', 11: 'l',
12: 'm', 13: 'n', 14: 'o', 15: 'p', 16: 'q', 17: 'r', 18: 's', 19: 't', 20: 'u', 21: 'v', 22: 'w',
23: 'x', 24: 'y', 25: 'z'}

key_list = numpy.array([[6, 13, 20], [24, 16, 17], [1, 10, 15]]) # 密钥矩阵
key_list_f = numpy.array([[8, 21, 21], [5, 8, 12], [10, 21, 8]])


# 加密算法
def encrypt(text_list):
# 字符串转换为列表
list_t = []
for ch in text_list:
for key, value in alphabet.items():
if ch == value:
list_t.append(key)
# 填充数字
while (len(list_t) % 3) != 0:
list_t.append(0)

# 切分列表
list_ts = []
for i in range(0, len(list_t), 3):
list_ts.append(list_t[i:i+3])
# print(f"切分列表后的结果为:{list_ts}")
# 与密钥矩阵相乘
list_n = []
for List in list_ts:
list_x = ((numpy.array(List) @ key_list) % 26).tolist()
list_n.append(list_x)

# 列表转字符串密文
list_str = ""
for its in list_n:
for it in its:
list_str += alphabet[it]
# list_str = list_str[:len(text_list)]

print(f"密文为:{list_str}")


# 解密函数 传入参数分别为:待解密的字符串和加密密钥和字符串长度
def decrypt(ciphertext, n):
# 字符串转数字列表
num_list = []
for i in ciphertext:
for key, value in alphabet.items():
if value == i:
num_list.append(key)
# 填充数字
while (len(num_list) % 3) != 0:
num_list.append(0)
# 切分列表
chunks = []
for i in range(0, len(num_list), 3):
chunks.append(num_list[i:i+3])
# 与解密矩阵相乘
decrypted_blocks = []
for chunk in chunks:
decrypted = ((numpy.array(chunk) @ key_list_f) % 26).tolist()
decrypted_blocks.append(decrypted)

# print(f"相乘后的解密列表为:{decrypted_blocks}")
# 转换为字符串
list_str = ""
for nums in decrypted_blocks:
for num in nums:
if num in alphabet:
list_str += alphabet[num]

list_str = list_str[:int(n)]
print(list_str)


if __name__ == "__main__":
while True:
print("-------------------------------------Hill加密系统----------------------------------------------")
print("1.加密")
print("2.解密")
print("3.退出")
choose = input("请输入:")
if choose == '1':
text = input("输入待加密的三位小写字母字符串:")
encrypt(text)
elif choose == '2':
key_n = input("输入待解密的字符串数量:")
key_text = input("输入待解密的小写字母密文:")
decrypt(key_text, key_n) # 解密
elif choose == '3':
print("感谢使用,期待下次使用!")
break
else:
print("---------------------------------------非法输入!----------------------------------------------")

总结

​ 在写这个算法的时候,还犯到了一个特别低级的错误。当时为了实现代码的便捷性,我就想把密文自动的裁切为和用户输入的字符串长度相同的长度,这样在解密的时候直接输入裁切后的字符串密文就可以了,免去了输入字符串长度的这个流程。当时测试发现,加密和解密可以正常的执行输出,但是加密的字符串长度如果不是3的倍数,解密的时候从第四位开始解密出来的内容就和原文不同。检查代码十几遍都没检查出来有什么错误,让GPT检查也检查不出来。最后多亏小橙子发现了我的错误。错误的原因在于,对于加密的密文,因为是先填充0,然后再和加密矩阵相乘,所以得到的密文是不可切割的。而我再代码中的处理是,切割后如果解密时不足3的倍数位,在密文转换的列表后填充0,这样直接就导致加密和解密前后不一的问题,所以会出现不同的解密结果。这个错误我想了一下午都没有发现,最后发现是一个 这么低级的错误…

​ 最后,感谢观看!