HW8: Caesar Ciphers

40 points; due Mon 4/28 @ 9am.

Goals

The goal of this assignment is to learn a bit about encryption and decryption, to do real work with functions, and to get more practice with loops, conditionals, and data representations. It is also designed to give you practice developing a well-structured large program.

Setup and Requirements

Create a directory named cipher that will contain your code for this project.

It is strongly recommended that you work on this assignment with a partner. Don't be shy; find somebody new to work with! If you'd like to work in a pair, please figure out who you'd like to work with via Piazza or email, and then email me once your pair is decided. Make sure to put both people's names at the top of each file. Write your code by pair-programming: code together, at the same computer, and switch off who's at the keyboard. Only one of you should ultimately submit your team's code via Moodle.

Part 1: A Simple Caesar Cipher

An important class of algorithms that some computer scientists concern themselves with are encryption and decryption techniques. How can text be encoded so that no one can read it apart from the desired recipient? This is critical for trade secrets and government messages, but even more critical for such common needs as secure web-based purchases, being able to send your password over the web without others listening in, and so on.

An old trick is the Caesar cipher. Pick a special value (called the “key”) between 1 and 25; then for each character in your message, shift each letter forward by the key, wrapping around the end of the alphabet. (Why wouldn't you want to use 26?) For example, if your original message (called the “plaintext”) is “helloyou”, and your key is 2, your encrypted message (the “ciphertext”) is “jgnnqaqw”. If you know the key, you can then later decrypt the ciphertext, and get back the original plaintext.

Supposedly, Julius Caesar used this technique to communicate with his generals. It is very easy to crack (you just have to try decrypting with all 25 possible keys, until you get something that makes sense), but it's still interesting to code up.

Program Description

The program you write will do the following:

Here is an example run of the finished program (user's input is in red and underlined):

> python simple-caesar.py
Encode, decode, or quit? encode
  What is your plaintext? follow the elk
  What shift do you want to use? 3
  Your ciphertext is: iroorz wkh hon
Encode, decode, or quit? alaska
  Sorry, I didn't understand your input.
Encode, decode, or quit? decode
  What is your ciphertext? iroorz wkh hon
  What shift do you want to use? 3
  Your ciphertext is: follow the elk
Encode, decode, or quit? quit
  Okay, bye!

This part of the assignment will walk you through writing this program. Then, in the second half of the assignment, you'll develop a more sophisticated version of it on your own.

Step 0: Starter Code

Create a Python file called simple-cipher.py. When you hand in this file, it must have at least the following five functions in it: main(), interactiveEncode(), interactiveDecode(), caesarEncode(), and caesarDecode(), defined in that order. The assignment guides you through filling these in.

Type out the following skeleton code in your file:

# Simple Caesar-cipher encoding and decoding, by YOUR_NAME_HERE.

def main():
    """An infinite-loop prompt for Caesar-cipher encoding and decoding."""
    # TODO: Replace this stand-in function body with code for the user prompt.
    print "  Main function is not yet implemented."

def interactiveEncode():
    """Gets user input to pass to the Caesar-cipher encoder."""
    # TODO: Get encoding input (plaintext and shift factor) from the user.
    print "  Encoding is not yet implemented."

def interactiveDecode():
    """Gets user input to pass to the Caesar-cipher decoder."""
    # TODO: Get decoding input (ciphertext and shift factor) from the user.
    print "  Decoding is not yet implemented."

def caesarEncode(plaintext, shift):
    """Returns the ciphertext obtained by transforming every letter in the
    plaintext by a Caesar cipher with the specified shift.  Non-letter
    characters remain unchanged in the ciphertext."""
    # TODO: Fill in the logic to encode the plaintext.
    print '    I don\'t yet know how to encode "%s".' % plaintext
    ciphertext = "[the correct ciphertext, of course]"
    return ciphertext

def caesarDecode(ciphertext, shift):
    """Returns the plaintext obtained by un-transforming every letter in the
    ciphertext by a Caesar cipher with the specified shift.  Non-letter
    characters remain unchanged."""
    # TODO: Fill in the logic to decode the ciphertext.
    print '    I don\'t yet know how to decode "%s".' % ciphertext
    plaintext = "[the correct plaintext, of course]"
    return plaintext

main()

There are a few important things to notice about this code, and the program that it embodies:

This code demonstrates a few very important software-development practices that you should keep in mind in your work from here on out:

  • Plan out the structure of your program in advance, as best as you can. Think through how to break down the one big problem you have (write the program) into a bunch of separate subproblems (write just a function to get input from the user, for example). Then make empty functions for each of these subproblems.
  • Functions should be separated into those that interact with the user (receiving typed input or printing output to the screen), those that do sophisticated computations, and those that tie together the other pieces.
  • Provide a docstring for every function that you write. In the docstring, specify the function's inputs (arguments) and output (return value).
  • Put all of the logic of your program in functions. The top-level code of your program (the stuff that isn't indented at all) should consist entirely of function definitions and basic commands to call a top-level function (usually called main()).
  • As you develop a sophisticated program, always try to keep it in a state where it runs (maybe incompletely) without producing errors. Add one feature at a time, always bringing your code back to a non-error-producing state before you try to add a new feature.
  • Mark unimplemented features with a “TODO” comment, and delete the comment once you've implemented the feature.

Step 1: The Main Function

The first feature we'll add to the program is the prompt that asks the user what to do. Fill in the main() function so that it makes calls to interactiveEncode() and interactiveDecode() in response to user input, as in the following example run:

> python simple-caesar.py
Encode, decode, or quit? encode
  Encoding is not yet implemented.
Encode, decode, or quit? alaska
  Sorry, I didn't understand your input.
Encode, decode, or quit? decode
  Decoding is not yet implemented.
Encode, decode, or quit? quit
  Okay, bye!

Get your program working in this way before moving on to the next step.

Step 2: Getting Input

Now modify interactiveEncode() and interactiveDecode() to make calls to caesarEncode() and caesarDecode(), respectively:

> python simple-caesar.py
Encode, decode, or quit? encode
  What is your plaintext? follow the elk
  What shift do you want to use? 3
    I don't yet know how to encode "follow the elk".
  Your ciphertext is: [the correct ciphertext, of course]
Encode, decode, or quit? quit
  Okay, bye!

Step 3: Encoding

Now modify caesarEncode() so that it encodes the given plaintext and returns the ciphertext. Note that this function should not print anything!

> python simple-caesar.py
Encode, decode, or quit? encode
  What is your plaintext? follow the elk
  What shift do you want to use? 3
  Your ciphertext is: iroorz wkh hon
Encode, decode, or quit? quit
  Okay, bye!

To do this step, you'll need the ord() and chr() functions (remember what they do?), as well as some clever loops and conditionals.

Non-letter characters should just be passed into the ciphertext unchanged. You can choose how to handle capital letters: you may either convert capitals to capitals, or just convert everything to lowercase if you'd like. Numbers are “non-letter characters”, so they should just pass through like anything else. (I know this makes your math secrets way less secure, but it's not worth the hassle for you to do that for this assignment.)

Step 4: Decoding

Finally, modify caesarDecode() so that it does the decoding. The body of this function should consist of only a single line of code after the docstring. There is no need to write complicated new logic; you can make just a single function call to get the correct plaintext, and you can return it directly without saving it to a variable first!

Congratulations! You've now written an encoder and decoder for the simple Caesar cipher, and wrapped them up in an interactive program! You'll turn in your simple-caesar.py file at the end of this assignment.

Part 2: The Double-Caesar Cipher

A slightly harder, but dramatically more effective variation of the Caesar cipher is called the “double-Caesar” cipher. It works just like the Caesar cipher, but each letter in your message gets shifted by a different amount. How much? It is based on another string of text, called the key. Each letter in the key tells you how many letters to advance: an “a” is 0, a “b” is 1, and so on.

For example, if your original message is “hello you” and your key is “dog”, the “d” means that you shift the first letter by 3, the “o” means you shift the second letter by 14, and the “g” means you shift the third letter by 6. You then repeat the pattern: the fourth letter of the plaintext (“hello you”) gets shifted by 3, the fifth letter gets shifted by 14, and so on. This produces the encrypted message "ksroc eri". Double-Caesar ciphers are actually quite hard to crack.

Just to make it clear, here's that same encoding again:

Original:      h e l l o   y o u
Repeated key:  d o g d o   g d o
Encrypted:     k s r o c   e r i

Implementing It in Code

Create a Python file called double-caesar.py with the same five methods as the last program (you might want to just copy over the skeleton code from Part 1, but remember to update the docstrings so they reflect your new task!). Here is an example session with the program you should produce:

> python double-caesar.py
Encode, decode, or quit? encode
  What is your plaintext? hello you
  What is your key? dog
  Your ciphertext is: ksroc eri
Encode, decode, or quit? decode
  What is your ciphertext? ksroc eri
  What is your key? dog
  Your plaintext is: hello you
Encode, decode, or quit? quit
  Okay, bye!

Go through the whole development process again, following the software-development practices outlined above. Make sure your code is always in a working state. Implement one feature at a time, and be sure it's working correctly before you move on to the next thing.

(For this program, the one-line restriction on caesarDecode() no longer applies. Keep in mind, though, that part of the point of functions is to avoid repeating the same code twice in the same program, so you should still be trying to make efficient use of logic that you've already implemented elsewhere in your code. If you'd like to try, with the assistance of a new helper function it is still possible to write caesarDecode() in a single line.)

Once you've got the double-Caesar program working, there's just one last step...

Part 3: Encrypting Spaces

All the parts up until this point were worth a total of 39 points. For the full 40, create one more program (in the file double-caesar-spaces.py) that is a variation of your double-Caesar program that also encrypts and decrypts spaces between words (instead of just leaving them unchanged). Spaces should be encoded just like any regular letter; this way, someone looking at the encrypted message can’t tell how many letters were in each word in the plain text.

To do this, the simplest way is to change your setup so that you think of your alphabet as having 27 letters: the original 26 letters, plus a space at the end. So a space character in the plaintext, if shifted by 3, should result in a “c” in the ciphertext; the letter “x” in the plaintext, if shifted by 3, should result in a space character in the ciphertext. So your encoded message may have spaces in it, but they won’t correspond to positions where spaces were in the plain text! How diabolical!

Grading and Submission

Put your programs (simple-caeser.py, double-caesar.py, and double-caesar-spaces.py) in a zip file called hw8.zip. Remember, the command to do this is:

zip hw8.zip simple-caesar.py double-caesar.py double-caesar-spaces.py

Then submit your zip file via Moodle.