I'll Take Some Vigenère With My Caesar

by snooze

To brush up on by (extremely minimal) crypto skills, I recently began reading Serious Cryptography, which made me want to implement some of the concepts I was learning.  For those of you who are unaware of what a Caesar cipher is, it is a pretty simple concept.  You basically "encrypt" a message by rotating each letter of an input plaintext by three characters each time, and wrap around to the beginning of the alphabet if your "plus three" rotation ends after "Z."

For example, the letter A becomes D, and Z becomes C, so on and so forth.  You would see the following in a Caesar encrypted message:

If you are familiar with this concept, you might also know it to be referred to as "ROT-3" encoding, where the ROT stands for Rotation and the number 3 refers to the amount of characters.  Note that since there are 26 characters in the alphabet, you can expand on the Caesar cipher by changing that particular variable.

What I wanted to discuss next is what happens when we actually use a "key" to determine how much each letter gets rotated by since this is a bit more interesting.

Pretend we had a key of HAK for the same TESTZ string above.  H, A, and K give us ROT-7, ROT-0, and ROT-10, respectively.

Since TESTZ has more characters than our key, we simply repeat the key the length of TESTZ, which would be HAKHA.

That results in the following output:

This is an example of the Vigenère cipher which, while more "secure" than the Caesar/ROT-3 cipher that came before it, is still comically insecure by today's standards and should not be used for anything remotely important.

That said, I wanted to see if I could write an algorithm that accepts a key as input from a user, then encrypt a plaintext using said key, providing the corresponding ciphertexts as output.

Grabbing user input and validating the key as alphabetical is easy enough:

import string
alpha = string.ascii_lowercase

plainText = input("Enter your plaintext to be encrypted: ")
userKey = input("Enter your alphabetical key; exits on invalid character: ").lower()
cipherText = ''

# Check validity of key; for demonstration purposes I only accept alphabet characters

for char in userKey:
  if char.lower() not in alpha:
    print("Invalid key; quitting.")
    quit()
  else:
    rot = alpha.index(char)
    print(rot)

If you run the above, you see that we get the "ROT" numbers as listed (7, 0, 10) previously.

Now the first dilemma comes up; we need to repeat the key HAK once it runs out of characters due to our plaintext being longer than the key itself.

After some sleuthing, it appears itertools.cycle is a great answer for this problem:

from itertools import cycle
cyc = cycle(userKey)

for char, rot in zip(plainText, cyc):
  print(char, alpha.index(rot))

This gives us the following output and provides the logic we are looking for:

Enter your plaintext to be encrypted: testz
Enter your alphabetical key; exits on invalid character: hak
7
0
10
t 7
e 0
s 10
t 7
z 0

Now it is time to utilize a ROT encoding algorithm which, as mentioned, I wrote previously.  That said, I just added it to my code as a function called rotateChar and made some modifications to handle non-alphabetical characters and varying letter case.

The full somewhat commented code:

vigcipher.py:

# See blog post at https://snoozesecurity.blogspot.com/2020/12/ill-take-some-vigenere-with-my-caesar.html

from itertools import cycle
import string
alpha = string.ascii_lowercase

plainText = input("Enter your plaintext to be encrypted: ")
userKey = input("Enter your alphabetical key; exits on invalid character: ").lower()
cipherText = ''
cycKey = cycle(userKey)

# Caesar/ROT Function

def rotateChar(s: str, rotate: int):
  out = ''
  boolUpper = s.isupper()
  s = s.lower()
  if s not in alpha:
    out = s
  elif s in alpha and alpha.index(s) + rotate > 25:
    if boolUpper:
      out = alpha[((alpha.index(s) + rotate) - 25) - 1].upper()
    else:
      out = alpha[((alpha.index(s) + rotate) - 25) - 1]
  else:
    if boolUpper:
      out = alpha[alpha.index(s) + rotate].upper()
    else:
      out = alpha[alpha.index(s) + rotate]
  return out

# Check validity of key; for demonstration purposes I only accept alphabet characters

for char in userKey:
  if char.lower() not in alpha:
    print("Invalid key; quitting.")
    quit()

# Create nested list(s) with the proper ROT number for each string in the plaintext

refList = []

for char, rot in zip([char for char in plainText if char.lower() in alpha], cycKey):
  if char.lower() in alpha:
    refList.append([char, alpha.index(rot)])

# Iterate through original plaintext and rotate when a legal character is at index 0 of refList then pop index 0.

for char in plainText:
  if refList and char == refList[0][0]:
    cipherText += rotateChar(char, refList[0][1])
    refList.pop(0)
  else:
    cipherText += char

print("Ciphertext:", cipherText)

You can save the above code to a new file and run it with: python3 /path/to/vigcipher.py

My sample output below:

$ python3 ./vigcipher.py
Enter your plaintext to be encrypted: Testing our CIPHER!
Enter your alphabetical key; exits on invalid character: secret
Ciphertext: Liukmgy swi GBHLGI!

Overall, this was a fun excercise and I look forward to implementing more cryptographic algorithms in the future!

Code: vigcipher.py

Return to $2600 Index