维吉尼亚密码——笔记1
程序员文章站
2022-03-05 12:42:17
...
维吉尼亚密码——笔记1
目录
原理
- 是一种多表代换密码
- 若**,即**长度为m;明文为,对应密文为
- 则对于明文中的每一个字符,有
无**破译分析
- 首先需要找到**的长度m
- Kasiski测试法
- 由Friedrich Kasiski在1863年给出描述,由Charles Babbage于1854年首先发现
基于这样一个事实:两个相同的明文段将加密成相同的密文段,它们的位置间距假设为,则。反过来,如果在密文中观察到两个相同的长度至少为3的密文段,那么将给破译者带来很大方便,因为它们实际上对应了相同的明文串
- 工作流程:
- 搜索长度至少为3的相同的密文段,记下其离起始点的那个密文段的距离
- 假设得到距离为:
- 那么可以猜测为这些的最大公因子的因子
- 重合指数法
- 由William Friedman提出
设是一条n个字母的串,的重合指数记为,定义为中两个随机元素相同的概率
- 若分别表示A,B,…,Z在x中出现的频数
- 则
- 如果x是英文文本串,有
- 如果x是一个完全的随机串,有
- 也就是说对于一个英文文本串,或是其经过移位密码加密后的密文,其重合指数应该接近0.065
- 但Vigenere加密中对每一明文字符移位距离不一定相同
- 对这种问题,我们将用**字中相同字符加密后的密文提取出
- 如这样提取出的字符串的重合指数应该接近0.065
- 由上一步分析结果,可对m进行测试,对于每一个测试的m,可将密文分组
- 求出这些字串的重合指数平均数,按平均数与0.065的接近程度排序,得到一系列m的可能值
- 可依次对每个m的可能值进行下步内容(确定**内容+译码)
- Kasiski测试法
- 得到**长度后,确定**内容
- 由前步分析知,对于分割的每一个子串,子串中每个密文字符都是用同一**字符加密的,相当于一个移位加密,其中子串对应移位为
- 用表示字串长度,表示此子串中字母A,B,…,Z出现的频数,则有26个字母在子串中的概率分布估计为:
- 又因为移位为,所以明文中频率等于密文中频率
- 于是有数值
- 到现在我们已经有了如下问题转换
- 如上步所述,对测试,记测试值为,有数值
- 取使得最接近0.065的g作为
- 这样的取法可能导致最终**并不一定是正确的,但大部分正确的可能性比较大,最后解密结果可能与真实结果有偏差,但偏差一般不会很大,一般可以人为看出来,如原文为Helloworld,可能错破译为Hjllowhrld
- 集合所得到的所有,合成最终**字
- 解密
代码
class VigenereCipher:
def __init__(self,s):
self.msg=s.lower()
# 更改字串
def update(self,s):
self.msg=s.lower()
# 重合指数计算
def Ic(self,x):
assert type(x)==str
x=x.lower()
n=len(x)
ans=0
for i in range(26):
f=x.count(chr(i+ord('a')))
ans+=f*(f-1)
ans=float(ans)/(n*(n-1))
return ans
# 得到**字长度的可能值(依各子串重合指数平均数与0.065的接近排序,接近的在前)
def findKeyLen(self,maxkeylen=30):
assert type(self.msg)==str
mp={}
for m in range(1,maxkeylen+1):
ans=sum([self.Ic(self.msg[i::m]) for i in range(m)])/float(m)
mp[m]=ans
mp=dict(sorted(mp.items(),key=lambda item:abs(item[1]-0.065)))
return list(mp.keys())
# 确定**内容
# 并不一定是真正的**内容,只是可能性大,即便**长度判断是正确的
def findKeyContent(self,keylen):
key=""
p=[float(i)/1000 for i in [82,15,28,43,127,22,20,61,70,2,8,40,24,67,75,19,1,60,63,91,28,10,23,1,20,1]]
for i in range(keylen):
yi=self.msg[i::keylen]
n=len(yi)
#频率f/n
fn=[float(fi)/n for fi in [yi.count(chr(i+ord('a'))) for i in range(26)]]
M=lambda g: sum(p[i]*fn[(i+g)%26] for i in range(26))
# 直接令每一块使得Mg最接近0.065的为ki
ki=min(range(26),key=lambda g:abs(M(g)-0.065))
key+=chr(ord('a')+ki)
return key
# 根据**对msg解密
def decode(self,key):
assert type(self.msg)==str
key=key.lower()
m=len(key)
digit=lambda a: ord(a)-ord('a')
alpha=lambda a: chr(a+ord('a'))
sub=lambda a,b: alpha((digit(a)-digit(b))%26)
ans=""
for i in range(len(self.msg)):
ans+=sub(self.msg[i],key[i%m])
return ans
# 根据**对msg加密
def encode(self,key):
assert type(self.msg)==str
key=key.lower()
m=len(key)
digit=lambda a: ord(a)-ord('a')
alpha=lambda a: chr(a+ord('a'))
add=lambda a,b: alpha((digit(a)+digit(b))%26)
ans=""
for i in range(len(self.msg)):
ans+=add(self.msg[i],key[i%m])
return ans
# **
def violentCrack(self,maxkeylen=30):
cnt=0
for m in self.findKeyLen(maxkeylen):
key=self.findKeyContent(m)
print("key="+key+":\n"+self.decode(key),end='\n\n')
cnt+=1
if cnt%10 == 0:
q=input('输入q退出。。。')
try:
if ord(q[0]) in [ord('q'),ord('Q')]:
return
except:
pass
if __name__=="__main__":
s="CHREEVOAHMAERATBIAXXWTNXBEEOPHBSBQMQEQERBWRVXUOAKXAOSXXWEAHBWGJMMQMNKGRFVGXWTRZXWIAKLXFPSKAUTEMNDCMGTSXMXBTUIADNGMGPSRELXNJELXVRVPRTULHDNQWTWDTYGBPHXTFALJHASVBFXNGLLCHRZBWELEKMSJIKNBHWRJGNMGJSGLXFEYPHAGNRBIEQJTAMRVLCRREMNDGLXRRIMGNSNRWCHRQHAEYEVTAQEBBIPEEWEVKAKOEWADREMXMTBHHCHRTKDNVRZCHRCLQOHPWQAIIWXNRMGWOIIFKEE"
ss="KCCPKBGUFDPHQTYAVINRRTMVGRKDNBVFDETDGILTXRGUDDKOTFMBPVGEGLTGCKQRACQCWDNAWCRXIZAKFTLEWRPTYCQKYVXCHKFTPONCQQRHJVAJUWETMCMSPKQDYHJVDAHCTRLSVSKCGCZQQDZXGSFRLSWCWSJTBHAFSIASPRJAHKJRJUMVGKMITZHFPDISPZLVLGWTFPLKKEBDPGCEBSHCTJRWXBAFSPEZQNRWXCVYCGAONWDDKACKAWBBIKFTIOVKCGGHJVLNHIFFSQESVYCLACNVRWBBIREPBBVFEXOSCDYGZWPFDTKFQIYCWHJVLNHIQIBTKHJVNPIST"
tmp=VigenereCipher(ss)
tmp.violentCrack()
上一篇: vivo全新子品牌iQOO宣布:不请代言人 专攻互联网用户
下一篇: Lucene快速入门(二)