该算法参考于现代密码学第二版2.3.2多表古典密码统计分析
由于书上内容的介绍难以理解,下面我将把我个人对Kasiski测试法和重合指数法这两种方法的理解用文字和代码表示
参考文章
维吉尼亚密码的破解算法及python代码实现_Vio1let的博客-CSDN博客
1.首先,需要一篇明文用采用Vigenere来加密
下面我将以课本给出的例子为例进行运算
课本明文(密文3)为(这里除去了标点符号和全部大写):THEALMONDTREEWASINTENTATIVEBLOSSOMTHEDAYSWERELONGEROFTENENDINGWITHMAGNIFICENTEVENINGSOFCORRUGATEDPINKSKIESTHEHUNTINGSEASONWASOVERWITHHOUNDSANDGUNSPUTAWAYFORSIXMONTHSTHEVINEYARDSWEREBUSYAGAINASTHEWELLORGANIZEDFARMERSTREATEDTHEIRVINESANDTHEMORELACKADAISICALNEIGHBORSHURRIEDTODOTHEPRUNINGTHEYSHOULDHAVEDONEINNOVEMBER
将明文以Vigenere的加密方式变为密文
此处密钥为:janet
import operator
fo=open("明文3.txt","r")
fname=fo.readlines()
fo.close()
str="".join(fname)
zfc=""
for i in str:
if 64<ord(i.upper())<92:
zfc+=i
else:
continue
d={}
for i in zfc:
if i in d:
d[i] += 1
else:
d[i] = 1
d = sorted(d.items(), key=operator.itemgetter(1), reverse=True)
print("明文词频统计:",d)
result=[]
jg=[]
zz=[]
for i in zfc:
j=i.upper()
t=ord(j)-65
result.append(t)
x=input("请输入密钥:")
for i in x:
m = ord(i.upper()) - 65
jg.append(m)
m = len(result)
n = len(jg)
k = m // n + 1
jg = jg * k
result = [(x + y) % 26 for x, y in zip(result, jg)]
for i in result:
t = chr(i + 65)
zz.append(t)
fname2 = open("密文3.txt", "w+")
fname2.writelines(zz)
fname2.close()
str="".join(zz)
dict={}
for j in str:
if j in dict:
dict[j] += 1
else:
dict[j] = 1
dict = sorted(dict.items(), key=operator.itemgetter(1), reverse=True)
print("密文词频统计:",dict)
密文3:CHREEVOAHMAERATBIAXXWTNXBEEOPHBSBQMQEQERBWRVXUOAKXAOSXXWEAHBWGJMMQMNKGRFVGXWTRZXWIAKLXFPSKAUTEMNDCMGTSXMXBTUIADNGMGPSRELXNJELXVRVPRTULHDNQWTWDTYGBPHXTFALJHASVBFXNGLLCHRZBWELEKMSJIKNBHWRJGNMGJSGLXFEYPHAGNRBIEQJTAMRVLCRREMNDGLXRRIMGNSNRWCHRQHAEYEVTAQEBBIPEEWEVKAKOEWADREMXMTBHHCHRTKDNVRZCHRCLQOHPWQAIIWXNRMGWOIIFKEE
2.Kasiski测试法推断密钥长度
Kasiski测试法:密文中出现两个相同字母组,它们所对应的明文字母相同的可能性很大,这样的两个密文字母组之间的距离可能为密钥长度的整数倍。(注意:相同的字母组长度最少为3)
在该段密码中我选取字符长度为3的进行测试
def miyao_length(cipher): jh=[] d={} b=[] wz=0 for i in range(len(cipher)-2):#遍历整个密文 str = cipher[i:i+3] jh.append(str)#把整个密文按照三分字符进行统计 for j in jh:#统计三个字符的相同字符组出现的次数 if j in d: d[j]+=1 else: d[j]=1 max_list=[] max_value =max(d.values())#得到最多出现字符的次数为5 for m, n in d.items(): if n == max_value: max_list.append(m)#把出现最多次的所有字符串找到CHR for i in max_list: a = [m.start() for m in re.finditer(i,cipher)]#查找这些字符的位置[0,165,235,175,285] s = [] if a[0]==0: del a[0]#如果这个字符出现在最前面,则后面字符出现的位置即距离 for j in range(2, a[0] + 1):#这个密钥长度只有可能是165除1以外的因子 if a[0] % j == 0: s.append(j)#[5,15,165] for n in s: num = 0 for k in a[1:len(a)]:#找出[165,235,175,285]公因子 if k % n == 0: num += 1 if num == len(a)-1: num = n b.append((i,num))#以元组的形式保存('CHR',5) if len(b)!=0: print("根据密文中子串%s的位置推断密钥长度最有可能为:%d" % (max(b))) else: print("在三个字符的字符串中未有满足条件的字符串!")
3.计算重合指数MIC,分组进一步确认密钥长度
假设密钥长度在10以内,进行分组;
当密钥长度为5,则
第一组:CVABWEBQBUAWWQRWWXANTBDPXXRDWBFAXCWMNJJFAIACNRNCATBWKDMCDCQQXWK
第二组:HOEITESEWOOEGMFTIFUDSTNSNVTNDPASNHESBGSEGEMRDRSHEAIEORTHNHOANOE
第三组:RARANOBQRASAJNVRAPTCXUGRJRUQTHLVGRLJHNGYNQRRGINRYQPVEEBRVRHIRIE
第四组:EHAXXPQEVKXHMKGZKSEMMIMEEVLWYXJBLZEIWMLPRJVELMRQEEEKWMHTRCPIMI
第五组:EMTXBHMRXXXBMGXXLKMGXAGLLPHTGTHFLBKKRGXHBTLMXGWHVBEAAXHKZLWWGF
重合指数即第一组出现字母'A'的次数乘以(字母'A'的次数-1)/第一组的长度乘以(第一组的长度-1),一直到字母z的求和,其他四组也进行这样的运算,最后全部加起来求平均值得出来的MIC即称为密钥长度为5的重合指数
此时,得到9个密钥长度的平均重合指数,进行判断看哪个MIC值最接近0.065,如果密文的重合指数非常接近0.065,那么说明它使用了单表替换;如果两段密文的重合指数相似,那么说明它们使用了同一种代换加密方式,即使用同一密钥进行加密。
def countMIC(t): d = {} for i in t:#统计改组每个字母出现的次数 if i in d: d[i] += 1 else: d[i] = 1 dict = sorted(d.items(), key=operator.itemgetter(1), reverse=True)#按照出现的次数从大到小排序 sum = 0 result=[] for i in range(len(dict)):#计算重合指数 sum += dict[i][1] * (dict[i][1] - 1) return sum/(len(t)*(len(t)-1)) def miyao_len(cipher): fg = list(cipher) miyaosum=[] for i in range(1, 10):#假设密钥长度在10以内,可以改 num=0 for j in range(i): t = fg[j::i]#根据密钥长度分组 MIC=countMIC(t)#计算重合指数的函数 num+=MIC#求和以便求平均值 if j+1==i: print("密钥长度为{}时,平均重合指数为{:.5f}".format(i,num/i)) miyaosum.append(round((num/i),6)) figure_MIC(miyaosum) min=1 for i in range(len(miyaosum)):#判断9种密钥长度哪个最接近0.065 if abs(miyaosum[i]-0.065)<min: min=abs(miyaosum[i]-0.065) n=i+1 print("根据重合指数进一步证实密钥长度为:{}".format(n)) return n
4.确定密钥长度为5后,计算各组之间的交互重合指数
交互重合指数:先分组同上,交互即计算第i组和第j组一直第五组,其中j>i,那么一共就是有10组;
重合指数即计算第一组中字母'A'出现的概率*第二组偏移n以后'A'出现的概率一直到字母'Z' 求和,出现26个交互重合指数即偏移量可能为的26个字母,偏移量即第一组保持不变,第二组所有字母往后偏移0~25位,在偏移后会得到新的字母'A'的概率,所以会得到26个不一样的交互重合指数;
i | j | 26个交互重合指数 |
1 | 2 | 0.028 0.027 0.028 0.034 0.039 0.037 0.026 0.025 0.052 0.068 0.044 0.026 0.037 0.043 0.037 0.043 0.037 0.028 0.041 0.041 0.034 0.037 0.051 0.045 0.042 0.036 |
1 | 3 | 0.039 0.033 0.040 0.034 0.028 0.053 0.048 0.033 0.029 0.056 0.050 0.045 0.039 0.040 0.036 0.037 0.032 0.027 0.037 0.036 0.031 0.037 0.055 0.029 0.024 0.037 |
1 | 4 | 0.034 0.043 0.025 0.027 0.038 0.049 0.040 0.032 0.029 0.034 0.039 0.044 0.044 0.034 0.039 0.045 0.044 0.037 0.055 0.047 0.032 0.027 0.039 0.037 0.039 0.035 |
1 | 5 | 0.043 0.033 0.028 0.046 0.043 0.044 0.039 0.031 0.026 0.030 0.036 0.040 0.041 0.024 0.019 0.048 0.070 0.044 0.028 0.038 0.044 0.043 0.047 0.033 0.026 0.046 |
2 | 3 | 0.046 0.048 0.041 0.032 0.036 0.035 0.036 0.030 0.024 0.039 0.034 0.029 0.040 0.067 0.041 0.033 0.037 0.045 0.033 0.033 0.027 0.033 0.045 0.052 0.042 0.030 |
2 | 4 | 0.046 0.034 0.043 0.044 0.034 0.031 0.040 0.045 0.040 0.048 0.044 0.033 0.024 0.028 0.042 0.039 0.026 0.034 0.050 0.035 0.032 0.040 0.056 0.043 0.028 0.028 |
2 | 5 | 0.033 0.033 0.036 0.046 0.026 0.018 0.043 0.080 0.050 0.029 0.031 0.045 0.039 0.037 0.027 0.026 0.031 0.039 0.040 0.037 0.041 0.046 0.045 0.043 0.035 0.030 |
3 | 4 | 0.038 0.036 0.040 0.033 0.036 0.060 0.035 0.041 0.029 0.058 0.035 0.035 0.034 0.053 0.030 0.032 0.035 0.036 0.036 0.028 0.046 0.032 0.051 0.032 0.034 0.030 |
3 | 5 | 0.035 0.034 0.034 0.036 0.030 0.043 0.043 0.050 0.025 0.041 0.051 0.050 0.035 0.032 0.033 0.033 0.052 0.031 0.027 0.030 0.072 0.035 0.034 0.032 0.043 0.027 |
4 | 5 | 0.052 0.038 0.033 0.038 0.041 0.043 0.037 0.048 0.028 0.028 0.036 0.061 0.033 0.033 0.032 0.052 0.034 0.027 0.039 0.043 0.033 0.027 0.030 0.039 0.048 0.035 |
def count_group_MIC(group1,group2,n):#计算交互重合指数的函数 MIC=0 count_1 = [0 for i in range(26)] # [26个0] count_2 = [0 for i in range(26)] for j in range(len(group1)): count_1[ord(group1[j]) - ord('A')] += 1#统计26个字母出现的次数 for i in range(len(group2)): count_2[(ord(group2[i]) - ord('A')+n)%26] += 1#这里的n即偏移量,偏移n后统计字母的频率 for i in range(26): MIC += count_1[i] * count_2[i] / (len(group1) *len(group2))#对应字母的频率相乘求和 return int(MIC*pow(10,3))/1000#为了显示图表方便取其结果小数点后三位(最后一位不四舍五入) def figure_k(pyl_k,lenmiyao):#画出表格的函数 x = PrettyTable(["i", "j", "密文子串的交互重合指数MIC"]) t=int(len(pyl_k)/26) for n in range(2,t): if n*(n-1)==2*t: num=n break n=1 m = 1 for i in range(int(len(pyl_k)/26)): if n<num: n += 1 else: m=m+1 n=m+1 siwei = [] for k in pyl_k[i*26:(i+1)*26]: k=f"{k:.3f}"#取小数点后三位,解决0.04这种问题 siwei.append(k) t=''.join(str(x) for x in siwei) pattern = re.compile('.{5}')#每五个字符插入一个空格 result=' '.join(pattern.findall(t)) x.add_row([m,n, result]) print(x) def group_k(cipher,miyao):#分组函数 fg = list(cipher) MIC_k=[] MIC_I=[] MIC_J=[] pyl_k = [] for i in range(miyao-1): group1 = fg[i::miyao] for n in range(i+1,miyao): group2 = fg[n::miyao] k = [0 for i in range(26)] for j in range(26): k[j] = count_group_MIC(group1, group2, j)#j即偏移量 pyl_k.append(k[j]) if k[j]>0.060:#在0.065附近,鉴于课本取该值(完善算法可按与0.065间的距离进行排序) m = j MIC_k.append(m)#记录偏移量k print("第%d组和第%d组之间偏移为%d时,交互重合指数为%.3f" % (i+1,n+1, m, k[m])) MIC_I.append(i+1)#记录i MIC_J.append(n+1)#记录j,为计算方程式做准备 figure_k(pyl_k,miyao) return MIC_k,MIC_I,MIC_J
5.计算方程式确定密钥之间的关系
根据偏移量可得各个密钥之间的关系:
k1-k2=9
k1-k5=16
k2-k3=13
k2-k5=7
k3-k5=20
k4-k5=11
根据上式可得各个密钥间的关系为 [k1, k1 + 17, k1 + 4, k1 + 21, k1 + 10]
def contact(k,i,j,miyao): k1, k2, k3, k4, k5, k6, k7, k8, k9 = sympy.symbols("k1 k2 k3 k4 k5 k6 k7 k8 k9")#因为假设密钥长度在10以内 daishu=[k1,k2,k3,k4,k5,k6,k7,k8,k9] ds=daishu[0:miyao]#取前五位[k1,k2,k3,k4,k5] MIC_ij = [] sz=[] print("根据偏移量可得各个密钥之间的关系:") for x,y,z in zip(i,j,k): t = 0 if ds[x-1]==0: ds[x - 1]=k1 print("%s-%s=%d"%(ds[x-1],ds[y-1],z))#将式子展示出来 #由于解方程是4个式子解四个未知数,所以要对式子进行筛选 if ds[x-1]==k1:#必定有k1,方便赋值k1=0 MIC_ij.append(ds[x - 1]) if ds[x-1] not in MIC_ij and ds[y-1] in MIC_ij:#式子必须只有一个未知数,才可以解方程 t=1 MIC_ij.append(ds[x-1]) if ds[x-1] in MIC_ij and ds[y-1] not in MIC_ij: t = 1 MIC_ij.append(ds[y- 1]) if t==1: if ds[x-1]==k1: ds[x - 1]=0 sz.append(ds[x - 1]-ds[y-1]-z)#结果筛选后的式子 del ds[0] a = sympy.solve(sz,ds)#{k2: -9, k3: -22, k4: -5, k5: -16} gx=[] for m,n in a.items(): gx.append(n) gx.insert(0,0) gx=[(26+i)%26 for i in gx] gxs=[i+k1 for i in gx]#[k1, k1 + 17, k1 + 4, k1 + 21, k1 + 10] print("根据上式可得各个密钥间的关系为",gxs) return gx
5.确定密钥,同时解密明文
有了密钥之间的关系,就可以得到26个密钥,其中25个都是伪密钥,我通过单表古典密码26个英文字母出现频率统计分析图进行判断唯一的真密钥
子母 | 频率 | 字母 | 频率 |
A | 0.082 | N | 0.067 |
B | 0.015 | O | 0.075 |
C | 0.028 | P | 0.019 |
D | 0.043 | Q | 0.001 |
E | 0.127 | R | 0.060 |
F | 0.022 | S | 0.063 |
G | 0.020 | T | 0.091 |
H | 0.061 | U | 0.028 |
I | 0.070 | V | 0.010 |
J | 0.002 | W | 0.023 |
K | 0.008 | X | 0.001 |
L | 0.040 | Y | 0.020 |
M | 0.024 | Z | 0.001 |
根据该表不难发现字母E的频率最高且相较于其他25个字母高太多了,所以只需要判断解密后明文的26个字母中出现频率最高的是字母E即可(当然这种方法不绝对保证)
def certain_miyao(k,cipher,miyaolen): for i in range(26): t=[(i+j)%26 for j in k]#遍历26个密钥 k1 = t * (len(cipher)// miyaolen + 1) k1=[chr((ord(i)-65-j)%26+65) for i,j in zip(cipher,k1)] d = {} for j in k1: if j in d: d[j] += 1 else: d[j] = 1 dict = sorted(d.items(), key=operator.itemgetter(1), reverse=True) k1="".join(k1) if dict[0][0]=='E': print("由单表古典密码统计分析出现的字母频率判断可知:") t=[chr(i+65) for i in t] word = wordninja.split(k1)#根据得出来的明文字符串按单词进行分割 plain = '' for i in range(len(word)): plain += word[i] plain += ' '#得到可以看得懂的明文 print("当密钥为%s时,明文为%s"%(t,plain))
密文:
cbkznkiyjsrofgnqadnzuqigscvxizgsjwucusrdkxuahgzrhywtvdjeiuwsrrtnpszbvpzncngztbvsrnzuqigscvfjwqgjwcytwdazuqigscvfjwqgjwjhkfdylmcbmhonbmbvdnvbmwbnacjaphhonbmbvdnvbmwbnaublsbdnjjneoroyfmxfhixpzpcozzuqigscvxcvhdmfgxmgovzsqmvzyvwyzmsczoajsejifoakdcrehwhgdehvmtnmvvmesvzifutzfjzoalwqztunwvdvmfhesvzifutzfjzoalwqztunpsnoyfleoxdetbwfsoyfjmfhjuxuagnarsfqydoyfjzsrzeujmfhjuubihrjdfinwsnepcawdnkbobvnmzucmghijjmbscjejnapddehlmqddmfxncqbfpxwfejifpqzhikiyaiozimubwuzufazsdjwdiudzmztivcmgp
密钥:uiozvrb(长度为7)
明文:
It was the best of times.It was the worst of times. It was the age of wisdom. It was the age offoolishness. It was the epoch of belief. It was the epoch of incredulity. It was the season of light. It was the season of darkness. It was the spring of hope. It was the winter of despair.We had everything before us. We had nothing before us. We were all going direct to heaven. We were all going direct the other way. In short, the period was so, far like the present period, that some of its noisiest authorities insisted on its being received, for good or for evil, in the superlative degree of comparison only.
密文:
krkpekmcwxtvknugcmkxfwmgmjvpttuflihcumgxafsdajfupgzzmjlkyykxdvccyqiwdncebwhyjmgkazybtdfsitncwdnolqiacmchnhwcgxfzlwtxzlvgqecllhimbnudynagrttgiiycmvyyimjzqaxvkcgkgrawxupmjwqemiptzrtmqdciakjudnnuadfrimbbuvyaeqwshtpuyqhxvyaeffldmtvrjkpllsxtrlnvkiajfukycvgjgibubldppkfpmkkuplafslaqycaigushmqxcityrwukqdftkgrlstncudnnuzteqjrxyafshaqljsljfunhwiqtehncpkgxspkfvbstarlsgkxfibffldmerptrqlygxpfrwxtvbdgqkztmtfsqegumcfararhwerchvygczyzjaacgntgvfktmjvlpmkflpecjqtfdcclbncqwhycccbgeanyciclxncrwxofqieqmcshhdccughsxxvzdnhwtycmcbcrttvmurqlphxnwddkopqtehzapgpfrlkkkcpgadmgxdlrchvygczkerwxyfpawefsawukmefgkmpwqicnhwlnihvycsxckf
密钥: crypt (长度为5)
明文:
I am alive here, my beloved, for the reason to adore you. Oh!How anxious I have been for you and how sorry I am about all you must have suffered in having no news from us. May heaven grant that this letter reaches you. Do not write to me, this would compromise all of us and above all,do not return under any circumstances. It is known that it was you who helped us to get away from here and all would be lost if you should show yourself.We are guarded day and night. I do not care you are not here. Do not be troubled on my account. Nothing will happen to me. The national assemble will show leniency. Farewell the most loved of men. Be quiet if you can take care of yourself.For myself I cannot write any more, but nothing in the world could stop me to adore you up to the death.
附上两篇测试文章
最后代码如下:
import operator
import wordninja#英语里的结巴库
import re#正则表达式
import matplotlib.pyplot as plt#画图
from prettytable import PrettyTable#画表
import sympy
def alpha(cipher): # 预处理,去掉空格以及回车
c = ''
for i in range(len(cipher)):
if (cipher[i].isalpha()):
c += cipher[i]
print("密文的长度为%d"%len(c))
return c
def figure(cipher):
zfc=list(cipher)
d={"A":0,"B":0,"C":0,"D":0,"E":0,"F":0,"G":0,"H":0,"I":0,"J":0,"K":0,"L":0,"M":0,"N":0,"O":0,"P":0,"Q":0,"R":0,"S":0,"T":0,"U":0,"V":0,"W":0,"X":0,"Y":0,"Z":0}
for i in zfc:
if i in d:
d[i] += 1
else:
d[i] = 1
d = sorted(d.items(), reverse=False)
zm = [i[0] for i in d]
sz = [round(i[1] / len(cipher), 4) for i in d]
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.title('密文频率统计分析图')
plt.xlabel('字母')
plt.ylabel('频率')
plt.plot(zm, sz, 'or-')
plt.show()
def miyao_length(cipher):
jh=[]
d={}
b=[]
wz=0
for i in range(len(cipher)-2):
str = cipher[i:i+3]
jh.append(str)#把整个密文按照三分字符进行统计
for j in jh:
if j in d:
d[j]+=1
else:
d[j]=1
max_list=[]
max_value =max(d.values())#求出最多的三个字符的个数(有多个这样的字符)
for m, n in d.items():
if n == max_value:
max_list.append(m)#把最多的字符找到
for i in max_list:
a = [m.start() for m in re.finditer(i,cipher)]#查找这些字符的位置
s = []
if a[0]==0:
del a[0]
for j in range(2, a[0] + 1):
if a[0] % j == 0:
s.append(j)
for n in s:
num = 0
for k in a[1:len(a)]:
if k % n == 0:
num += 1
if num == len(a)-1:
num = n
b.append((i,num))
if len(b)!=0:
print("根据密文中子串%s的位置推断密钥长度最有可能为:%d" % (max(b)))
else:
print("在三个字符的字符串中未有满足条件的字符串!")
def figure_MIC(t):
x=[i+1 for i in range(len(t))]
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.title('各密钥长度的平均交互重合指数')
plt.xlabel('密钥长度')
plt.ylabel('平均交互重合指数')
plt.plot(x,t, 'or-')
plt.show()
def countMIC(t):
d = {}
for i in t:
if i in d:
d[i] += 1
else:
d[i] = 1
dict = sorted(d.items(), key=operator.itemgetter(1), reverse=True)
sum = 0
result=[]
for i in range(len(dict)):
sum += dict[i][1] * (dict[i][1] - 1)
return sum/(len(t)*(len(t)-1))
def miyao_len(cipher):
fg = list(cipher)
miyaosum=[]
for i in range(1, 10):
num=0
for j in range(i):
t = fg[j::i]
MIC=countMIC(t)
num+=MIC
if j+1==i:
print("密钥长度为{}时,平均重合指数为{:.5f}".format(i,num/i))
miyaosum.append(round((num/i),6))
figure_MIC(miyaosum)
min=1
for i in range(len(miyaosum)):
if abs(miyaosum[i]-0.065)<min:
min=abs(miyaosum[i]-0.065)
n=i+1
print("根据重合指数进一步证实密钥长度为:{}".format(n))
return n
def figure_len(t,cipher):
zf=list(cipher)
sum=[]
for i in range(t):
fg=zf[i::t]
d = {"A": 0, "B": 0, "C": 0, "D": 0, "E": 0, "F": 0, "G": 0, "H": 0, "I": 0, "J": 0, "K": 0, "L": 0, "M": 0,
"N": 0,"O": 0, "P": 0, "Q": 0, "R": 0, "S": 0, "T": 0, "U": 0, "V": 0, "W": 0, "X": 0, "Y": 0, "Z": 0}
for i in fg:
if i in d:
d[i] += 1
else:
d[i] = 1
d = sorted(d.items(), reverse=False)
sz = [round(i[1] / len(cipher), 4) for i in d]
sum.append(sz)
x = [chr(i + 65) for i in range(26)]
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.title('分组后的频率统计分析图')
plt.xlabel('字母')
plt.ylabel('频率')
ys=['ob-','vg--','sr-.','^c:','+m-','ok--','+r:','^k-.','sc*']
tl=["第一组", "第二组", "第三组", "第四组", "第五组", "第六组", "第七组",'第八组','第九组']
for j in range(len(sum)):
plt.plot(x, sum[j],ys[j])
plt.legend(tl[0:len(sum)],loc="upper right")
plt.show()
def count_group_MIC(group1,group2,n):
MIC=0
count_1 = [0 for i in range(26)] # [26个0]
count_2 = [0 for i in range(26)]
for j in range(len(group1)):
count_1[ord(group1[j]) - ord('A')] += 1
for i in range(len(group2)):
count_2[(ord(group2[i]) - ord('A')+n)%26] += 1
for i in range(26):
MIC += count_1[i] * count_2[i] / (len(group1) *len(group2))
return int(MIC*pow(10,3))/1000
def figure_k(pyl_k,lenmiyao):
x = PrettyTable(["i", "j", "密文子串的交互重合指数MIC"])
t=int(len(pyl_k)/26)
for n in range(2,t):
if n*(n-1)==2*t:
num=n
break
n=1
m = 1
for i in range(int(len(pyl_k)/26)):
if n<num:
n += 1
else:
m=m+1
n=m+1
siwei = []
for k in pyl_k[i*26:(i+1)*26]:
k=f"{k:.3f}"
siwei.append(k)
t=''.join(str(x) for x in siwei)
pattern = re.compile('.{5}')
result=' '.join(pattern.findall(t))
x.add_row([m,n, result])
print(x)
def group_k(cipher,miyao):
fg = list(cipher)
MIC_k=[]
MIC_I=[]
MIC_J=[]
pyl_k = []
for i in range(miyao-1):
group1 = fg[i::miyao]
for n in range(i+1,miyao):
group2 = fg[n::miyao]
k = [0 for i in range(26)]
for j in range(26):
k[j] = count_group_MIC(group1, group2, j)
pyl_k.append(k[j])
if k[j]>0.060:
m = j
MIC_k.append(m)
print("第%d组和第%d组之间偏移为%d时,交互重合指数为%.3f" % (i+1,n+1, m, k[m]))
MIC_I.append(i+1)
MIC_J.append(n+1)
figure_k(pyl_k,miyao)
return MIC_k,MIC_I,MIC_J
def contact(k,i,j,miyao):
k1, k2, k3, k4, k5, k6, k7, k8, k9 = sympy.symbols("k1 k2 k3 k4 k5 k6 k7 k8 k9")
daishu=[k1,k2,k3,k4,k5,k6,k7,k8,k9]
ds=daishu[0:miyao]
MIC_ij = []
sz=[]
print("根据偏移量可得各个密钥之间的关系:")
for x,y,z in zip(i,j,k):
t = 0
print("%s-%s=%d"%(ds[x-1],ds[y-1],z))
if ds[x-1]==k1:
MIC_ij.append(ds[x - 1])
if ds[x-1] not in MIC_ij and ds[y-1] in MIC_ij:
t=1
MIC_ij.append(ds[x-1])
if ds[x-1] in MIC_ij and ds[y-1] not in MIC_ij:
t = 1
MIC_ij.append(ds[y- 1])
if t==1:
if ds[x-1]==k1:
ds[x - 1]=0
sz.append(ds[x - 1]-ds[y-1]-z)
del ds[0]
a = sympy.solve(sz,ds)
gx=[]
for m,n in a.items():
gx.append(n)
gx.insert(0,0)
gx=[(26+i)%26 for i in gx]
gxs=[i+k1 for i in gx]
print("根据上式可得各个密钥间的关系为",gxs)
return gx
def certain_miyao(k,cipher,miyaolen):
for i in range(26):
t=[(i+j)%26 for j in k]
k1 = t * (len(cipher)// miyaolen + 1)
k1=[chr((ord(i)-65-j)%26+65) for i,j in zip(cipher,k1)]
d = {}
for j in k1:
if j in d:
d[j] += 1
else:
d[j] = 1
dict = sorted(d.items(), key=operator.itemgetter(1), reverse=True)
k1="".join(k1)
if dict[0][0]=='E':
print("由单表古典密码统计分析出现的字母频率判断可知:")
t=[chr(i+65) for i in t]
word = wordninja.split(k1)
plain = ''
for i in range(len(word)):
plain += word[i]
plain += ' '
print("当密钥为%s时,明文为%s"%(t,plain))
if __name__ == "__main__":
fp = open("密文3.txt", "r")
cipher = ''
for i in fp.readlines():
cipher = cipher + i
fp.close()
cipher = alpha(cipher)
figure(cipher)
miyaolength=miyao_length(cipher)
miyaolen=miyao_len(cipher)
figure_len(miyaolen,cipher)
k,i,j=group_k(cipher,miyaolen)
contact_k=contact(k,i,j,miyaolen)
certain_miyao(contact_k,cipher,miyaolen