题目:
请解密以下代换密码的密文:
JGRMQOYGHMVBJWRWQFPWHGFFDQGFPFZRKBEEBJIZQQOCIBZKLFAFGQVFZFWWEOGWOPFGFHWOLPHLRLOLFDMFGQWBLWBWQOLKFWBYLBLYLFSFLJGRMQBOLWJVFPFWQVHQWFFPQOQVFPQOCFPOGFWFJIGFQVHLHLROQVFGWJVFPFOLFHGQVQVFILEOGQILHQFQGIQVVOSFAFGBWQVHQWIJVWJVFPFWHGFIWIHZZRQGBABHZQOCGFHX
解析:
代换密码(Simple Substitution Cipher),顾名思义,就是一种使用substitution来进行加密的算法。代换密码采用一一映射的方式,将明文每个字符逐一唯一地代换成另外的一个字符。对于代换密码的详细说明,可点击这里。在凯撒密码中,每个字母都按照其在字母表中的顺序向后(或向前)移动固定数目(循环移动)作为密文。而代换密码则不是简单的按顺序移位,而是完全混乱的毫无规律地进行映射。在这两种加密方法中,密钥的长度都是26个字符,如密钥“DGLHEIJPQFRMSTNUVWXKOYZBAC”,则在加密的时候,A->D,B->G,以此类推。下面举个例子:
(1)凯撒密码:
明文:ABCDEFGHIJKLMNOPQRSTUVWXYZ
密文:DEFGHIJKLMNOPQRSTUVWXYZABC
(2)代换密码
明文:ABCDEFGHIJKLMNOPQRSTUVWXYZ
密文:DGLHEIJPQFRMSTNUVWXKOYZBAC
攻击:
观察前面的两种加密方式,可以发现,凯撒就是简单的字母的移位,例子中的偏移量是3,仅仅有25种可能的密钥,暴力穷举法可以瞬间攻破它。而在代换密码中,则没法简单地看出字符的对应关系了,其可能的密钥可达26!个,因此要攻击代换密码的话,就没法采用简单的暴力破解方法了。
对于代换密码,有多种的常用的攻击方法,其中常见两种为“词频分析法”和“爬山法”。所谓的“词频攻击法”,即把密文中字母使用的相对频率统计出来,与日常英语中英文字母使用的相对频率进行比较,利用频率进行匹配,即密文中频率较高的字母对应于日常英语中使用频率较高的英文字母,反之亦然,从而推导出密钥。由于要使用到数学统计的方法,此攻击方法需要用到较长的密文。这里介绍一个网站,词频攻击法可点击这里进行线上解密。
下面着重介绍“爬山法”。
“爬山法”
在这个算法中,我们需要选择性地尝试不同解密密钥,然后给每一个解密出来的明文标记上一个适应度。若解密出来的明文越接近我们的日常用的英语,它的适应度就越高;若解密出来的明文越难读懂或者越不像我们日常用的英语,则其适应度越低。我们使用一个基于quadgram statistics的适应度计算方法。关于quadgram statistics的详细介绍可点击这里。
下面详细描述算法步骤:
(1)随机生成一个key,称为parentkey,用它解密得对应的明文m1,对明文计算适应度d1。
(2)随机交换parentkey中的两个字符得到子密钥child,解密出对应的明文m2并计算适应度d2。
(3)若d1 (4)不断循环进行(2)(3)直到最后的1000次循环中不再有更高的适应度生成 (5)回到(1)重新生成parentkey继续迭代寻找,或者由操作者终止程序 重新执行(1),是为了防止(2)(3)的操作使结果陷入局部最优的困境。对于生成的明文的适应度的比较,其实可以看作是对不同解密密钥的比较,解密出来的明文的适应度越高,对应的密钥就更好。 下面是我参照官方源码写的代码: # -*- coding: UTF-8 -*- """算法思路:因为整个密文都只是大写英文字母,所以并不需要进行字符转换操作。解密的时候,在生成了key之后,将key放入一个字典变量中,字典中每个变量都是“密文字符:明文字符”的映射对,从而对密文的字符进行一个接着一个地转换。循环嵌套有两层,外层是每次随机生成一个起始的parentkey,里层进行爬山法,每次随机交换parentkey里的两个元素以解密,最后每次外层循环都判断一次是否找到更优的密钥""" import random from ngram_score import ngram_score #[!]参数初始化 ciphertext='JGRMQOYGHMVBJWRWQFPWHGFFDQGFPFZRKBEEBJIZQQOCIBZKLFAFGQVFZFWWEOGWOPFGFHWOLPHLRLOLFDMFGQWBLWBWQOLKFWBYLBLYLFSFLJGRMQBOLWJVFPFWQVHQWFFPQOQVFPQOCFPOGFWFJIGFQVHLHLROQVFGWJVFPFOLFHGQVQVFIEOGQILHQFQGIQVVOSFAFGBWQVHQWIJVWJVFPFWHGFIWIHZZRQGBABHZQOCGFHX' parentkey = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ') key = {'A':'A'}#只是用来声明key是个字典 fitness = ngram_score('quadgrams.txt') #读取quadgram statistics parentscore = -99e9 maxscore = -99e9 #[!] j = 0 print '***********************************start*******************************************' while 1: j = j+1 random.shuffle(parentkey)#随机打乱key中的元素,以达到生成新的起始密钥来进行尝试的目的 #将密钥做成字典,以日后进行一一映射来解密 for i in range(len(parentkey)): key[parentkey[i]] = chr(ord('A')+i) #用字典一个一个字符地进行一一映射地代换回去以解密 decipher = ciphertext for i in range(len(decipher)): decipher = decipher[:i] + key[decipher[i]] + decipher[i+1:] parentscore = fitness.score(decipher)#计算适应度 #[!]在当前密钥下随机交换两个密钥的元素从而寻找是否有更优的解 count = 0 while count < 1000: a = random.randint(0,25) b = random.randint(0,25) #随机交换父密钥中的两个元素生成子密钥,并用其进行解密 child = parentkey[:] child[a],child[b] = child[b],child[a] childkey = {'A':'A'} for i in range(len(child)): childkey[child[i]] = chr(ord('A')+i) decipher = ciphertext for i in range(len(decipher)): decipher = decipher[:i] + childkey[decipher[i]] + decipher[i+1:] score = fitness.score(decipher) #若用子密钥所生成的明文更加贴近英文,也就是此明文的适应度更高,则用此子密钥代替其对应的父密钥 if score > parentscore: parentscore = score parentkey = child[:] count = 0 count = count+1 #[!] #我认为已经找到了最优的key,然后输出该key和明文 if parentscore > maxscore: maxscore = parentscore maxkey = parentkey[:] for i in range(len(maxkey)): key[maxkey[i]] = chr(ord('A')+i) decipher = ciphertext for i in range(len(decipher)): decipher = decipher[:i] + key[decipher[i]] + decipher[i+1:] print 'Currrent key: '+''.join(maxkey) print 'Iteration total:', j print 'Plaintext: ', decipher 备注:quadgrams.txt和ngram_score.py可点击这里在文章最下方获取。 算法结果: 可以看出,此算法效率还是比较高的,平均十次迭代以内可以得出比较令人满意的明文。 不过此算法还是有点可改进的地方,就是最后还得手动在单词之间和密文换行的时候添加空格,才可得出结果:CRYPTOGRAPHIC SYSTEMS ARE EXTREMELY DIFFICULT TO BUILD NEVERTHELESS FOR SOME REASON MANY NON EXPERTS INSIST ON DESIGNING NEW ENCRYPTION SCHEMES THAT SEEM TO THEM TO BE MORE SECURE THAN ANY OTHER SCHEME ON EARTH THE UNFORTUNATE TRUTH HOWEVER IS THAT SUCH SCHEMES ARE USUALLY TRIVIAL TO BREAK 另外还有其他可以改进的地方,对于大写字符以外的字符的处理不可简单地使用上面的代码,但可以使用库pycipher,将官方示例代码的稍微改了一下可得以下代码: from pycipher import SimpleSubstitution as SimpleSub import random import re from ngram_score import ngram_score fitness = ngram_score('quadgrams.txt') # load our quadgram statistics ctext='JGRMQOYGHMVBJWRWQFPWHGFFDQGFPFZRKBEEBJIZQQOCIBZKLFAFGQVFZFWWEOGWOPFGFHWOLPHLRLOLFDMFGQWBLWBWQOLKFWBYLBLYLFSFLJGRMQBOLWJVFPFWQVHQWFFPQOQVFPQOCFPOGFWFJIGFQVHLHLROQVFGWJVFPFOLFHGQVQVFIEOGQILHQFQGIQVVOSFAFGBWQVHQWIJVWJVFPFWHGFIWIHZZRQGBABHZQOCGFHX' ctext = re.sub('[^A-Z]','',ctext.upper()) maxkey = list('ABCDEFGHIJKLMNOPQRSTUVWXYZ') maxscore = -99e9 parentscore,parentkey = maxscore,maxkey[:] print "Substitution Cipher solver, you may have to wait several iterations" print "for the correct result. Press ctrl+c to exit program." # keep going until we are killed by the user i = 0 while 1: i = i+1 random.shuffle(parentkey) deciphered = SimpleSub(parentkey).decipher(ctext) parentscore = fitness.score(deciphered) count = 0 while count < 1000: a = random.randint(0,25) b = random.randint(0,25) child = parentkey[:] # swap two characters in the child child[a],child[b] = child[b],child[a] deciphered = SimpleSub(child).decipher(ctext) score = fitness.score(deciphered) # if the child was better, replace the parent with it if score > parentscore: parentscore = score parentkey = child[:] count = 0 count = count+1 # keep track of best score seen so far if parentscore>maxscore: maxscore,maxkey = parentscore,parentkey[:] print '\nbest score so far:',maxscore,'on iteration',i ss = SimpleSub(maxkey) print ' best key: '+''.join(maxkey) print ' plaintext: '+ss.decipher(ctext) 以上算法同样算法效率很高。 “爬山法”的具体描述可点击这里。 有什么错误或者不足还请大家多多指教~