目录
加密过程加密原理代码加密的思路代码预览代码解析与运行效果唯密文解密过程解密原理代码预览代码分析与运行结果加密过程
加密原理
字母表中每一个字母都对应着一个数字,从A~ Z依次是0~25,给定一个明文与密文,假设明文是showmaker,密钥是bde,则密钥三个字母对应的三个数字分别是1,3,4,而密钥的长度为3,我们以每组3个字符的长度将明文分为 sho wma ker三组,其中每组的第一个字母s w k需要加上1,也就是向后取一个字母,得到t x l,而每组的第二个字母需要+3,每组的第三个字母+4,其中,若加密得到的字母超出z(即25),则从a开始重新计数,也就是取26的模,明文全部加密按顺序得到的字符串即为密文。
明文:showmaker
密钥:bde
密文:tksxpelhv
代码加密的思路
维吉尼亚密码的加密过程较为简单,只需要通过循环将明文字母的Unicode码加上相应的数字即可实现,要注意的是输入的明文中可能存在大小写与标点符号,需要将标点符号剔除,并将其统一转化为大写形式或者小写形式。
所以主要有以下步骤:
1、获取密钥长度与密钥中字母所对应的0~25数字。
2、剔除明文中的标点符号并统一大小写
3、通过循环将所有明文字母的Unicode码加上相应的数字(mod 26)
代码预览
str = input("请输入密钥:")length = len(str)str.lower()#将输入字母全部转换为小写list_cipher = []#创建空列表,用于存放密钥对应的数字#下面将将密钥对应的数字输入到列表list_cipher中for i in str:no_cipher = ord(i) - ord('a')list_cipher.append(no_cipher)plaintext_path = input("请输入明文所在txt文件的路径:")#下面获取明文f_1 = open(plaintext_path,'r')plaintext_0 = f_1.read()f_1.close()plaintext = ''#下面将一个个字符的Unicode码进行比对,来达到去除标点符号的目的for i in plaintext_0:if (ord('a') <= ord(i) and ord('z') >= ord(i)) or (ord('A') <= ord(i) and ord('Z') >= ord(i)):plaintext += iplaintext = plaintext.lower()count = 0cipher = ''#下面是加密程序while count <= (len(plaintext)-1):for i in range(length):if count + i <= (len(plaintext)-1):cipher_word = chr((ord(plaintext[count+i]) + list_cipher[i] - ord('a')) % 26 + ord('a'))cipher += cipher_wordcount += lengthf_2 = open('C:\\Users\\辉夜大小姐\\PycharmProjects\\pythonProject\\cipher.txt','w')for i in range(len(cipher)):f_2.write(cipher[i])f_2.close()
代码解析与运行效果
str = input("请输入密钥:")length = len(str)str.lower()#将输入字母全部转换为小写list_cipher = []#创建空列表,用于存放密钥对应的数字#下面将将密钥对应的数字输入到列表list_cipher中for i in str:no_cipher = ord(i) - ord('a')list_cipher.append(no_cipher)
先输入密钥,假设我输入的密钥是crypto。
length的值即为密钥长度,将密钥转化为小写,每个字母与字母a的Unicode码的差即为其所对应的数字,依次保存在列表list_cipher中。
plaintext_path = input("请输入明文所在txt文件的路径:")#下面获取明文f_1 = open(plaintext_path,'r')plaintext_0 = f_1.read()f_1.close()plaintext = ''#下面将一个个字符的Unicode码进行比对,来达到去除标点符号的目的for i in plaintext_0:if (ord('a') <= ord(i) and ord('z') >= ord(i)) or (ord('A') <= ord(i) and ord('Z') >= ord(i)):plaintext += iplaintext = plaintext.lower()
将明文写在一个txt文件中,如下图。
输入该txt文件所在的路径,而后将内容读到plaintext_0中,将plaintext_0中的所有字符遍历一遍,留下Unicode码在a~z之间或者A到Z之间的字符并保存在plaintext中,达到剔除标点符号的目的,再通过lower函数将其全部小写,方便后续的加密过程。
count = 0cipher = ''#下面是加密程序while count <= (len(plaintext)-1):for i in range(length): if count + i <= (len(plaintext)-1):cipher_word = chr((ord(plaintext[count+i]) + list_cipher[i] - ord('a')) % 26 + ord('a'))cipher += cipher_wordcount += lengthf_2 = open('C:\\Users\\辉夜大小姐\\PycharmProjects\\pythonProject\\cipher.txt','w')for i in range(len(cipher)):f_2.write(cipher[i])f_2.close()
以length(密钥长度)进行for循环,通过Unicode码的转换将明文转化为密文,并写入到另一个txt文件中。我们运行一遍代码看看cipher.txt。
可以看到txt中已经有了写入的密文,如果该txt文件中原本有内容,那也会将原内容进行覆盖。
唯密文解密过程
解密原理
我们先看一个表
这是从众多小说、杂志和报纸上搜集统计出的26个字母出现的相对概率。
本次代码中我使用重合指数法来实现维吉尼亚密码的解密。
用f0,f1,…f25f_0,f_1,…f_{25}f0,f1,…f25来表示A,B,C,……Z出现的频数,则在长度为n的字母串中随机取两个字母,取到相同字母的概率为Ic(x)=∑i=025fi(fi−1)n(n−1)I_c(x)={{\sum_{i=0}^{25}f_i(f_i-1)}\over{n(n-1)}}Ic(x)=n(n−1)∑i=025fi(fi−1),表1.1中的数据可得出Ic(x)≈0.065I_c(x)≈0.065Ic(x)≈0.065。
假设长度为n的密文串为Y=y1y2…ynY=y_1y_2…y_nY=y1y2…yn。将其分割为m组长度相等的子串,具体如下。
Y1=y1ym+1y2m+1…Y_1=y_1y_{m+1}y_{2m+1}…Y1=y1ym+1y2m+1…
Y2=y2ym+2y2m+2…Y_2=y_2y_{m+2}y_{2m+2}…Y2=y2ym+2y2m+2…
┆ ┆ ┆
Ym=ymy2my3m…Y_m=y_my_{2m}y_{3m}…Ym=ymy2my3m…
如果m是密钥字的长度,那么每一个Ic(Yi)I_c(Y_i)Ic(Yi)的值会在0.065附近。
所以我们可以通过循环来从m = 1开始尝试,一直到选定的一个值(比如m = 10),找出其中Ic(Y)I_c(Y)Ic(Y)最接近于0.065时m的值,此时m的值最有可能是密钥的长度。
得到密钥长度后,定义公式Mg=∑i=025pifi+gn′M_g=\sum_{i=0}^{25}{p_if_{i+g}\over{n'}}Mg=∑i=025n′pifi+g(n’为每个子串的长度)(0≤g≤250≤g≤250≤g≤25,代表着26个字母)
若g=kig=k_ig=ki,即ggg对应密钥当前位的正确字母,则应该有Mg≈0.065M_g≈0.065Mg≈0.065。
通过遍历0~25,得到MgM_gMg最接近0.065时的ggg的值,重复该过程m次,而后将得到的数字全部转化为字母顺序排列,即可得到密钥。
代码预览
cipher_path = input("请输入密文所在txt文件的路径:")f = open(cipher_path)cipher = f.read()f.close()cipher = cipher.lower()cipher = ",".join(cipher)list_cipher = cipher.split(",") #将密文中的每个字母转化为列表中的元素#下面这个函数用于记录字母在列表中出现的频数def count_f(unicode,list):length = len(list)count = 0for i in range(length):if chr(unicode) == list[i]:count = count + 1return count#该函数用于将密文列表分为m组,每一组n个def list_M(m,n):list_m = []for i in range(m):for j in range(n):list_m.append(list_cipher[i + j * m])return list_m#下面来求密钥字长度mfor m in range(1,10):n = int((len(list_cipher) - len(list_cipher) % m) / m) #n为每一组的个数,若无法平分则舍弃最后的几个密文list_m = list_M(m, n)#得到的列表list_m每n个为一组,共m组#下面求每一组中各个字母的出现频数list_I = [] #用于保存每一组的重合指数for i in range(m):list_f = [] # 用于记录各个字母出现的频数for j in range(26):unicode_j = ord('a') + jlist_f.append(count_f(unicode_j, list_m[i * n:(i * n + n)]))#下面求每一组的重合指数I = 0for j in range(26):I = I + (list_f[j]*(list_f[j]-1))/(n * (n - 1))list_I.append(I)#下面求平均重合指数以及它与0.065的差值sum = 0for i in range(m):sum = sum + list_I[i]avg_I = sum / mdifference = abs(avg_I-0.065)if m == 1:t = differencek = 1elif m != 1:if difference <= t: #作比较,若这一次的差值小于上一次的差值,说明这一次的重合指数更接近0.065,循环结束后得到的k即为密钥字长度t = differencek = m#得到了密钥的长度,下面来求密钥n = int((len(list_cipher) - len(list_cipher) % k) / k)list_m = list_M(k, n)list_secret = []list_usual = [0.082,0.015,0.028,0.043,0.127,0.022,0.020,0.061,0.070,0.002,0.008,0.040,0.024,0.067,0.075,0.019,0.001,0.060,0.063,0.091,0.028,0.010,0.023,0.001,0.020,0.001]for i in range(k):list_f = [] # 用于记录各个字母出现的频数for j in range(26):unicode_j = ord('a') + jlist_f.append(count_f(unicode_j, list_m[i * n:(i * n + n)]))for g in range(26):sum = 0for p in range(26):sum = sum + list_usual[p]*list_f[(p+g)%26]M = abs((sum / n) - 0.065)if g == 0:t = Msecret = 0elif g != 0:if M <= t:t = Msecret = glist_secret.append(secret)list_str = []for i in range(k):list_str.append(chr(ord('a') + list_secret[i]))str = ''.join(list_str)print(str)list_str.append(chr(ord('a') + list_secret[i]))str = ''.join(list_str)print(str)
代码分析与运行结果
cipher_path = input("请输入密文所在txt文件的路径:")f = open(cipher_path)cipher = f.read()f.close()cipher = cipher.lower()cipher = ",".join(cipher)list_cipher = cipher.split(",") #将密文中的每个字母转化为列表中的元素
读取密文所在txt文件中的内容,并将其全部转化为小写后保存在列表中,每个字母均为一个单独的元素
#下面这个函数用于记录字母在列表中出现的频数def count_f(unicode,list):length = len(list)count = 0for i in range(length):if chr(unicode) == list[i]:count = count + 1return count#该函数用于将密文列表分为m组,每一组n个def list_M(m,n):list_m = []for i in range(m):for j in range(n):list_m.append(list_cipher[i + j * m])return list_m
两个函数的编写,用于后续使用
count_f函数为输入一个字母的Unicode码与一个列表,而后返回该字母在此列表中出现的频数。
list_M函数用于将密文分组。
#下面来求密钥字长度mfor m in range(1,10):n = int((len(list_cipher) - len(list_cipher) % m) / m) #n为每一组的个数,若无法平分则舍弃最后的几个密文list_m = list_M(m, n)#得到的列表list_m每n个为一组,共m组#下面求每一组中各个字母的出现频数list_I = [] #用于保存每一组的重合指数for i in range(m):list_f = [] # 用于记录各个字母出现的频数for j in range(26):unicode_j = ord('a') + jlist_f.append(count_f(unicode_j, list_m[i * n:(i * n + n)]))#下面求每一组的重合指数I = 0for j in range(26):I = I + (list_f[j]*(list_f[j]-1))/(n * (n - 1))list_I.append(I)#下面求平均重合指数以及它与0.065的差值sum = 0for i in range(m):sum = sum + list_I[i]avg_I = sum / mdifference = abs(avg_I-0.065)if m == 1:t = differencek = 1elif m != 1:if difference <= t: #作比较,若这一次的差值小于上一次的差值,说明这一次的重合指数更接近0.065,循环结束后得到的k即为密钥字长度t = differencek = m
这里m的循环我只写了(1,10),只进行到9,也就是说只能破解9位以下的密钥。
由于密文的长度n不一定每一次都能整除m,所以直接舍弃最后一部分,来保证可以将其分为长度相等的m组。
list_m列表的顺序为[y1,ym+1,…y(n−1)∗m+1,y2…ymny_1,y_{m+1},…y_{(n-1)*m+1},y_2…y_{mn}y1,ym+1,…y(n−1)∗m+1,y2…ymn],即依次为Y1,Y2…YmY_1,Y_2…Y_mY1,Y2…Ym。
list_f列表的顺序与list_m相同,后者的元素是密文中的字母,前者的元素是其对应字母的在每一组中的出现频数(例如字母a在Y1Y_1Y1中的出现频数)
list_I中记录的依次是Y1,Y2,…YmY_1,Y_2,…Y_mY1,Y2,…Ym的重合指数。
而后取出list_I中的元素求总的平均重合指数,并求与0.065的差值,每一次m与前一次m的差值进行比较,用k保留差值较小时的m,循环结束后得到的k即为密钥的长度。
#得到了密钥的长度,下面来求密钥n = int((len(list_cipher) - len(list_cipher) % k) / k)list_m = list_M(k, n)list_secret = []#用于保存密钥字母对应的数字list_usual = [0.082,0.015,0.028,0.043,0.127,0.022,0.020,0.061,0.070,0.002,0.008,0.040,0.024,0.067,0.075,0.019,0.001,0.060,0.063,0.091,0.028,0.010,0.023,0.001,0.020,0.001]for in range(k):list_f = [] # 用于记录各个字母出现的频数for j in range(26):unicode_j = ord('a') + jlist_f.append(count_f(unicode_j, list_m[i * n:(i * n + n)]))for g in range(26):sum = 0for p in range(26):sum = sum + list_usual[p]*list_f[(p+g)%26]M = abs((sum / n) - 0.065)if g == 0:t = Msecret = 0elif g != 0:if M <= t:t = Msecret = glist_secret.append(secret)list_str = []for i in range(k):list_str.append(chr(ord('a') + list_secret[i]))str = ''.join(list_str)print(str)
此时密钥长度k已经得到,利用list_M函数来进行分成k组。
list_usual中的元素依次为上面表中A~Z的出现概率,用其进行MgM_gMg的计算,并与0.065进行比较,将最靠近0.065时的ggg值保存在list_secret列表中,最后利用Unicode码转化为字母并保存在字符串str中并打印出str,即密钥。
前面我们利用加密程序对明文加密得到了密文,现在我们将得到的密文在解密程序中运行来看看结果。
可以看到密钥完全正确。
如果觉得《用python实现维吉尼亚密码的加密与唯密文解密》对你有帮助,请点赞、收藏,并留下你的观点哦!