替代密码是指先建立一个替换表,加密时将需要加密的明文依次通过查表,替换为相应的字符,明文字符被逐个替换后,生成无任何意义的字符串,即密文,替代密码的密钥就是其替换表 。
分为单表替换与多表替换,此处讨论单表替换
加密解密实例:
1 # Simple Substitution Cipher 2 # http://inventwithpython.com/hacking (BSD Licensed) 3 4 import pyperclip, sys, random 5 6 7 LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 8 9 def main(): 10 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' 11 myKey = 'LFWOAYUISVKMNXPBDCRJTQEGHZ' 12 myMode = 'encrypt' # set to 'encrypt' or 'decrypt' 13 14 checkValidKey(myKey) 15 16 if myMode == 'encrypt': 17 translated = encryptMessage(myKey, myMessage) 18 elif myMode == 'decrypt': 19 translated = decryptMessage(myKey, myMessage) 20 print('Using key %s' % (myKey)) 21 print('The %sed message is:' % (myMode)) 22 print(translated) 23 pyperclip.copy(translated) 24 print() 25 print('This message has been copied to the clipboard.') 26 27 28 def checkValidKey(key): 29 keyList = list(key) 30 lettersList = list(LETTERS) 31 keyList.sort() 32 lettersList.sort() 33 if keyList != lettersList: 34 sys.exit('There is an error in the key or symbol set.') 35 36 37 def encryptMessage(key, message): 38 return translateMessage(key, message, 'encrypt') 39 40 41 def decryptMessage(key, message): 42 return translateMessage(key, message, 'decrypt') 43 44 45 def translateMessage(key, message, mode): 46 translated = '' 47 charsA = LETTERS 48 charsB = key 49 if mode == 'decrypt': 50 # For decrypting, we can use the same code as encrypting. We 51 # just need to swap where the key and LETTERS strings are used. 52 charsA, charsB = charsB, charsA 53 54 # loop through each symbol in the message 55 for symbol in message: 56 if symbol.upper() in charsA: 57 # encrypt/decrypt the symbol 58 symIndex = charsA.find(symbol.upper()) 59 if symbol.isupper(): 60 translated += charsB[symIndex].upper() 61 else: 62 translated += charsB[symIndex].lower() 63 else: 64 # symbol is not in LETTERS, just add it 65 translated += symbol 66 67 return translated 68 69 70 def getRandomKey(): 71 key = list(LETTERS) 72 random.shuffle(key) 73 return ''.join(key) 74 75 76 if __name__ == '__main__': 77 main()
破译方法:
对于单表替换密文与原文字母的字母是 一 一 对应的,所以可以统计出每个每个单词的格式 如:name:0,1,2,3 apple:0,1,1,2,3。 此处称为单词模式
从词典得到单词结构,再与密文单词相互对应,最终统计到每一个密文与原文的对应关系
- 找出密文里每个单词的单词模式
- 找出每个密词可以解密成哪些英文单词
- 使用密词的候选单词列表为每个密词创建一个密字映射。(密字映射是字典值)
- 计算所有的密字交集,得到唯一的交集映射
- 从交集密字映射移除任何已破解的字母
消息越长越有可能被破译
1 # Makes the wordPatterns.py File 2 # http://inventwithpython.com/hacking (BSD Licensed) 3 4 # Creates wordPatterns.py based on the words in our dictionary 5 # text file, dictionary.txt. (Download this file from 6 # http://invpy.com/dictionary.txt) 7 8 import pprint 9 10 11 def getWordPattern(word): 12 # Returns a string of the pattern form of the given word. 13 # e.g. '0.1.2.3.4.1.2.3.5.6' for 'DUSTBUSTER' 14 word = word.upper() 15 nextNum = 0 16 letterNums = {} 17 wordPattern = [] 18 19 for letter in word: 20 if letter not in letterNums: 21 letterNums[letter] = str(nextNum) 22 nextNum += 1 23 wordPattern.append(letterNums[letter]) 24 return '.'.join(wordPattern) 25 26 27 def main(): 28 allPatterns = {} 29 30 fo = open('dictionary.txt') 31 wordList = fo.read().split(' ') 32 fo.close() 33 34 for word in wordList: 35 # Get the pattern for each string in wordList. 36 pattern = getWordPattern(word) 37 38 if pattern not in allPatterns: 39 allPatterns[pattern] = [word] 40 else: 41 allPatterns[pattern].append(word) 42 43 # This is code that writes code. The wordPatterns.py file contains 44 # one very, very large assignment statement. 45 fo = open('wordPatterns.py', 'w') 46 fo.write('allPatterns = ') 47 fo.write(pprint.pformat(allPatterns)) 48 fo.close() 49 50 51 if __name__ == '__main__': 52 main()
然后导入之前的文件并破解
1 # Simple Substitution Cipher Hacker 2 # http://inventwithpython.com/hacking (BSD Licensed) 3 4 import os, re, copy, pprint, pyperclip, simpleSubCipher, makeWordPatterns 5 6 if not os.path.exists('wordPatterns.py'): 7 makeWordPatterns.main() # create the wordPatterns.py file 8 import wordPatterns 9 10 LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' 11 nonLettersOrSpacePattern = re.compile('[^A-Zs]') 12 13 def main(): 14 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' 15 16 # Determine the possible valid ciphertext translations. 17 print('Hacking...') 18 letterMapping = hackSimpleSub(message) 19 20 # Display the results to the user. 21 print('Mapping:') 22 pprint.pprint(letterMapping) 23 print() 24 print('Original ciphertext:') 25 print(message) 26 print() 27 print('Copying hacked message to clipboard:') 28 hackedMessage = decryptWithCipherletterMapping(message, letterMapping) 29 pyperclip.copy(hackedMessage) 30 print(hackedMessage) 31 32 33 def getBlankCipherletterMapping(): 34 # Returns a dictionary value that is a blank cipherletter mapping. 35 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': []} 36 37 38 def addLettersToMapping(letterMapping, cipherword, candidate): 39 # The letterMapping parameter is a "cipherletter mapping" dictionary 40 # value that the return value of this function starts as a copy of. 41 # The cipherword parameter is a string value of the ciphertext word. 42 # The candidate parameter is a possible English word that the 43 # cipherword could decrypt to. 44 45 # This function adds the letters of the candidate as potential 46 # decryption letters for the cipherletters in the cipherletter 47 # mapping. 48 49 letterMapping = copy.deepcopy(letterMapping) 50 for i in range(len(cipherword)): 51 if candidate[i] not in letterMapping[cipherword[i]]: 52 letterMapping[cipherword[i]].append(candidate[i]) 53 return letterMapping 54 55 56 def intersectMappings(mapA, mapB): 57 # To intersect two maps, create a blank map, and then add only the 58 # potential decryption letters if they exist in BOTH maps. 59 intersectedMapping = getBlankCipherletterMapping() 60 for letter in LETTERS: 61 62 # An empty list means "any letter is possible". In this case just 63 # copy the other map entirely. 64 if mapA[letter] == []: 65 intersectedMapping[letter] = copy.deepcopy(mapB[letter]) 66 elif mapB[letter] == []: 67 intersectedMapping[letter] = copy.deepcopy(mapA[letter]) 68 else: 69 # If a letter in mapA[letter] exists in mapB[letter], add 70 # that letter to intersectedMapping[letter]. 71 for mappedLetter in mapA[letter]: 72 if mappedLetter in mapB[letter]: 73 intersectedMapping[letter].append(mappedLetter) 74 75 return intersectedMapping 76 77 78 def removeSolvedLettersFromMapping(letterMapping): 79 # Cipher letters in the mapping that map to only one letter are 80 # "solved" and can be removed from the other letters. 81 # For example, if 'A' maps to potential letters ['M', 'N'], and 'B' 82 # maps to ['N'], then we know that 'B' must map to 'N', so we can 83 # remove 'N' from the list of what 'A' could map to. So 'A' then maps 84 # to ['M']. Note that now that 'A' maps to only one letter, we can 85 # remove 'M' from the list of letters for every other 86 # letter. (This is why there is a loop that keeps reducing the map.) 87 letterMapping = copy.deepcopy(letterMapping) 88 loopAgain = True 89 while loopAgain: 90 # First assume that we will not loop again: 91 loopAgain = False 92 93 # solvedLetters will be a list of uppercase letters that have one 94 # and only one possible mapping in letterMapping 95 solvedLetters = [] 96 for cipherletter in LETTERS: 97 if len(letterMapping[cipherletter]) == 1: 98 solvedLetters.append(letterMapping[cipherletter][0]) 99 100 # If a letter is solved, than it cannot possibly be a potential 101 # decryption letter for a different ciphertext letter, so we 102 # should remove it from those other lists. 103 for cipherletter in LETTERS: 104 for s in solvedLetters: 105 if len(letterMapping[cipherletter]) != 1 and s in letterMapping[cipherletter]: 106 letterMapping[cipherletter].remove(s) 107 if len(letterMapping[cipherletter]) == 1: 108 # A new letter is now solved, so loop again. 109 loopAgain = True 110 return letterMapping 111 112 113 def hackSimpleSub(message): 114 intersectedMap = getBlankCipherletterMapping() 115 cipherwordList = nonLettersOrSpacePattern.sub('', message.upper()).split() 116 for cipherword in cipherwordList: 117 # Get a new cipherletter mapping for each ciphertext word. 118 newMap = getBlankCipherletterMapping() 119 120 wordPattern = makeWordPatterns.getWordPattern(cipherword) 121 if wordPattern not in wordPatterns.allPatterns: 122 continue # This word was not in our dictionary, so continue. 123 124 # Add the letters of each candidate to the mapping. 125 for candidate in wordPatterns.allPatterns[wordPattern]: 126 newMap = addLettersToMapping(newMap, cipherword, candidate) 127 128 # Intersect the new mapping with the existing intersected mapping. 129 intersectedMap = intersectMappings(intersectedMap, newMap) 130 131 # Remove any solved letters from the other lists. 132 return removeSolvedLettersFromMapping(intersectedMap) 133 134 135 def decryptWithCipherletterMapping(ciphertext, letterMapping): 136 # Return a string of the ciphertext decrypted with the letter mapping, 137 # with any ambiguous decrypted letters replaced with an _ underscore. 138 139 # First create a simple sub key from the letterMapping mapping. 140 key = ['x'] * len(LETTERS) 141 for cipherletter in LETTERS: 142 if len(letterMapping[cipherletter]) == 1: 143 # If there's only one letter, add it to the key. 144 keyIndex = LETTERS.find(letterMapping[cipherletter][0]) 145 key[keyIndex] = cipherletter 146 else: 147 ciphertext = ciphertext.replace(cipherletter.lower(), '_') 148 ciphertext = ciphertext.replace(cipherletter.upper(), '_') 149 key = ''.join(key) 150 151 # With the key we've created, decrypt the ciphertext. 152 return simpleSubCipher.decryptMessage(key, ciphertext) 153 154 155 if __name__ == '__main__': 156 main()
此方法密钥为一张对应表,不易记,可用一句话作为密钥,用程序处理后再用于加密
1 def makeSimpleSubKey(keyword): 2 # create the key from the keyword 3 newKey = '' 4 keyword = keyword.upper() 5 keyAlphabet = list(simpleSubCipher.LETTERS) 6 for i in range(len(keyword)): 7 if keyword[i] not in newKey: 8 newKey += keyword[i] 9 keyAlphabet.remove(keyword[i]) 10 key = newKey + ''.join(keyAlphabet) 11 return key
1 # Simple Substitution Dictionary Hacker, http://inventwithpython.com/hacking (BSD Licensed) 2 import pyperclip, simpleSubKeyword, detectEnglish 3 4 SILENT_MODE = False 5 6 def main(): 7 myMessage = r"""SJITDOPIQR: JIR RIQMUNQRO AY P WDQC QCR NRSMRQN JT A SJITDORO QJ CRMNRGT AY S. -PHAMJNR ADRMSR""" 8 9 brokenCiphertext = hackSimpleSubDictionary(myMessage) 10 11 if brokenCiphertext == None: 12 # hackSimpleSubDictionary() will return the None value if it was unable to hack the encryption. 13 print('Hacking failed. Unable to hack this ciphertext.') 14 else: 15 # The plaintext is displayed on the screen. For the convenience of the user, we copy the text of the code to the clipboard. 16 print('Copying broken ciphertext to clipboard:') 17 print(brokenCiphertext) 18 pyperclip.copy(brokenCiphertext) 19 20 21 def hackSimpleSubDictionary(message): 22 print('Hacking with %s possible dictionary words...' % (len(detectEnglish.ENGLISH_WORDS) * 3)) 23 24 # Python programs can be stopped at any time by pressing Ctrl-C (on Windows) or Ctrl-D (on Mac and Linux) 25 print('(Press Ctrl-C or Ctrl-D to quit at any time.)') 26 27 tryNum = 1 28 29 # brute-force by looping through every possible key 30 for key in detectEnglish.ENGLISH_WORDS: 31 if tryNum % 100 == 0 and not SILENT_MODE: 32 print('%s keys tried. (%s)' % (tryNum, key)) 33 34 decryptedText = simpleSubKeyword.decryptMessage(key, message) 35 36 if detectEnglish.getEnglishCount(decryptedText) > 0.20: 37 # Check with the user to see if the decrypted key has been found. 38 print() 39 print('Possible encryption hack:') 40 print('Key: ' + str(key)) 41 print('Decrypted message: ' + decryptedText[:100]) 42 print() 43 print('Enter D for done, or just press Enter to continue hacking:') 44 response = input('> ') 45 46 if response.upper().startswith('D'): 47 return decryptedText 48 49 tryNum += 1 50 return None 51 52 if __name__ == '__main__': 53 main()
1 # Simple Substitution Cipher Editor, http://inventwithpython.com/hacking (BSD Licensed) 2 3 import textwrap, string, pyperclip 4 5 myMessage = '' 6 SYMBOLS = '' 7 8 9 def main(useText=None, useMapping=None): 10 print('Simple Substitution Cipher Editor') 11 12 while True: 13 # Get the text to start editing: 14 if useText == None: 15 # start editing a new cipher 16 print('Enter the cipher text you want to decrypt (or "quit"):') 17 18 # Handle if the user wants to quit: 19 ciphertext = input('> ').upper() 20 if ciphertext == 'QUIT': 21 return 22 else: 23 ciphertext = useText 24 25 if useMapping == None: 26 mapping = getBlankMapping() # start with a new, blank mapping. 27 else: 28 mapping = useMapping 29 30 31 while True: 32 # On each iteration of this loop, display the current translation 33 # and let the user type in a command to perform. 34 35 # Display the current translation: 36 print(' ') 37 printMessage(ciphertext, mapping) 38 printMapping(mapping) 39 print('COMMANDS: Enter ciphertext letter to substitute, or "quit", "clear",') 40 print('"copy message", "copy key", "enter key", or "new":') 41 42 # Get a command from the user and perform it: 43 command = input('> ').upper() 44 if command == 'QUIT': 45 return 46 elif command == 'CLEAR': 47 # reset the mapping to a new, blank mapping 48 mapping = getBlankMapping() 49 elif command == 'NEW': 50 print(' ' * 25) # print a huge gap 51 break # break out of the inner loop 52 elif command == 'COPY MESSAGE': 53 pyperclip.copy(getTranslation(ciphertext, mapping)) 54 print('Copied the translated text to the clipboard.') 55 elif command == 'COPY KEY': 56 key = '' 57 for letter in string.ascii_uppercase: 58 key += mapping[letter] 59 pyperclip.copy(key) 60 print('Copied the key to the clipboard.') 61 elif command == 'ENTER KEY': 62 pass # TODO 63 else: 64 # Assume the user is trying to suggest a ciphertext replacement: 65 66 # get the ciphertext letter 67 if len(command) != 1 or command not in string.ascii_uppercase: 68 print('Invalid character. Please specify a single letter.') 69 continue 70 71 # get the letter that will replace this ciphertext letter 72 print('Enter letter that %s should map to:' % command) 73 mapToLetter = input('> ').upper() 74 if mapToLetter == '': 75 # entering nothing means the user wants to reset that ciphertext letter 76 mapToLetter = '_' 77 if len(mapToLetter) != 1 or mapToLetter not in string.ascii_uppercase + '_': 78 print('Invalid character. Please specify a single letter.') 79 continue 80 81 # add this replacement letter to the current mapping 82 mapping[command] = mapToLetter.lower() 83 84 85 def getTranslation(ciphertext, mapping): 86 # Returns a string of the translation of ciphertext. Each character 87 # in ciphertext is used as a key in mapping, and the returned 88 # string uses the character that is the value for that key. 89 result = '' 90 for letter in ciphertext: 91 if letter not in string.ascii_uppercase: 92 result += letter 93 else: 94 result += mapping[letter] 95 return result 96 97 98 def getBlankMapping(): 99 # Returns a dict with keys of the uppercase letters and values of 100 # the string '_'. 101 mapping = {} 102 for letter in string.ascii_uppercase: 103 mapping[letter] = '_' 104 return mapping 105 106 107 def printMessage(ciphertext, mapping): 108 # Print the cipher text, along with the translation according to the 109 # current mapping. The text will never go past 80 characters in length 110 # per line. 111 112 # Split up the cipher text into lines of at most 80 characters in length, 113 # and then put them in a list of these lines. 114 wrappedText = textwrap.fill(ciphertext) 115 lines = wrappedText.split(' ') 116 117 for line in lines: 118 # Print each line of ciphertext, followed by its translation. 119 print(line) 120 print(getTranslation(line, mapping)) 121 print() 122 123 124 def printMapping(mapping): 125 # Print the mapping in a user-friendly format. 126 print('Current Key:') 127 print(' ' + ' '.join(list(string.ascii_uppercase))) 128 129 print(' ', end='') 130 for letter in string.ascii_uppercase: 131 print(mapping[letter] + ' ', end='') 132 print() 133 134 135 if __name__ == '__main__': 136 main()