换位加密和仿射加密方法扛不住暴力破译的原因很清楚:
密钥空间太短,计算机消耗一定的时间就可以完成破译工作。
我们想办法用简单的逻辑把密钥空间增大,一种方法就是简单的替换
对于:
他的所有置换有 |S| = 26 * 25 * ···*2*1 个。
>>> a 1 >>> for i in range(1, 27): ... a *= i ... print(a) ... 1 2 6 24 120 720 5040 40320 362880 3628800 39916800 479001600 6227020800 87178291200 1307674368000 20922789888000 355687428096000 6402373705728000 121645100408832000 2432902008176640000 51090942171709440000 1124000727777607680000 25852016738884976640000 620448401733239439360000 15511210043330985984000000 403291461126605635584000000 >>>
即所有可能的密钥有26! = 403291461126605635584000000个,计算机要全部破译他们也挺费劲的。
简单替换加密/解密算法:
# Simple Substitution Cipher # http://inventwithpython.com/hacking (BSD Licensed) import pyperclip, sys, random # LETTERS 和 myKey 两个集合一一映射 LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' def main(): # myMessage = 'If a man is offered a fact which goes against his instincts, he will scrutinize it closely, and unless the evidence is overwhelming, he will refuse to believe it. If, on the other hand, he is offered something which affords a reason for acting in accordance to his instincts, he will accept it even on the slightest evidence. The origin of myths is explained in this way. -Bertrand Russell' myMessage = 'Sy l nlx sr pyyacao l ylwj eiswi upar lulsxrj isr sxrjsxwjr, ia esmm rwctjsxsza sj wmpramh, lxo txmarr jia aqsoaxwa sr pqaceiamnsxu, ia esmm caytra jp famsaqa sj. Sy, px jia pjiac ilxo, ia sr pyyacao rpnajisxu eiswi lyypcor l calrpx ypc lwjsxu sx lwwpcolxwa jp isr sxrjsxwjr, ia esmm lwwabj sj aqax px jia rmsuijarj aqsoaxwa. Jia pcsusx py nhjir sr agbmlsxao sx jisr elh. -Facjclxo Ctrramm' myKey = 'LFWOAYUISVKMNXPBDCRJTQEGHZ' myMode = 'decrypt' # set to 'encrypt' or 'decrypt' checkValidKey(myKey) if myMode == 'encrypt': translated = encryptMessage(myKey, myMessage) elif myMode == 'decrypt': translated = decryptMessage(myKey, myMessage) print('Using key %s' % (myKey)) print('The %sed message is:' % (myMode)) print(translated) pyperclip.copy(translated) print() print('This message has been copied to the clipboard.') # 判断key的合法性, 如果不是一一映射 程序将退出 # string.sort() 方法在这里是将一个字符串转换成项为单个字符的列表 # sort()方法是就地修改!!! def checkValidKey(key): keyList = list(key) lettersList = list(LETTERS) keyList.sort() lettersList.sort() if keyList != lettersList: sys.exit('There is an error in the key or symbol set.') # 把函数封装起来会增强代码可读性和逻辑,以及健壮性, # 阅读的人可以方便地调用它而不必考虑太多细节 推荐 def encryptMessage(key, message): return translateMessage(key, message, 'encrypt') def decryptMessage(key, message): return translateMessage(key, message, 'decrypt') def translateMessage(key, message, mode): translated = '' charsA = LETTERS charsB = key if mode == 'decrypt': # 简单替换的加密和解密在逻辑上的相似和对应,使得加密解密的映射相反即可 # 加密保持现状,解密把映射取反,即交换charsA, charsB # For decrypting, we can use the same code as encrypting. We # just need to swap where the key and LETTERS strings are used. charsA, charsB = charsB, charsA # loop through each symbol in the message # 明文消息中大写,对于密文还是大写,小写对应还是小写 # 符号则不转换(加密)直接赋值 for symbol in message: if symbol.upper() in charsA: # encrypt/decrypt the symbol symIndex = charsA.find(symbol.upper()) if symbol.isupper(): translated += charsB[symIndex].upper() else: translated += charsB[symIndex].lower() else: # symbol is not in LETTERS, just add it translated += symbol return translated # 得到一个随机的一一映射(密钥),实际应用当然要把这个密钥保存下来 def getRandomKey(): key = list(LETTERS) random.shuffle(key) return ''.join(key) if __name__ == '__main__': main()
输出:
Using key LFWOAYUISVKMNXPBDCRJTQEGHZ The decrypted message is: If a man is offered a fact which goes against his instincts, he will scrutinize it closely, and unless the evidence is overwhelming, he will refuse to believe it. If, on the other hand, he is offered something which affords a reason for acting in accordance to his instincts, he will accept it even on the slightest evidence. The origin of myths is explained in this way. -Bertrand Russell This message has been copied to the clipboard.
isupper()和islower()字符串方法:
'a string'.isupper()返回True
1.这个字符串没有任何小写字母
'a string'.islower()返回True
1.这个字符串没有任何大写字母
>>> string1 = 'aaaaaa' >>> string2 = 'aAaaaa' >>> string3 = 'BBBBBB' >>> string4 = 'BbBBBB' >>> string1.isupper() False >>> string2.isupper() False >>> string5 = 'asdasdJJJJJJ' >>> string5.isupper() False >>> string3.isupper() True >>> string4.isupper() False >>> string1.islower() True >>> string2.islower() False >>> string3.islower() False >>> string4.islower() False >>> string5.islower() False >>>
lower()和upper()方法对像'?'不起作用 >>> '?'.lower() '?' >>> 'A'.lower() 'a' >>> 'a'.lower() 'a' >>> ' '.lower() ' ' >>> '5'.lower() '5' >>>
从上面的算法我们知道,简单替换没有对符号进行加密,只是加密了大写字母和小写字母,
那么要如何加密空格和标点符号呢?
简单,加上想要的符号,做一个新的密文符号集,
当然,对应的密钥key要和他有相同的大小,且为一一映射!
这样使得密文看起来更像是随机的乱码!
一种单词模式破译思想(密码分析)
我们将破译"如此长的"密钥化简化简,使得加下来的问题更具有密码学意义----密码分析
密码分析是分析密文的某种特征
如果我们希望破译简单替换,就要用更加智能的方式。
考虑一个使用简单替换后的密文单词吧:
HGHHU
我们可以说:不管明文是什么,
- 他有5个字母长度
- 他的第1、3、4个字母是一样的
- 他由3个不同的字母组成
英语里面有什么单词匹配这种模式?
'Puppy'
'Mommy'
'Lilly'
暂且把这种特别的单词归一化,指定一种单词模式,用数字表达:
'puppy' --- '0.1.0.0.2'
'cat' --- '1.2.3'
'classification' --- '0.1.2.3.3.4.5.4.0.2.6.4.7.8'
明文单词和他的密文总是有着相同的单词模式,不管是用哪个简单代替密钥。
我们可以通过这种模式,来构造一个字典,借用计算机帮助我们找出密钥是什么
要猜测HGHHU解密成什么,我们可以查阅一个包含各种单词的字典文件,找出满足0.1.0.0.2单词模式的所有单词
且把这些单词叫做密词的候选单词
密文单词 | HGHHU |
单词模式 | 0.1.0.0.2 |
候选单词 | puppy |
mommy | |
bobby | |
lulls | |
nanny | |
lilly |
我们观察密字的字母,即H、G、U,然后猜测他们可能是对应什么字母:
密字 | H | G | U |
潜在的解密字母 | p | u | y |
m | o | y | |
b | o | y | |
l | u | s | |
n | a | y | |
... | ... | ... |
用这张表格我们创建一个一对多的密字映射
H --->[P, M, B, L, N, ...]
G --->[U, O, A, ...]
U --->[Y, S, ...]
当密字对应的潜在字母都只有一个时,那么就知道这个密字应该解密成什么字母了,
所以,我们希望找出字典文件里面的每个单词的模式--->把他们保存到另外一个文件--->
把每种模式对应的单词分类保存成新的字典--->尝试使用用密词对应的单词模式下的所有对应单词解密
单词模式破译思想程序
# Makes the wordPatterns.py File # http://inventwithpython.com/hacking (BSD Licensed) # Creates wordPatterns.py based on the words in our dictionary # text file, dictionary.txt. (Download this file from # http://invpy.com/dictionary.txt) import pprint # 用这个函数来计算单词模式 单词模式我们保存为一个字符串 # 传入一个单词,生产其对应的模式字符串 # 如传入 'insert', 返回'0.1.2.3.4.5' def getWordPattern(word): # Returns a string of the pattern form of the given word. # e.g. '0.1.2.3.4.1.2.3.5.6' for 'DUSTBUSTER' word = word.upper() nextNum = 0 letterNums = {} wordPattern = [] for letter in word: if letter not in letterNums: letterNums[letter] = str(nextNum) nextNum += 1 wordPattern.append(letterNums[letter]) return '.'.join(wordPattern) # 考虑这里为什么要用letterNums字典,因为要给每个word里面的密字匹配一个模式数字,从0开始 # 如果有一个神奇的单词有包含了26个字母,那这个数字最大值应该为25 def main(): # allPatterns这个字典是我们要保存到wordPatterns.py文件里的东西, # 他的键是单词模式的字符串,像'0.1.2.3.4.5' 键的值是匹配那个模式的英文单词的字符串列表 allPatterns = {} # 把所有的单词读到wordList做成一个单词列表 fo = open('dictionary.txt') wordList = fo.read().split(' ') fo.close() # 接下来希望把列表里的每个单词,确定其模式以及生成对应的键-值对 一个键(单词模式)可能有很多单词 for word in wordList: # Get the pattern for each string in wordList. pattern = getWordPattern(word) # 一个键必须有一个值,否则就会出错,所以要先判断这个键存在与否,不存在的话就要创建一个列表,然后放在对应的键里面 if pattern not in allPatterns: # 可以以这样的方式在字典里新增(创建)一个键-值对 allPatterns[pattern] = [word] else: # 在已经存在的键里面,他的值是一个list,往list里面append一个新的项 allPatterns[pattern].append(word) # This is code that writes code. The wordPatterns.py file contains # one very, very large assignment statement. # 以'w'打开,是判断是否存在一个wordPatterns.py,如果存在,先删除,再创建,为我们的操作让路 fo = open('wordPatterns.py', 'w') # 打开后多次写入是可以的 fo.write('allPatterns = ') # 把生成的单词模式字典allPatterns写入到wordPatterns.py文件里,我们希望它看起来很优雅 # 使用pprint模块美化输出 fo.write(pprint.pformat(allPatterns)) fo.close() # 如果这个程序是自己运行而不是其他程序导入,就会运行main函数 if __name__ == '__main__': main()
破译简单替代加密法
有了上面的基础,我们就可以把破译做出来了
- 找出密文里的每个密词的单词模式
- 找出每个密词可以解密成哪些英文单词
- 使用密词的候选单词列表{模式}[键]的.值 为每一个密词创建一个密字映射(密字映射是一个字典值)H --->[P, M, B, L, N, ...]
- 计算所有密字映射的交集,得到一个交集密字映射
- 从交集里面移除已经破译的字母(那些交集里面只有一个元素(字母))
不要惧怕一个很大包含很多单词的密文,因为做计算的是计算机而不是你手算呀!
密文里的密词越多,我们用来计算交集的密字映射就越多,使得每个字母的潜在解密字母就少,我们就越可能破译它!
-
找出每个密词可以解密成哪些英文单词
这是比较方便的,比如我们只找一个密词 'OLQIHXIRCKGNZ' 模式为 '0.1.2.3.4.5.3.6.7.8.9.10.11'的对应所有单词(候选者)
>>> import wordPatterns >>> candidates = wordPatterns.allPatterns['0.1.2.3.4.5.3.6.7.8.9.10.11'] >>> candidates ['UNCOMFORTABLE', 'UNCOMFORTABLY'] >>>
接下来
'OLQIHXIRCKGNZ'--->'UNCOMFORTABLE'(候选者一号)
且
'OLQIHXIRCKGNZ'--->'UNCOMFORTABLY'(候选者二号)
用这两个映射得到一个密字映射,尽管不会使所有的字母键的值都赋值,但是密字单词的候选者一多就有了哈哈哈哈!
{'A': [], 'B': [], 'C': [], 'D': [], 'E': [], 'F': [], 'G': [], 'H': [], 'I': [], 'J': [], 'K': [], 'L': [], 'M': [], 'N': [], 'O': [], 'P': [], 'Q': [], 'R': [], 'S': [], 'T': [], 'U': [], 'V': [], 'W': [], 'X': [], 'Y': [], 'Z': []}
把这个操作封装在函数里面方便调用:
def addLettersToMapping(letterMapping, cipherword, candidate): # The letterMapping parameter is a "cipherletter mapping" dictionary # value that the return value of this function starts as a copy of. # The cipherword parameter is a string value of the ciphertext word. # The candidate parameter is a possible English word that the # cipherword could decrypt to. # This function adds the letters of the candidate as potential # decryption letters for the cipherletters in the cipherletter # mapping. letterMapping = copy.deepcopy(letterMapping) for i in range(len(cipherword)): if candidate[i] not in letterMapping[cipherword[i]]: letterMapping[cipherword[i]].append(candidate[i]) return letterMapping
example:
>>> letterMapping1 = simpleSubHacker.addLettersToMapping(letterMapping1, 'OLQIHXIRCKGNZ', candidates[0]) >>> letterMapping1 {'A': [], 'B': [], 'C': ['T'], 'D': [], 'E': [], 'F': [], 'G': ['B'], 'H': ['M'], 'I': ['O'], 'J': [], 'K': ['A'], 'L': ['N'], 'M': [], 'N': ['L'], 'O': ['U'], 'P': [], 'Q': ['C'], 'R': ['R'], 'S': [], 'T': [], 'U': [], 'V': [], 'W': [], 'X': ['F'], 'Y': [], 'Z': ['E']} >>>
>>> letterMapping1 = simpleSubHacker.addLettersToMapping(letterMapping1, 'OLQIHXIRCKGNZ', candidates[1]) >>> letterMapping1 {'A': [], 'B': [], 'C': ['T'], 'D': [], 'E': [], 'F': [], 'G': ['B'], 'H': ['M'], 'I': ['O'], 'J': [], 'K': ['A'], 'L': ['N'], 'M': [], 'N': ['L'], 'O': ['U'], 'P': [], 'Q': ['C'], 'R': ['R'], 'S': [], 'T': [], 'U': [], 'V': [], 'W': [], 'X': ['F'], 'Y': [], 'Z': ['E', 'Y']} >>>
看,我们上面说的话验证了吧!
到现在,我们只是搞定了一个密词,密文里面可是有很多密词的!
我们另外选择一个密词'PLQRZKBZB',做同样的操作得到另外一个密字映射
>>> letterMapping2 = simpleSubHacker.getBlankCipherletterMapping() >>> wordPat = makeWordPatterns.getWordPattern('PLQRZKBZB') >>> candidates = wordPatterns.allPatterns[wordPat] >>> candidates ['CONVERSES', 'INCREASES', 'PORTENDED', 'UNIVERSES'] >>> for candidate in candidates: ... letterMapping2 = simpleSubHacker.addLettersToMapping(letterMapping2, 'PLQRZKBZB', candidate) ... >>> letterMapping2 {'A': [], 'B': ['S', 'D'], 'C': [], 'D': [], 'E': [], 'F': [], 'G': [], 'H': [], 'I': [], 'J': [], 'K': ['R', 'A', 'N'], 'L': ['O', 'N'], 'M': [], 'N': [], 'O': [], 'P': ['C', 'I', 'P', 'U'], 'Q': ['N', 'C', 'R', 'I'], 'R': ['V', 'R', 'T'], 'S': [], 'T': [], 'U': [], 'V': [], 'W': [], 'X': [], 'Y': [], 'Z': ['E']} >>>
这样就完成了两个单词的密字映射,是时候计算他们的交集来看看最后的结果了。
=================================================================================
我们想象,计算交集之后,会得到得到形如这样的字典:
{'A': ['E'], 'B': ['Y', 'P', 'B'], 'C': ['R'], 'D': [], 'E': ['W'], 'F': ['B', 'P'], 'G': ['B', 'Q', 'X', 'P', 'Y'], 'H': ['P', 'Y', 'K', 'X', 'B'], 'I': ['H'], 'J': ['T'], 'K': [], 'L': ['A'], 'M': ['L'], 'N': ['M'], 'O': ['D'], 'P': ['O'], 'Q': ['V'], 'R': ['S'], 'S': ['I'], 'T': ['U'], 'U': ['G'], 'V': [], 'W': ['C'], 'X': ['N'], 'Y': ['F'], 'Z': ['Z']}
- 为什么会有 'D': [] ,简单,这是因为不同的密字映射相交当然可能是空集,举个例子:
A猜C是男人,B猜C是女人,那C是男人还是女人就不好说了
A猜C是男人,B也猜C是男人,那就猜呗!
- 对于 'G': ['B', 'Q', 'X', 'P', 'Y'], 也不好说G对应的字母是啥,那就不知道 干脆用一个字符代替好了 '_'
所以这两种情况解密时干脆用一个字符代替好了 '_',反正我们可以解密其余大部分的字母!!!
============================================================================
两个神似的交集的计算,当然是封装在一个函数里来的快乐!
交集映射里的任何密字的潜在字母列表包含了这个密字同时在两个密字映射列表里的潜在解密字母。交集嘛~
考虑一些例外:
如果其中一个映射的列表是空的,那么其他映射里的所有潜在字母都会放进交集映射,空映射表示任何字母都能用
def intersectMappings(mapA, mapB): # To intersect two maps, create a blank map, and then add only the # potential decryption letters if they exist in BOTH maps. intersectedMapping = getBlankCipherletterMapping() for letter in LETTERS: # An empty list means "any letter is possible". In this case just # copy the other map entirely. if mapA[letter] == []: intersectedMapping[letter] = copy.deepcopy(mapB[letter]) elif mapB[letter] == []: intersectedMapping[letter] = copy.deepcopy(mapA[letter]) else: # If a letter in mapA[letter] exists in mapB[letter], add # that letter to intersectedMapping[letter]. for mappedLetter in mapA[letter]: if mappedLetter in mapB[letter]: intersectedMapping[letter].append(mappedLetter) return intersectedMapping
对上面得到的letterMapping1和letterMapping2进行操作得到intersectedMapping:
>>> intersectedMapping = simpleSubHacker.intersectMappings(letterMapping1, letterMapping2) >>> intersectedMapping {'A': [], 'B': ['S', 'D'], 'C': ['T'], 'D': [], 'E': [], 'F': [], 'G': ['B'], 'H': ['M'], 'I': ['O'], 'J': [], 'K': ['A'], 'L': ['N'], 'M': [], 'N': ['L'], 'O': ['U'], 'P': ['C', 'I', 'P', 'U'], 'Q': ['C'], 'R': ['R'], 'S': [], 'T': [], 'U': [], 'V': [], 'W': [], 'X': ['F'], 'Y': [], 'Z': ['E']} >>>
我们可以按照这个逻辑,对一个新的密字做上面的操作直到和已经得到的intersectedMapping做完交集得到一个更新过的intersectedMapping
但是还有一个细节上的问题,有时候我们发先
{}['K'] ---->['A']
{}['M'] ---->['A', 'D']
这意味着什么?我们已经确定了密字K有唯一的交集映射就是'A',那就是确定了'A'就是‘K’的替换
现在密字M也说我的交集映射列表里面有不止一个,其中就包括‘A’,K说:给爷爬!
所以我们有这一步: 从交集里面移除已经破译的字母(那些交集里面只有一个元素(字母))
def removeSolvedLettersFromMapping(letterMapping): # Cipher letters in the mapping that map to only one letter are # "solved" and can be removed from the other letters. # For example, if 'A' maps to potential letters ['M', 'N'], and 'B' # maps to ['N'], then we know that 'B' must map to 'N', so we can # remove 'N' from the list of what 'A' could map to. So 'A' then maps # to ['M']. Note that now that 'A' maps to only one letter, we can # remove 'M' from the list of letters for every other # letter. (This is why there is a loop that keeps reducing the map.) letterMapping = copy.deepcopy(letterMapping) loopAgain = True while loopAgain: # First assume that we will not loop again: loopAgain = False # solvedLetters will be a list of uppercase letters that have one # and only one possible mapping in letterMapping solvedLetters = [] for cipherletter in LETTERS: if len(letterMapping[cipherletter]) == 1: solvedLetters.append(letterMapping[cipherletter][0]) # If a letter is solved, than it cannot possibly be a potential # decryption letter for a different ciphertext letter, so we # should remove it from those other lists. for cipherletter in LETTERS: for s in solvedLetters: if len(letterMapping[cipherletter]) != 1 and s in letterMapping[cipherletter]: letterMapping[cipherletter].remove(s) if len(letterMapping[cipherletter]) == 1: # A new letter is now solved, so loop again. loopAgain = True return letterMapping
这个操作应该是在decryptWithCipherletterMapping(ciphertext, letterMapping)之前做的检查。
完全有必要把上面的步骤整合封装到一个新的函数中:目的是得到一个处理过的交集映射。
def hackSimpleSub(message): intersectedMap = getBlankCipherletterMapping() cipherwordList = nonLettersOrSpacePattern.sub('', message.upper()).split() for cipherword in cipherwordList: # Get a new cipherletter mapping for each ciphertext word. newMap = getBlankCipherletterMapping() wordPattern = makeWordPatterns.getWordPattern(cipherword) if wordPattern not in wordPatterns.allPatterns: continue # This word was not in our dictionary, so continue. # Add the letters of each candidate to the mapping. for candidate in wordPatterns.allPatterns[wordPattern]: newMap = addLettersToMapping(newMap, cipherword, candidate) # Intersect the new mapping with the existing intersected mapping. intersectedMap = intersectMappings(intersectedMap, newMap) # Remove any solved letters from the other lists. return removeSolvedLettersFromMapping(intersectedMap)
我们可以用交集映射来解密密文了,就是加密的逆替换过程。同样把他封装成一个函数:
def decryptWithCipherletterMapping(ciphertext, letterMapping):
# Return a string of the ciphertext decrypted with the letter mapping,
# with any ambiguous decrypted letters replaced with an _ underscore.
# First create a simple sub key from the letterMapping mapping.
# ['x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x', 'x']
key = ['x'] * len(LETTERS)
for cipherletter in LETTERS:
if len(letterMapping[cipherletter]) == 1:
# If there's only one letter, add it to the key.
keyIndex = LETTERS.find(letterMapping[cipherletter][0])
key[keyIndex] = cipherletter
else:
ciphertext = ciphertext.replace(cipherletter.lower(), '_')
ciphertext = ciphertext.replace(cipherletter.upper(), '_')
key = ''.join(key)
# With the key we've created, decrypt the ciphertext.
return simpleSubCipher.decryptMessage(key, ciphertext)
如果交集里面的某个列表只有一个元素,那么就可以确定下这个cipherletter对应的元素,
如果交集里面的某个列表有很多个或者是空集,那么就把密文里面的对应字符先转化为符号'_',
因为我们不知道他要解密成什么是正确的,干脆就不解密,反正在decryptMessage(key, ciphertext)
中,ciphertext里面的符号也是保留下来的。
key这样赋值的话,那些不知道密钥的字母对应的key是'x',像
key: 'LxWOAYUISxxMNXPxxCRJTQExxZ'
有问题吗?
我们在回顾一下简单替换的替换程序
def translateMessage(key, message, mode): translated = '' charsA = LETTERS charsB = key if mode == 'decrypt': # 简单替换的加密和解密在逻辑上的相似和对应,使得加密解密的映射相反即可 # 加密保持现状,解密把映射取反,即交换charsA, charsB # For decrypting, we can use the same code as encrypting. We # just need to swap where the key and LETTERS strings are used. charsA, charsB = charsB, charsA # loop through each symbol in the message # 明文消息中大写,对于密文还是大写,小写对应还是小写 # 符号则不转换(加密)直接赋值 lower()和upper()方法对像'?'不起作用 for symbol in message: if symbol.upper() in charsA: # encrypt/decrypt the symbol symIndex = charsA.find(symbol.upper()) if symbol.isupper(): translated += charsB[symIndex].upper() else: translated += charsB[symIndex].lower() else: # symbol is not in LETTERS, just add it translated += symbol return translated
传入的key: 'LxWOAYUISxxMNXPxxCRJTQExxZ' 未知的是x全部小写,注意,压根不会找到'x'头上
‘x’就和其他符号一样,只是戴了一个面具而已,精巧的是这个程序可以识这种伪装。
这个地方使用了+进行字符串的连接,
简单替代破译程序源代码:
# Simple Substitution Cipher Hacker # http://inventwithpython.com/hacking (BSD Licensed) import os, re, copy, pprint, pyperclip, simpleSubCipher, makeWordPatterns if not os.path.exists('wordPatterns.py'): makeWordPatterns.main() # create the wordPatterns.py file import wordPatterns LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' nonLettersOrSpacePattern = re.compile('[^A-Zs]') def main(): message = 'Sy l nlx sr pyyacao l ylwj eiswi upar lulsxrj isr sxrjsxwjr, ia esmm rwctjsxsza sj wmpramh, lxo txmarr jia aqsoaxwa sr pqaceiamnsxu, ia esmm caytra jp famsaqa sj. Sy, px jia pjiac ilxo, ia sr pyyacao rpnajisxu eiswi lyypcor l calrpx ypc lwjsxu sx lwwpcolxwa jp isr sxrjsxwjr, ia esmm lwwabj sj aqax px jia rmsuijarj aqsoaxwa. Jia pcsusx py nhjir sr agbmlsxao sx jisr elh. -Facjclxo Ctrramm' # Determine the possible valid ciphertext translations. print('Hacking...') letterMapping = hackSimpleSub(message) # Display the results to the user. print('Mapping:') pprint.pprint(letterMapping) print() print('Original ciphertext:') print(message) print() print('Copying hacked message to clipboard:') hackedMessage = decryptWithCipherletterMapping(message, letterMapping) pyperclip.copy(hackedMessage) print(hackedMessage) def getBlankCipherletterMapping(): # Returns a dictionary value that is a blank cipherletter mapping. # 每个键对应的值都是一个空的列表,但并不是None ''' >>> mydict = {'A': []} >>> mydict['A'] == None False >>> ''' return {'A': [], 'B': [], 'C': [], 'D': [], 'E': [], 'F': [], 'G': [], 'H': [], 'I': [], 'J': [], 'K': [], 'L': [], 'M': [], 'N': [], 'O': [], 'P': [], 'Q': [], 'R': [], 'S': [], 'T': [], 'U': [], 'V': [], 'W': [], 'X': [], 'Y': [], 'Z': []} def addLettersToMapping(letterMapping, cipherword, candidate): # The letterMapping parameter is a "cipherletter mapping" dictionary # value that the return value of this function starts as a copy of. # The cipherword parameter is a string value of the ciphertext word. # The candidate parameter is a possible English word that the # cipherword could decrypt to. # This function adds the letters of the candidate as potential # decryption letters for the cipherletters in the cipherletter # mapping. letterMapping = copy.deepcopy(letterMapping) for i in range(len(cipherword)): if candidate[i] not in letterMapping[cipherword[i]]: letterMapping[cipherword[i]].append(candidate[i]) return letterMapping def intersectMappings(mapA, mapB): # To intersect two maps, create a blank map, and then add only the # potential decryption letters if they exist in BOTH maps. intersectedMapping = getBlankCipherletterMapping() for letter in LETTERS: # An empty list means "any letter is possible". In this case just # copy the other map entirely. if mapA[letter] == []: intersectedMapping[letter] = copy.deepcopy(mapB[letter]) elif mapB[letter] == []: intersectedMapping[letter] = copy.deepcopy(mapA[letter]) else: # If a letter in mapA[letter] exists in mapB[letter], add # that letter to intersectedMapping[letter]. for mappedLetter in mapA[letter]: if mappedLetter in mapB[letter]: intersectedMapping[letter].append(mappedLetter) return intersectedMapping def removeSolvedLettersFromMapping(letterMapping): # Cipher letters in the mapping that map to only one letter are # "solved" and can be removed from the other letters. # For example, if 'A' maps to potential letters ['M', 'N'], and 'B' # maps to ['N'], then we know that 'B' must map to 'N', so we can # remove 'N' from the list of what 'A' could map to. So 'A' then maps # to ['M']. Note that now that 'A' maps to only one letter, we can # remove 'M' from the list of letters for every other # letter. (This is why there is a loop that keeps reducing the map.) letterMapping = copy.deepcopy(letterMapping) loopAgain = True while loopAgain: # First assume that we will not loop again: loopAgain = False # solvedLetters will be a list of uppercase letters that have one # and only one possible mapping in letterMapping solvedLetters = [] for cipherletter in LETTERS: if len(letterMapping[cipherletter]) == 1: solvedLetters.append(letterMapping[cipherletter][0]) # If a letter is solved, than it cannot possibly be a potential # decryption letter for a different ciphertext letter, so we # should remove it from those other lists. for cipherletter in LETTERS: for s in solvedLetters: if len(letterMapping[cipherletter]) != 1 and s in letterMapping[cipherletter]: letterMapping[cipherletter].remove(s) if len(letterMapping[cipherletter]) == 1: # A new letter is now solved, so loop again. loopAgain = True return letterMapping def hackSimpleSub(message): intersectedMap = getBlankCipherletterMapping() cipherwordList = nonLettersOrSpacePattern.sub('', message.upper()).split() for cipherword in cipherwordList: # Get a new cipherletter mapping for each ciphertext word. newMap = getBlankCipherletterMapping() wordPattern = makeWordPatterns.getWordPattern(cipherword) if wordPattern not in wordPatterns.allPatterns: continue # This word was not in our dictionary, so continue. # Add the letters of each candidate to the mapping. for candidate in wordPatterns.allPatterns[wordPattern]: newMap = addLettersToMapping(newMap, cipherword, candidate) # Intersect the new mapping with the existing intersected mapping. intersectedMap = intersectMappings(intersectedMap, newMap) # Remove any solved letters from the other lists. return removeSolvedLettersFromMapping(intersectedMap) def decryptWithCipherletterMapping(ciphertext, letterMapping): # Return a string of the ciphertext decrypted with the letter mapping, # with any ambiguous decrypted letters replaced with an _ underscore. # First create a simple sub key from the letterMapping mapping. key = ['x'] * len(LETTERS) for cipherletter in LETTERS: if len(letterMapping[cipherletter]) == 1: # If there's only one letter, add it to the key. keyIndex = LETTERS.find(letterMapping[cipherletter][0]) key[keyIndex] = cipherletter else: ciphertext = ciphertext.replace(cipherletter.lower(), '_') ciphertext = ciphertext.replace(cipherletter.upper(), '_') key = ''.join(key) # With the key we've created, decrypt the ciphertext. return simpleSubCipher.decryptMessage(key, ciphertext) if __name__ == '__main__': main()
输出:
Hacking... Mapping: {'A': ['E'], 'B': ['Y', 'P', 'B'], 'C': ['R'], 'D': [], 'E': ['W'], 'F': ['B', 'P'], 'G': ['B', 'Q', 'X', 'P', 'Y'], 'H': ['P', 'Y', 'K', 'X', 'B'], 'I': ['H'], 'J': ['T'], 'K': [], 'L': ['A'], 'M': ['L'], 'N': ['M'], 'O': ['D'], 'P': ['O'], 'Q': ['V'], 'R': ['S'], 'S': ['I'], 'T': ['U'], 'U': ['G'], 'V': [], 'W': ['C'], 'X': ['N'], 'Y': ['F'], 'Z': ['Z']} Original ciphertext: Sy l nlx sr pyyacao l ylwj eiswi upar lulsxrj isr sxrjsxwjr, ia esmm rwctjsxsza sj wmpramh, lxo txmarr jia aqsoaxwa sr pqaceiamnsxu, ia esmm caytra jp famsaqa sj. Sy, px jia pjiac ilxo, ia sr pyyacao rpnajisxu eiswi lyypcor l calrpx ypc lwjsxu sx lwwpcolxwa jp isr sxrjsxwjr, ia esmm lwwabj sj aqax px jia rmsuijarj aqsoaxwa. Jia pcsusx py nhjir sr agbmlsxao sx jisr elh. -Facjclxo Ctrramm Copying hacked message to clipboard: If a man is offered a fact which goes against his instincts, he will scrutinize it closel_, and unless the evidence is overwhelming, he will refuse to _elieve it. If, on the other hand, he is offered something which affords a reason for acting in accordance to his instincts, he will acce_t it even on the slightest evidence. The origin of m_ths is e__lained in this wa_. -_ertrand Russell
P.S.
正则表达式
我们不能加密空格吗?
当然可以!用特定的字符代替空格会使得密文看起来更乱,但是空格是使用最多的字符了,加密后可以容易分辨出来,所以这样看来
加密空格没什么必要