The Caesar Shift Cypher in Python
The Caesar Shift Cypher was named after Julius Caesar and is the simplest method of encypherment possible. It consists of shifting letters along by one or more places, so for example if you use a shift of 1 then A becomes B, B becomes C etc.. To decypher the message you just shift letters in the opposite direction. Clearly it lacks sophistication and can easily be cracked, either by trial and error or, if you have a reasonable length of encrypted text, using frequency analysis.
As a little programming exercise I will code the Caesar Shift Cypher in Python, and in a future post will break it with frequency analysis. You can clone/download the code from the Github repository which contains these two files:
caesarshiftcypher.py
main.py
This is caesarshiftcypher.py.
caesarshiftcypher.py
def encypher(plaintext, shift):
"""
Encypher given text with given shift.
Only letters are encyphered, all other characters are ignored.
Encyphered text is all upper case.
Exception raised if shift is not between 2 and 25.
"""
if shift < 2 or shift > 25:
raise ValueError("shift must be between 2 and 25")
cyphertext = []
for letter in plaintext:
if letter.isalpha():
encyphered_letter_ord = ord(letter.upper()) + shift
if encyphered_letter_ord > 90:
encyphered_letter_ord -= 26
cyphertext.append(chr(encyphered_letter_ord))
return "".join(cyphertext)
def decypher(cyphertext, shift):
"""
Decyphers given text with given shift.
Decyphered text is converted to lower case.
Exception raised if shift is not between 2 and 25.
"""
if shift < 2 or shift > 25:
raise ValueError("shift must be between 2 and 25")
plaintext = []
for letter in cyphertext:
decyphered_letter_ord = ord(letter) - shift
if decyphered_letter_ord < 65:
decyphered_letter_ord += 26
plaintext.append(chr(decyphered_letter_ord).lower())
return "".join(plaintext)
The encypher function takes a piece of text to encypher and the number of letters to shift it by. In principle the shift could be any number (except 0 and 26, which get you nowhere or back where you started respectively) but negative shifts or shifts more than 26 only cycle round to shifts which are equivalent to between 2 and 25. I have therefore restricted shift to this range and anything else raises a ValueError exception.
Having got past the range check we create an empty list to hold the characters of the encyphered text. What we need as an end result is a string but building a potentially lengthy string one character at a time using concatenation is very inefficient. Strings are immutable so concatenation actually involves creating a whole new string each time. To get round this inefficiency I am using a list and appending letters to it; when we've finished we can create a string from it using the join method.
Now let's iterate the plaintext, firstly checking if each character is an actual letter, all other characters being ignored. If so the next line actually does three things: converts the letter to upper case with upper, retrieves the ASCII code with ord, and then adds the shift. The letter is now almost encrypted but we need to check if it has run off the end of the alphabet, ie is more than 90 (Z). If so we subtract 26 to roll round to the beginning of the alphabet. Finally the fully encrypted letter is appended to the list.
At the end of the loop we just return an empty string with the contents of the list added with the join method as mentioned above.
The decypher function basically undoes the above process but is simpler because we don't need to check for non-letters. In a real-world situation you would probably want to allow for any character, either ignoring non-letters or raising an exception. However, as this is just a "toy" implementation I have assumed the decypher function is only going to be given a string with upper case letters, as generated by the encypher function.
We still check that shift is within range, and then iterate the letters of the encyphered text, picking up and shifting each letter's ASCII code. This is rolled round if necessary, and then the decyphered letters are added to the list. They are converted to lower case, following the convention that encrypted text is upper case and plain text or decrypted text is lower case. Finally we just use join again to return the decyphered text.
Now let's move on to main.py.
main.py
import caesarshiftcypher
def main():
"""
Creates and prints a piece of text,
encyphers it and prints cypher text,
then decyphers that and prints result.
"""
print("-----------------------")
print("| codedrome.com |")
print("| Caesar Shift Cypher |")
print("-----------------------\n")
plaintext = "The first ray of light which illumines the gloom, and converts into a dazzling brilliancy that obscurity in which the earlier history of the public career of the immortal Pickwick would appear to be involved, is derived from the perusal of the following entry in the Transactions of the Pickwick Club, which the editor of these papers feels the highest pleasure in laying before his readers, as a proof of the careful attention, indefatigable assiduity, and nice discrimination, with which his search among the multifarious documents confided to him has been conducted."
print(plaintext)
print("")
try:
cyphertext = caesarshiftcypher.encypher(plaintext, 3)
print(cyphertext)
print("")
decypheredtext = caesarshiftcypher.decypher(cyphertext, 3)
print(decypheredtext)
except ValueError as e:
print(e)
main()
Firstly we import caesarshiftcypher, and then within main create a string to encypher. The string is the first paragraph of The Pickwick Papers by Charles Dickens. After printing it we throw it at the encypher function and print the result. We then pass that result to decypher (obviously using the same shift) and print out the decyphered text. Note that the two function calls are made in a try block; you might want to try out an invalid shift to check the validation is working.
That's the code finished so run it with this command:
python3 main.py
The output is
Stay tuned for the frequency analysis decryption article.