The Vigenère cipher, misattributed to 19th-century cryptographer Blaise de Vigenère (others had independently invented it earlier), was impossible to crack for hundreds of years. It is essentially the Caesar cipher, except it makes use of a multipart key. The so-called Vigenère key is a word, or even a random series of letters. Each letter represents a number by which to shift the letter in the message: A represents shifting a letter in the message by 0, B represents 1, C represents 2, and so on.
For example, if a Vigenère key is the word “CAT,” the C represents a shift of 2, the A represents 0, and the T represents 19. The first letter of the message gets shifted by 2, the second letter by 0, and the third letter by 19. For the fourth letter, we repeat the key of 2.
This use of multiple Caesar cipher keys is what gives the Vigenère cipher its strength. The possible number of combinations is too big to brute force. At the same time, the Vigenère cipher doesn’t suffer from the frequency analysis weakness that can crack the simple substitution cipher. For centuries, the Vigenère cipher represented the state of the art in cryptography.
You’ll notice many similarities between the code for the Vigenère and Caesar cipher programs. More info about the Vigenère cipher can be found at https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher. 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 vigenere.py, the output will look like this:
Vigenère Cipher, by Al Sweigart [email protected]
The Vigenère cipher is a polyalphabetic substitution cipher that was
powerful enough to remain unbroken for centuries.
Do you want to (e)ncrypt or (d)ecrypt?
> e
Please specify the key to use.
It can be a word or any combination of letters:
> PIZZA
Enter the message to encrypt.
> Meet me by the rose bushes tonight.
Encrypted message:
Bmds mt jx sht znre qcrgeh bnmivps.
Full encrypted text copied to clipboard.
Because the encryption and decryption processes are fairly similar, the translateMessage()
function handles both of them. The encryptMessage()
and decryptMessage()
functions are merely wrapper functions for translateMessage()
. In other words, they are functions that adjust their arguments, forward these to another function, and then return that function’s return value. This program uses these wrapper functions so that they can be called in a manner similar to encryptMessage()
and decryptMessage()
in Project 66, “Simple Substitution Cipher.” You can import these projects as modules in other programs to make use of their encryption code without having to copy and paste the code directly into your new program.
1. """Vigenère Cipher, by Al Sweigart [email protected]
2. The Vigenère cipher is a polyalphabetic substitution cipher that was
3. powerful enough to remain unbroken for centuries.
4. More info at: https://en.wikipedia.org/wiki/Vigen%C3%A8re_cipher
5. This code is available at https://nostarch.com/big-book-small-python-programming
6. Tags: short, cryptography, math"""
7.
8. try:
9. import pyperclip # pyperclip copies text to the clipboard.
10. except ImportError:
11. pass # If pyperclip is not installed, do nothing. It's no big deal.
12.
13. # Every possible symbol that can be encrypted/decrypted:
14. LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
15.
16.
17. def main():
18. print('''Vigenère Cipher, by Al Sweigart [email protected]
19. The Viegenère cipher is a polyalphabetic substitution cipher that was
20. powerful enough to remain unbroken for centuries.''')
21.
22. # Let the user specify if they are encrypting or decrypting:
23. while True: # Keep asking until the user enters e or d.
24. print('Do you want to (e)ncrypt or (d)ecrypt?')
25. response = input('> ').lower()
26. if response.startswith('e'):
27. myMode = 'encrypt'
28. break
29. elif response.startswith('d'):
30. myMode = 'decrypt'
31. break
32. print('Please enter the letter e or d.')
33.
34. # Let the user specify the key to use:
35. while True: # Keep asking until the user enters a valid key.
36. print('Please specify the key to use.')
37. print('It can be a word or any combination of letters:')
38. response = input('> ').upper()
39. if response.isalpha():
40. myKey = response
41. break
42.
43. # Let the user specify the message to encrypt/decrypt:
44. print('Enter the message to {}.'.format(myMode))
45. myMessage = input('> ')
46.
47. # Perform the encryption/decryption:
48. if myMode == 'encrypt':
49. translated = encryptMessage(myMessage, myKey)
50. elif myMode == 'decrypt':
51. translated = decryptMessage(myMessage, myKey)
52.
53. print('%sed message:' % (myMode.title()))
54. print(translated)
55.
56. try:
57. pyperclip.copy(translated)
58. print('Full %sed text copied to clipboard.' % (myMode))
59. except:
60. pass # Do nothing if pyperclip wasn't installed.
61.
62.
63. def encryptMessage(message, key):
64. """Encrypt the message using the key."""
65. return translateMessage(message, key, 'encrypt')
66.
67.
68. def decryptMessage(message, key):
69. """Decrypt the message using the key."""
70. return translateMessage(message, key, 'decrypt')
71.
72.
73. def translateMessage(message, key, mode):
74. """Encrypt or decrypt the message using the key."""
75. translated = [] # Stores the encrypted/decrypted message string.
76.
77. keyIndex = 0
78. key = key.upper()
79.
80. for symbol in message: # Loop through each character in message.
81. num = LETTERS.find(symbol.upper())
82. if num != -1: # -1 means symbol.upper() was not in LETTERS.
83. if mode == 'encrypt':
84. # Add if encrypting:
85. num += LETTERS.find(key[keyIndex])
86. elif mode == 'decrypt':
87. # Subtract if decrypting:
88. num -= LETTERS.find(key[keyIndex])
89.
90. num %= len(LETTERS) # Handle the potential wrap-around.
91.
92. # Add the encrypted/decrypted symbol to translated.
93. if symbol.isupper():
94. translated.append(LETTERS[num])
95. elif symbol.islower():
96. translated.append(LETTERS[num].lower())
97.
98. keyIndex += 1 # Move to the next letter in the key.
99. if keyIndex == len(key):
100. keyIndex = 0
101. else:
102. # Just add the symbol without encrypting/decrypting:
103. translated.append(symbol)
104.
105. return ''.join(translated)
106.
107.
108. # If this program was run (instead of imported), run the program:
109. if __name__ == '__main__':
110. 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.
'A'
?myKey = response
on line 40?