Project 5

Caesar Cipher

The Project

In this project, you’ll write a program that takes a plaintext sentence like THE WEATHER IS NICE TODAY and uses a key to convert it to a ciphertext sentence like GUR JRNGURE VF AVPR GBQNL. The program will also convert ciphertext back to plaintext, when given the same key.

All of the words and pictures in the next 4 sections have been copy-pasted directly from Al Sweigart's excellent book "Invent Your Own Computer Games With Python". I've made a few minor edits. This is OK because Al has graciously made his book available under a Creative Commons license. Thanks, Al!

Encryption

The science of writing secret codes is called cryptography. For thousands of years cryptography has made secret messages that only the sender and recipient could read, even if someone captured the messenger and read the coded message. A secret code system is called a cipher. The cipher used by the program you’re about to build is called the Caesar cipher.

In cryptography, we call the message that we want to be secret the plaintext. The plaintext could look like this:

HELLO THERE! THE KEYS TO THE HOUSE ARE HIDDEN UNDER THE FLOWER POT.

Converting the plaintext into the encoded message is called encrypting the plaintext. The plaintext is encrypted into the ciphertext. The ciphertext looks like random letters, and we cannot understand what the original plaintext was just by looking at the ciphertext. Here is the previous example encrypted into ciphertext:

YVCCF KYVIV! KYV BVPJ KF KYV YFLJV RIV YZUUVE LEUVI KYV WCFNVI GFK.

But if you know about the cipher used to encrypt the message, you can decrypt the ciphertext back to the plaintext. (Decryption is the opposite of encryption.)

Many ciphers also use keys. Keys are secret values that let you decrypt ciphertext that was encrypted using a specific cipher. Think of the cipher as being like a door lock. You can only unlock it with a particular key.


The Caesar Cipher

The key for the Caesar Cipher will be a number from 1 to 26. Unless you know the key (that is, know the number used to encrypt the message), you won’t be able to decrypt the secret code.

The Caesar Cipher was one of the earliest ciphers ever invented. In this cipher, you encrypt a message by taking each letter in the message (in cryptography, these letters are called symbols because they can be letters, numbers, or any other sign) and replacing it with a “shifted” letter. If you shift the letter A by one space, you get the letter B. If you shift the letter A by two spaces, you get the letter C.

Take a look at picture at the right, where some letters are shifted over by three spaces.

To get each shifted letter, draw out a row of boxes with each letter of the alphabet. Then draw a second row of boxes under it, but start a certain number of spaces over. After the letters at the end, wrap around back to the start of the boxes.

Here’s an example with all the letters of the alphabet shifted by three spaces:


The Key

The number of spaces you shift is the KEY in the Caesar Cipher. The example above shows the letter translations for the key 3. We will keep any non-letter characters the same.

To encrypt the plaintext “HOWDY” with a key of 3, go from the top boxes to the bottom boxes:

  • The letter “H” becomes “K”.
  • The letter “O” becomes “R”.
  • The letter “W” becomes “Z”.
  • The letter “D” becomes “G”.
  • The letter “Y” becomes “B”.

The plaintext HOWDY with key 3 becomes the ciphertext KRZGB.


To decrypt “KRZGB” with the key 3, we go from the bottom boxes back to the top:

  • The letter “K” becomes “H”.
  • The letter “R” becomes “O”.
  • The letter “Z” becomes “W”.
  • The letter “G” becomes “D”.
  • The letter “B” becomes “Y”.

The ciphertext KRZGB with key 3 becomes the plaintext HOWDY.

ASCII, and Using Numbers for Letters

How do we implement this shifting of the letters as code? We can do this by representing each letter as a number called an ordinal, and then adding or subtracting from this number to form a new ordinal (and a new letter). ASCII (pronounced “ask-ee” and stands for American Standard Code for Information Interchange) is a code that connects each character to a number between 32 and 126.

The capital letters “A” through “Z” have the ASCII numbers 65 through 90. The lowercase letters “a” through “z” have the ASCII numbers 97 through 122. The numeric digits “0” through “9” have the ASCII numbers 48 through 57.

So if you wanted to shift “A” by three spaces, you would do the following:

  • Convert “A” to an ordinal (65).
  • Add 3 to 65, to get 68.
  • Convert the ordinal 68 back to a letter (“D”).



That's the end of the copy-pasted section of Al's book. Everything after this box was written by JR like usual.



Converting between letters and numbers


Letters A-Z Don’t Have ASCII Codes 1-26

This might feel weird at first, but you’ll get used to it. Most of the ASCII codes between 0 and 31 are junk left over from the days when computers were giant room-sized machines controlled by jury-rigged typewriters.

Here’s the full ASCII table from asciitable.com - don’t worry, you don’t need to memorize this or anything, but it's a good resource as you try to figure out how to get the computer to convert messages correctly. I’ve highlighted the section of the table that concerns the uppercase letters A-Z.

You only care about the “Dec” (decimal) and “Char” (character) columns in this table.

So for example,

  • uppercase J (Char column) has the ASCII code 74 (Dec column), and
  • lowercase j (Char column) has the ASCII code 106 (Dec column).

converting using ord & chr

Python comes with the ord() function, which lets you convert a letter to its corresponding ordinal number:

print(ord('J'))
Output:
  74

To go from an ordinal number back to a letter, you can use the chr() function.

print(chr(74))
Output:
  J

Let’s try shifting the letter H over by 3, like we did in the "Howdy" example above:

print(chr(ord('H') + 3))
Output:
  K

It turns into K, just like we expected! ord() and chr() are going to be your best friends while you’re working on this project.

The code snippet above is going to be very, very, very useful in this project. Mess around with it in the IDLE console (change the 3 to a 5 or a -7, change the H to some other uppercase letter) to make sure you understand it.


String Manipulation Tip

You’ll probably want to use a for loop at some point in your program - as a reminder, here’s how you can use a for loop to do something to each letter of a string:

some_letters = "ABCDEFG"
lowercased_letters = ""
for letter in some_letters:
    lowercased_letters = lowercased_letters + chr(ord(letter) + 32)
print(lowercased_letters)
Output:
  abcdefg

You will want to do something sort of similar when you’re building up your program’s ciphertext variable.

Do not just copy that snippet into your program. It won’t do what you want. Carefully read it, think about it, and try to figure out how you might use something like it in your program.



Requirements


Start with Only Uppercase Letters

If your program is given some plaintext that includes numbers, or lowercase letters, or punctuation marks like ! or . or $ or anything that’s not a letter from A to Z, it should leave that character unmodified. For example, if given a plaintext string of HOWDY! Hello. and a key of 5, your program should output the ciphertext MTBID! Mello.


Write Two Functions: Encrypt & decrypt

encrypt

The function encrypt(text, key) takes a string and an integer key and returns a string that’s text encrypted using the Caesar cipher with the key key.

When you call encrypt('ABC', 2), you should get 'CDE' back.


decrypt

The function decrypt(text, key) is the opposite - it takes a string and an integer key and returns a string that’s text decrypted using the Caesar cipher with the key key.

When you call decrypt('CDE', 2), you should get 'ABC' back.


NOTES:

  • A good way to test that you’ve written your functions correctly is to check to see if e.g. decrypt(encrypt('PIZZA', 10), 10) is equal to PIZZA.
  • Once you’ve written encrypt(text, key), it should be possible to implement decrypt(text, key) with a single line of code inside the decrypt function. If you find yourself copy-pasting all of the code from encrypt(text, key) into decrypt(text, key), you’re doing something wrong. Take a step back and try to find a simpler way.


After That, Write A `main` Function

Remember how there’s always a main() function, and if __name__ == '__main__': bit at the end of all our project files?

main

Write a main() function that prompts the user for some input as shown below, and calls encrypt() or decrypt() with the stuff the user gives you. Remember that you can write code like input("Hi there, what's your name?") to get a string of text from the user.

Do you wish to encrypt or decrypt a message?  encrypt                                                                         Enter your message:  HOWDY! Hello.                                                                   Enter the key (1-26):  5   

Your encrypted message is:  MTBID! Mello                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            


Note that you’re only calling one of encrypt() or decrypt(). Use the user’s input to decide which of those two functions to call. Print out the return value of the function.


if __name__ == '__main__'

Copy-paste these two lines and add them to the bottom of your program:

if __name__ == '__main__':
    main()

One of these days I’ll explain why you need to do that :)


Nitty Gritty

When testing your program, I'll only give it the expected inputs. If the user inputs an invalid mode (i.e. something that’s not “encrypt” or “decrypt”), or an invalid key (i.e. something that’s not a number between 0 and 26), it’s OK if your program crashes.



Other Features

Add these features to your program after you get basic encryption and decryption working.


Lowercase Letters

If you haven't commited your code to GitHub yet, do it now. You might mess up your code in this next part and it will be nice to be able to look back at it.


Make your program work with uppercase and lowercase characters, like this:

Do you wish to encrypt or decrypt a message?  encrypt                                                                         Enter your message: HOWDY!  Hello.                                                                   Enter the key (1-26):  5                                                                               Your translated text is:  MTbId! Mjqqt.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         


When I did this, I ended up switching away from ord() and chr(), and instead made a string like

transformable_characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'

and had my code do stuff based on the position of each letter of the message in my transformable_characters string.

Here’s how you can find the first position of a letter in a string in Python:

# 'l' is in 'Hello', so this evaluates to the number 2:
'Hello'.find('l')
Output:
  2
# 'z' isn't in 'Hello', so this evalutes to -1, which is
# Python's way of saying "I looked for this but couldn't find it!"
'Hello'.find('z')
Output:
  -1


Brute Force

Add a brute_force function that lets you try to decrypt a message even if you don’t know the right key for it (notice the correct translation on Line 5).

For this mode, the human user will have to look through the printed list to determine the correct translation.


Enter your message to brute force:  MTbId! Mjqqt.                                                                   Your translated text is: 
1 LSaHc! Lipps.                                                                 
2 KRZGb! Khoor.          
3 JQYFa! Jgnnq.                                                                 
4 IPXEZ! Ifmmp.                                                                 
5 HOWDY! Hello.                                                                 
6 GNVCX! Gdkkn.                                                                 
7 FMUBW! Fcjjm.                                                                 
8 ELTAV! Ebiil.                                                                 
9 DKSzU! Dahhk.                                                                 
10 CJRyT! CZggj.                                                                
11 BIQxS! BYffi.                                                                
12 AHPwR! AXeeh.                                                                
13 zGOvQ! zWddg.                                                                
14 yFNuP! yVccf.                                                                
15 xEMtO! xUbbe.                                                                
16 wDLsN! wTaad.                                                                
17 vCKrM! vSZZc.                                                                
18 uBJqL! uRYYb.                                                                
19 tAIpK! tQXXa.                                                                
20 szHoJ! sPWWZ.                                                                
21 ryGnI! rOVVY.                                                                
22 qxFmH! qNUUX.                                                                
23 pwElG! pMTTW.                                                                
24 ovDkF! oLSSV.                                                                
25 nuCjE! nKRRU.                                                                                


Smart Brute Force

This is my favorite: enhance your program’s “brute force” mode so that it can automatically detect the correct key.


Do you wish to encrypt, decrypt, brute force, or smart-brute-force a message? smart                                                                           Enter your message:  MTbId! Mjqqt.                                                                   Your key and translated text is:  5: HOWDY! Hello.                                                                

Do you wish to encrypt, decrypt, brute force, or smart-brute-force a message? smart                                                                           Enter your message:  XemTo! XuBBE.                                                                   Your key and translated text is:  16: HOWDY! Hello.                                                                                      


You can do this however you want. Be creative! My solution involved using this text file, which is a list of all of the English words in the 1934 edition of Webster’s Second International Dictionary.

If you’d like to figure out how to open a text file in Python and get all the lines out of it, Google around until you find a solution - you can always ask me for help if you get stuck, but you can get pretty far by just Googling stuff!


Submitting your project

Before submitting your project, review the rubric and make sure to test your program a bunch of times to make sure it works!


Your CODE should follow the style guidelines. The part about descriptive names is important! For instance:

  • n is a bad name, username is a good one.
  • ns is a bad name, number_of_symbols is a good one.


On the first line of that file, write a comment with your name(s) on it, like this:

# Tamara O'Malley


On Google Classroom, submit your program in a file called caesar_cipher_your name_partner name.py. For instance, I might submit a file called caesar_cipher_Tamara_JR.py.

The projects in this class were created by JR Heard, a TEALS volunteer at Madison, 2017-2019. His version of this project lives at https://blog.jrheard.com/python/password-checker .