I was working on some fun LeetCode type questions, and I came across a challenge to replicate the Vigenere cipher encryption and decryption in Python.
In short, the Vigenere cipher allows one to encrypt and decrypt a message if the user knows an alphabetic key. It’s notable for being easy to use; encryption and decryption are done by overlaying the key next to the message, then shifting the message letter by the letter number of the overlaid key. For more information, see the Wikipedia page discussing the Vigenere cipher .
The below functions are the “know-your-number-theory” / expected versions of how to encrypt/decrypt, where c is the encrypted message to decrypt, m is the unencrypted text to encrypt, and keyword is the secret encoding key.
def vigenere_decrypt_cipher(c: str, keyword: str) -> str:
keyword_repeated = (keyword * (len(c) // len(keyword))) + keyword[:len(c) % len(keyword)]
plaintext = ''
for i in range(len(c)):
if c[i].isalpha():
shift = ord(keyword_repeated[i].upper()) - ord('A')
if c[i].islower():
plaintext += chr((ord(c[i]) - ord('a') - shift) % 26 + ord('a'))
else:
plaintext += chr((ord(c[i]) - ord('A') - shift) % 26 + ord('A'))
else:
plaintext += c[i]
return plaintext
def vigenere_encrypt_message(m: str, keyword: str) -> str:
#filter to kick out spaces and punctuation
filtered_m = ""
for toon in m:
if toon.isalpha():
filtered_m = filtered_m + toon
else:
pass
#the rest to process the "real" stuff
m = filtered_m.upper()
keyword = keyword.upper()
encrypted_message = ''
keyword_repeated = (keyword * (len(m) // len(keyword))) + keyword[:len(m) % len(keyword)]
for i in range(len(m)):
char = m[i]
if char.isalpha():
shift = ord(keyword_repeated[i].upper()) - ord('A')
if char.islower():
encrypted_message += chr((ord(char) - ord('a') + shift) % 26 + ord('a'))
else:
encrypted_message += chr((ord(char) - ord('A') + shift) % 26 + ord('A'))
else:
encrypted_message += char
return encrypted_message.upper()
Honestly, while it was fun to implement, it’s not immediately obvious how Vigenere’s works from the code. So for fun I wrote the functions below, which sort of mimics how Vigenere messages would be coded/decoded by hand:
def look_up_letter_index(letter):
alphabet = "abcdefghijklmnopqrstuvwxyz".upper()
return alphabet.find(letter.upper())
def decrypt_vignere(encrypted, key):
translated = ""
alphabet = "abcdefghijklmnopqrstuvwxyz".upper()
print(len(alphabet))
count = 0
alphabet_array = []
for letter in alphabet:
single_line = alphabet[count:26] + alphabet[0:count]
alphabet_array.append(single_line)
count = count + 1
print(alphabet_array)
print(look_up_letter_index("a"))
overlaid_key = key + key + key + key + key + key + key + key + key + key + key + key + key + key
overlaid_key += key + key + key + key + key + key + key + key + key + key + key + key + key + key
overlaid_key += key + key + key + key + key + key + key + key + key + key + key + key + key + key
overlaid_key += key + key + key + key + key + key + key + key + key + key + key + key + key + key
overlaid_key += key + key + key + key + key + key + key + key + key + key + key + key + key + key
overlaid_key += key + key + key + key + key + key + key + key + key + key + key + key + key + key
overlaid_key += key + key + key + key + key + key + key + key + key + key + key + key + key + key
overlaid_key += key + key + key + key + key + key + key + key + key + key + key + key + key + key
overlaid_key += key + key + key + key + key + key + key + key + key + key + key + key + key + key
overlaid_key += key + key + key + key + key + key + key + key + key + key + key + key + key + key
overlaid_key += key + key + key + key + key + key + key + key + key + key + key + key + key + key
encrypted_count = 0
for encrypted_letter in encrypted:
print("encrypted letter" + encrypted_letter)
print("keyoverlaid letter" + overlaid_key[encrypted_count:encrypted_count + 1])
encrypted_index = look_up_letter_index(encrypted_letter)
overlaidkey_index = look_up_letter_index(overlaid_key[encrypted_count:encrypted_count + 1])
encrypted_count = encrypted_count + 1
print(encrypted_index)
print(overlaidkey_index)
#loop through alphabet array
single_alphabet_index = 0
for single_alphabet in alphabet_array:
single_alphabet_letter_test = single_alphabet[overlaidkey_index:overlaidkey_index + 1]
if single_alphabet_letter_test == encrypted_letter:
print(single_alphabet_index)
print(alphabet[single_alphabet_index:single_alphabet_index + 1])
translated += alphabet[single_alphabet_index:single_alphabet_index + 1]
single_alphabet_index = single_alphabet_index + 1
print(translated)
return translated
def encrypt_vignere(message, key):
alphabet = "abcdefghijklmnopqrstuvwxyz".upper()
print(len(alphabet))
count = 0
alphabet_array = []
for letter in alphabet:
single_line = alphabet[count:26] + alphabet[0:count]
alphabet_array.append(single_line)
count = count + 1
print(alphabet_array)
print(look_up_letter_index("a"))
overlaid_key = key + key + key + key + key + key + key + key + key + key + key + key + key + key
overlaid_key += key + key + key + key + key + key + key + key + key + key + key + key + key + key
overlaid_key += key + key + key + key + key + key + key + key + key + key + key + key + key + key
overlaid_key += key + key + key + key + key + key + key + key + key + key + key + key + key + key
overlaid_key += key + key + key + key + key + key + key + key + key + key + key + key + key + key
overlaid_key += key + key + key + key + key + key + key + key + key + key + key + key + key + key
overlaid_key += key + key + key + key + key + key + key + key + key + key + key + key + key + key
overlaid_key += key + key + key + key + key + key + key + key + key + key + key + key + key + key
overlaid_key += key + key + key + key + key + key + key + key + key + key + key + key + key + key
overlaid_key += key + key + key + key + key + key + key + key + key + key + key + key + key + key
overlaid_key += key + key + key + key + key + key + key + key + key + key + key + key + key + key
message_count = 0
encrypted_message = ""
for message_letter in message:
print(message_letter)
overlaid_key_letter = overlaid_key[message_count:message_count + 1]
print(overlaid_key_letter)
message_letter_index = look_up_letter_index(message_letter)
overlaid_key_letter_index = look_up_letter_index(overlaid_key_letter)
translated_letter = alphabet_array[overlaid_key_letter_index][message_letter_index:message_letter_index + 1]
print(translated_letter)
encrypted_message += translated_letter
print("--")
message_count = message_count + 1
print(encrypted_message)
return encrypted_message
print(encrypt_vignere("MESSAGE", "SECRETKEY"))
print(decrypt_vignere("EIUJEZO", "SECRETKEY"))
While these functions are much longer, I think they’re much more readable than the previous set of functions. These versions generate a matrix of 26×26 alphabets; the first row is a to z, the second row is shifted 1 to the right (b to z then a), the third row is shifted 2 to the right (c to z then ab), etc. Then we overlay the secret key and use it along with the message/encrypted message to encrypt/decrypt by finding the appropriate entry in our matrix. Admittedly the code is a little ugly and could be cleaned up, but I thought it would be fun to share.