前言时刻:
总结一波经典密码学中的几个经典加密算法。这里面有一个小插曲,好玩的是,我之前做项目时需要对一个链接进行加密和解密,我首先想到的是用 base64 进行加密,因为这个算法很成熟,但是这种算法是防君子不能防小人,网上随便搜 base64 解密,会出现一堆解密网站。所以我就想着对使用 base64 加密后的数据的最后10位进行替换,如果是字母就往后偏移4位,是数字默认跳过。效果很好,也解决了问题,不过最终还是采用 AES-128加密算法。
当我看到经典密码学中的代换密码
中的凯撒密码时,我傻了,这不就是一样的思路吗😂。突然间发现自己的思想落后了一个世纪呢,哈哈。
经典密码学一般采用代换技术和置换技术对数据混淆加密,虽然看起来简单,但是这个思想很重要,以至于后来的标准加密算法 DES 和 AES 里面都有使用代换和置换技术。
1、代换密码
代换密码包括:单表代换密码、多表代换密码、一次一密等
-
凯撒密码,核心思想是对每个字母用其向后偏移 k 位的字母替换。解密时将字母向前偏移三位。
缺点:容易被破解,统计经常使用的单词字母,然后与密文进行对比,就可轻易的得到加密规则。
# 加密:C = (p+k) mod 26 # 解密:P = (C-k) mod 26 # 1、凯撒密码 # 设k为3 k = 3 plaintext = "cipherxyz" ciphertext = " ".join([chr((ord(i)- ord('A') + k)%26 + ord('A')) for i in plaintext.upper()]) print(f"明文:{plaintext}\n密文:{ciphertext}") # 明文:cipherxyz # 密文:F L S K H U A B C
-
单表代换密码,核心思想是明文中的每个字母都由一个特定的字母替换。相对于凯撒密码做了秘钥复杂度升级,每次加密时,第一个字母用26个字母中的一个随机字母替代,第二个用剩下的 25 中的随机一个……,这样秘钥复杂度就有 26! 。缺点:仅仅增强了秘钥复杂度,但是还是没有改进攻击者对密文进字母频率统计的问题,容易被破解。
-
多表代换密码,核心思想是,明文中的每一个字母都由多个可能的字母替换。这个厉害了,简直就是单表代换的升级版,避免了字母频率统计问题。著名的多代换密码算法有Playfair密码、Vibenere密码等
1.1 Playfair密码
这个思想很厉害也很巧妙,实现了一对多的可能性,可有效避免攻击者对字母频率进行分析破击秘钥。
生成秘钥规则:
-
用户自行给出一个秘钥单词如:goodluck,
-
将其放入 5 X 5 的矩阵中,按照从左到右,从上到下的原则填充。
-
应去除被当做秘钥的单词中的重复字母
goodluck -> godluck
,不足25个,剩下的按照字母表的顺序,去除和秘钥中的字母重复的,按顺序依次填充。G O D L U C K A B E F H I/J M N P Q R S T V W X Y Z
生成密文规则:
-
用户给出明文如:
balloon
-
将其按照2位一组划分,不足2位最后补一个字母(可自行设置)如:
k
,或者2位字母一样的中间插入一个字母设:k
。例如:balloon -> ba lk lo on
-
落在矩阵中的同一行的两个字母,用其对中的字母右边的字母代换。若是最右边的字母则用该行最左边的字母代换。例如:ba变成EB,wy变成XZ。
-
落在矩阵中的同一列的两个字母,用其对中的字母下面的字母代换。若是最下面的字母则用该行最上面的字母代换。例如:cf变成FP,bs变成MY。
-
其他的字母按照,取两个字母所在行和列的各自对角线上的值。例如:cq变成kp,ks变成B
-
手写代码实现Playfair算法:
很费时间,不过还是完成了。
初始化:
# 手写 哈哈 勇气 import numpy as np # 1、生成秘钥 def set_key(): """生成秘钥""" # 1.1 秘钥去重 main_key_list = list(main_key.upper()) main_key_set = list(set(main_key_list)) # 去重 main_key_set.sort(key=main_key_list.index) # 按原秘钥的顺序排序 # 1.2 整合作为秘钥的字母 alphabet_list = list("ABCDEFGHIKLMNOPQRSTUVWXYZ") for i in main_key_set: alphabet_list.remove(i) key = main_key_set + alphabet_list # 拼接秘钥 # 1.3 生成5 X 5的矩阵 key_matrix = np.array(key).reshape(5, 5) # 变成5 X 5的矩阵 print(key_matrix) return key_matrix # 2、处理明文 def deal_plaintext(pad='m'): """对明文进行 分组 填充""" plain_list, i= [], 0 # i:用于切片明文 pad_num = 0 # 记录填充的个数,用于后面计算,是否剩余最后一位没分组 while i<len(plaintext)-1: # 用一下冒泡排序的思想,从左向右扫描 if plaintext[i] == plaintext[i+1]: plain_list.append(plaintext[i]+pad) pad_num += 1 i+=1 else: plain_list.append(plaintext[i:i+2]) i+=2 # 如果:剩余的最后一位时,也和填充pad组成一组 if len(plain_list)*2 < pad_num + len(plaintext): plain_list.append(plaintext[-1] + pad) return plain_list
加密算法:
def encrypt(text_list, key): """加密函数""" encrypt_text = '' h, w = key.shape # key矩阵的行数和宽数,其实就是5X5 for each_t in text_list: m, n = tuple(each_t.upper()) x1, y1 = np.where(key==m) # numpy中查询元素索引序号,用于判断两元素行列 x2, y2 = np.where(key==n) # 1. 同一行情况下 if x1 == x2: encrypt_text += key[x1, min(y1, y2)][0] # 先算最左边的,x右加1一定不会超界 encrypt_text += key[x1, max(y1, y2)][0] if max(y1, y2) != w else key[x1, 0][0] # 2. 同一列情况下 elif y1 == y2: encrypt_text += key[min(x1, x2), y1][0] # 先算最上边的,y下加1一定不会超界 encrypt_text += key[max(x1, x2), y1][0] if max(x1, x2) != h else key[0, y1][0] # 3. 对角线 else: encrypt_text += key[x1, y2][0] encrypt_text += key[x2, y1][0] return encrypt_text
解密算法:
def decipher(text, key, pad='m'): """解密函数""" decipher_text, index = '', 0 h, w = key.shape # key矩阵的行数和宽数,其实就是5X5 while index<len(text): m, n = tuple(text[index:index+2]) x1, y1 = np.where(key==m) x2, y2 = np.where(key==n) # 1. 同一行情况下 if x1 == x2: # 先算最右边的,x右加1一定不会超界 和加密是相对的 decipher_text += key[x1, max(y1, y2)-1][0] decipher_text += key[x1, min(y1, y2)-1][0] if min(y1, y2) != 0 else key[x1, w][0] # 2. 同一列情况下 elif y1 == y2: decipher_text += key[max(x1, x2)-1, y1][0] decipher_text += key[min(x1, x2)-1, y1][0] if min(x1, x2) != 0 else key[h, y1][0] # 3. 对角线 else: decipher_text += key[x1, y2][0] decipher_text += key[x2, y1][0] index += 2 # 2、处理解密后的明文中的含有填充的字母pad nums_list = [] # 存放是pad='m'的索引序号,等会删除 decipher_t_list = list(decipher_text) for i in range(len(decipher_t_list)-2): if i%2==0 and decipher_t_list[i] == decipher_t_list[i+2] and decipher_t_list[i+1] == pad.upper(): nums_list.append(i+1) # 判断最后一位,若等于pad则删除 if decipher_t_list[-1] == pad.upper(): nums_list.append(-1) # 注意:这里对要删除的索引进行倒序迭代。防止正序迭代删除索引,列表会进行自动向前补充移位,导致索引不对应。 for i in nums_list[::-1]: decipher_t_list.pop(i) return ''.join(decipher_t_list).lower()
# 初始化的明文和秘钥 plaintext = "cipheerxyz" # 明文 main_key = 'goodluck' # 用户设置的秘钥 key = '' # 真正参与加密的的秘钥 pad = 'm' key = set_key() plaintext_group = deal_plaintext() print(plaintext_group) # ['ci', 'ph', 'em', 'er', 'xy', 'zm'] encrypt_text = encrypt(plaintext_group, key) print(encrypt_text) # AFQFBNATXYYN deciper_text = decipher(encrypt_text, key) print(deciper_text) # cipheerxwz
Playfair总结:
解密函数我感觉写的有点问题,但是搞不懂。就是遇到如果明文中有两个重复字母中间夹了一个 pad 字母,那岂不是在解密函数中就被误删除了?等有时间在研究一下。
1.2 Vigenere密码
这个算法我不写代码了,有点费时间,书后面还有很多内容呢😂,不行写代码有助于理解算法,得写。
Vigenere密码比较简单,很容易理解。
生成秘钥规则:
-
用户自行给出一个秘钥单词如:goodluck,
-
重复循环拼接,补齐26位,如:goodluckgoodluckgoodluckgo。
生成密文规则:
- 用户给出明文如:
i like python
- 去除里面的空格如:ilikepython
- 加密规则,设26个字母中 a 代表0,依次递增1。从第一个明文字母开始 i ,对应第一个秘钥字母 g ,密文是就是
i + g mod 26=i + 7mod26=p
- 秘钥:goodluckgoodluckgoodluckgo
- 明文:ilikepython
- 密文:ozwnpjadncb
- 手写代码:
# Vigenere代换密码 # 初始化的明文和秘钥 plaintext = "i like python" # 明文 main_key = 'goodluck' # 用户设置的秘钥 key = '' # 真正参与加密的的秘钥 def set_key(): global key key = (main_key*(int(26/len(main_key))+1))[0:26] print(f"key:{key}") return key def encrypt(plaintext, key): encrypt_text, index = '', 0 while index<len(plaintext): text_ord = ord(plaintext[index]) - ord('a') key_iv = ord(key[index]) - ord('a') # 秘钥字母相对于 'a' 的偏移量 encrypt_text = encrypt_text + chr(( text_ord+ key_iv)%26 + ord('a')) index+=1 print(f"encrypt_text:{encrypt_text}") return encrypt_text print(f"plaintext:{plaintext.replace(' ', '')}") set_key() encrypt(plaintext.replace(' ', ''), key) """ plaintext:ilikepython key:goodluckgoodluckgoodluckgo encrypt_text:ozwnpjadncb """
总结:
看似枯燥的理论加密算法,其实仔细读下来,也不难,如果动手写下加密算法,记得就更牢固了。
参考链接:
https://baike.baidu.com/item/playfair%E5%AF%86%E7%A0%81/8999814?fr=aladdin