The Simple Substitution Cipher substitutes one letter for another. Since there are 26 possible substitutions for the letter A, 25 possible substitutions for B, 24 for C, and so on, the total number of possible keys is 26 × 25 × 24 × 23 × . . . × 1, or 403,291,461,126,605,635,584,000,000 keys! That’s far too many keys for even a supercomputer to brute force, so the code-breaking method used in Project 7, “Caesar Hacker,” can’t be used against the simple cipher. Unfortunately, devious attackers can take advantage of known weakness to break the code. If you’d like to learn more about ciphers and code breaking, you can read my book Cracking Codes with Python (No Starch Press, 2018; https://nostarch.com/crackingcodes/).
When you run simplesubcipher.py, the output will look like this:
Simple Substitution Cipher, by Al Sweigart
A simple substitution cipher has a one-to-one translation for each
symbol in the plaintext and each symbol in the ciphertext.
Do you want to (e)ncrypt or (d)ecrypt?
> e
Please specify the key to use.
Or enter RANDOM to have one generated for you.
> random
The key is WNOMTRCEHDXBFVSLKAGZIPYJQU. KEEP THIS SECRET!
Enter the message to encrypt.
> Meet me by the rose bushes tonight.
The encrypted message is:
Fttz ft nq zet asgt nigetg zsvhcez.
Full encrypted text copied to clipboard.
Simple Substitution Cipher, by Al Sweigart
A simple substitution cipher has a one-to-one translation for each
symbol in the plaintext and each symbol in the ciphertext.
Do you want to (e)ncrypt or (d)ecrypt?
> d
Please specify the key to use.
> WNOMTRCEHDXBFVSLKAGZIPYJQU
Enter the message to decrypt.
> Fttz ft nq zet asgt nigetg zsvhcez.
The decrypted message is:
Meet me by the rose bushes tonight.
Full decrypted text copied to clipboard.
The position of each of the key’s 26 letters corresponds to the letter of the alphabet at that same position:
With this key, the letter A encrypts to W (and W decrypts to A), the letter B encrypts to N, and so on. The LETTERS
and key
variables are assigned to charsA
and charsB
(or the other way around if decrypting). Any message characters in charsA
are substituted with the corresponding character in charsB
to produce the final translated message.
1. """Simple Substitution Cipher, by Al Sweigart [email protected]
2. A simple substitution cipher has a one-to-one translation for each
3. symbol in the plaintext and each symbol in the ciphertext.
4. More info at: https://en.wikipedia.org/wiki/Substitution_cipher
5. This code is available at https://nostarch.com/big-book-small-python-programming
6. Tags: short, cryptography, math"""
7.
8. import random
9.
10. try:
11. import pyperclip # pyperclip copies text to the clipboard.
12. except ImportError:
13. pass # If pyperclip is not installed, do nothing. It's no big deal.
14.
15. # Every possible symbol that can be encrypted/decrypted:
16. LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
17.
18. def main():
19. print('''Simple Substitution Cipher, by Al Sweigart
20. A simple substitution cipher has a one-to-one translation for each
21. symbol in the plaintext and each symbol in the ciphertext.''')
22.
23. # Let the user specify if they are encrypting or decrypting:
24. while True: # Keep asking until the user enters e or d.
25. print('Do you want to (e)ncrypt or (d)ecrypt?')
26. response = input('> ').lower()
27. if response.startswith('e'):
28. myMode = 'encrypt'
29. break
30. elif response.startswith('d'):
31. myMode = 'decrypt'
32. break
33. print('Please enter the letter e or d.')
34.
35. # Let the user specify the key to use:
36. while True: # Keep asking until the user enters a valid key.
37. print('Please specify the key to use.')
38. if myMode == 'encrypt':
39. print('Or enter RANDOM to have one generated for you.')
40. response = input('> ').upper()
41. if response == 'RANDOM':
42. myKey = generateRandomKey()
43. print('The key is {}. KEEP THIS SECRET!'.format(myKey))
44. break
45. else:
46. if checkKey(response):
47. myKey = response
48. break
49.
50. # Let the user specify the message to encrypt/decrypt:
51. print('Enter the message to {}.'.format(myMode))
52. myMessage = input('> ')
53.
54. # Perform the encryption/decryption:
55. if myMode == 'encrypt':
56. translated = encryptMessage(myMessage, myKey)
57. elif myMode == 'decrypt':
58. translated = decryptMessage(myMessage, myKey)
59.
60. # Display the results:
61. print('The %sed message is:' % (myMode))
62. print(translated)
63.
64. try:
65. pyperclip.copy(translated)
66. print('Full %sed text copied to clipboard.' % (myMode))
67. except:
68. pass # Do nothing if pyperclip wasn't installed.
69.
70.
71. def checkKey(key):
72. """Return True if key is valid. Otherwise return False."""
73. keyList = list(key)
74. lettersList = list(LETTERS)
75. keyList.sort()
76. lettersList.sort()
77. if keyList != lettersList:
78. print('There is an error in the key or symbol set.')
79. return False
80. return True
81.
82.
83. def encryptMessage(message, key):
84. """Encrypt the message using the key."""
85. return translateMessage(message, key, 'encrypt')
86.
87.
88. def decryptMessage(message, key):
89. """Decrypt the message using the key."""
90. return translateMessage(message, key, 'decrypt')
91.
92.
93. def translateMessage(message, key, mode):
94. """Encrypt or decrypt the message using the key."""
95. translated = ''
96. charsA = LETTERS
97. charsB = key
98. if mode == 'decrypt':
99. # For decrypting, we can use the same code as encrypting. We
100. # just need to swap where the key and LETTERS strings are used.
101. charsA, charsB = charsB, charsA
102.
103. # Loop through each symbol in the message:
104. for symbol in message:
105. if symbol.upper() in charsA:
106. # Encrypt/decrypt the symbol:
107. symIndex = charsA.find(symbol.upper())
108. if symbol.isupper():
109. translated += charsB[symIndex].upper()
110. else:
111. translated += charsB[symIndex].lower()
112. else:
113. # The symbol is not in LETTERS, just add it unchanged.
114. translated += symbol
115.
116. return translated
117.
118.
119. def generateRandomKey():
120. """Generate and return a random encryption key."""
121. key = list(LETTERS) # Get a list from the LETTERS string.
122. random.shuffle(key) # Randomly shuffle the list.
123. return ''.join(key) # Get a string from the list.
124.
125.
126. # If this program was run (instead of imported), run the program:
127. if __name__ == '__main__':
128. main()
Try to find the answers to the following questions. Experiment with some modifications to the code and rerun the program to see what effect the changes have.
random.shuffle(key)
on line 122 and enter RANDOM
for the key?LETTERS
string on line 16 to become 'ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
?