替换密码

替代密码是指先建立一个替换表,加密时将需要加密的明文依次通过查表,替换为相应的字符,明文字符被逐个替换后,生成无任何意义的字符串,即密文,替代密码的密钥就是其替换表 。

分为单表替换与多表替换,此处讨论单表替换

加密解密实例:

 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()
simpleSubCipher

破译方法:

对于单表替换密文与原文字母的字母是 一 一 对应的,所以可以统计出每个每个单词的格式 如:name:0,1,2,3   apple:0,1,1,2,3。 此处称为单词模式

从词典得到单词结构,再与密文单词相互对应,最终统计到每一个密文与原文的对应关系

  1. 找出密文里每个单词的单词模式
  2. 找出每个密词可以解密成哪些英文单词
  3. 使用密词的候选单词列表为每个密词创建一个密字映射。(密字映射是字典值)
  4. 计算所有的密字交集,得到唯一的交集映射
  5. 从交集密字映射移除任何已破解的字母

消息越长越有可能被破译

 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()
makeWordPatterns

然后导入之前的文件并破解

  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()
simpleSubHacker

此方法密钥为一张对应表,不易记,可用一句话作为密钥,用程序处理后再用于加密

 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
makeSimpleSubKey
 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()
simpleSubDictionaryHacker
  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()
simpleSubEditor
原文地址:https://www.cnblogs.com/zhangzixian/p/10483385.html