lab_cipher

Capricious Ciphers

Due: Feb 13, 23:59 PM

Learning Objectives

  • Experience manipulating lists items and lists indices
  • Write open-ended code with multiple valid algorithmic approaches
  • Learn fun trivia about cryptography

Submission Instructions

Using the Prairielearn workspace, test and save your solution to the following exercises. (You are welcome to download and work on the files locally but they must be re-uploaded or copied over to the workspace for submission).

Assignment Description

In this lab, you will be tasked with using a combination of lists and strings to implement two different text transformations – the Caesar cipher and the Vigenere cipher. Both are substitution ciphers, where each character in the text is replaced by some other character but the character order and the total count of characters remains the same. Accordingly, coding up both approaches relies more on a clear understanding of arrays (Python lists), list offsets, and/or text to int conversions.

To help simplify the problem, you may assume that all input messages for both ciphers contain only lowercase English letters and the space character (“ “). All encoders and decoders should be able to handle these 27 characters.

Part 1: Caesar Cipher

caesarEncode()

str caesarEncode(str inString, int offset))
# INPUT: 
# inString, a string containing the message being encrypted
# offset, an int defining the shift the cipher should use
# OUTPUT:
# a string containing the encrypted version of inString.
# NOTE: 
# The offset can either be left shifted (if negative) or right shifted (if positive)
# Ex: offset = -1 , B -> A
# Ex: offset = 3 ,  A -> D

The Caesar cipher, named after Julius Caesar (who used it in his letters), is a very straightforward cipher: each letter in a plaintext is replaced by a different letter a fixed offset away. In the contexts of this lab, the alphabet will include only lowercase letters as well as the space character “ “, as written below:

['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', ' ']

For edge cases where the offset would either look for a letter before ‘a’ or after “ “, you should wrap around. For example, ‘b’ with an offset of -5 would be ‘x’.

To provide some additional examples to help drive the point home:

caesarCipher("x", 4) -> "a" (because the shifted characters are y,z," ", and the fourth character is "a")
caesarCipher("go go gadget", 2) -> "iqbiqbicfigv" (Note that space is a character like any other)
caesarCipher("d",-6) -> "y" (shifted characters are c, d, a, " ", z, and the sixth character is "y")

caesarDecode()

str caesarDecode(str inString, int offset))
# INPUT: 
# inString, a string containing the message being decrypted
# offset, an int defining the shift the cipher used
# OUTPUT:
# a string containing the decrypted version of inString.
# NOTE: 
# The offset can either be left shifted (if negative) or right shifted (if positive)
# HINT: To figure out decryption, write out a few small examples of text and their encrpytions

When writing caesarDecode, be aware that the offset given is the offset used by the encoder. Think about how you might reverse this shift, given the shifted character and the offset used.

Part 2: Vigenere Cipher

The Vigenere cipher was first described Giovan Bellaso in 1553 and – while easy to understand and implement – was not broken for over three centuries. Even now, the cipher remains a valid “unbreakable” cryptosystem through the use of a one-time pad (giving each character in the message its own independently random shift). What makes this so interesting is the basis for this cipher is a very minor modification of the Caesar cipher – instead of having a single consistent offset, a second string (the key) is used to identify different offsets for different characters.

vigenereEncode()

str vigenereEncode(str inString, str key))
# INPUT: 
# inString, a string containing the message being encrypted
# offset, a string defining the pattern of shifts the cipher should use
# OUTPUT:
# a string containing the encrypted version of inString.
# NOTE:
# If the key is not sufficiently long to encode the message, repeat the key

To implement this cipher, it is easiest to visualize a 2D grid called the Vigenere square. Your solution should be based on a 27 letter alphabet with the space character appended to the end. That said, a simpler example to demonstrate the algorithm is shown below:

  A B C D
A A B C D
B B C D A
C C D A B
D D A B C

Ignoring the first row and the first letter in each row, the square is simply the full alphabet shifted left once repeatedly. To actually use this square as an encryption, a row is selected based on the first character in the key. The column is then selected based on the character in the plaintext being encrypted and the encrypted version of the character is the character matching the row and column. To give a concrete example for the message "DAB" and the encryption key: "DC":

  1. The encryption key begins with character “D” and our message also begins with character “D”. We look up row “D” and column “D” in the Vigenere square and find the character “C” – this is our encrypted character.
  2. The key’s next character is “C” and our message is “A”. We look up row “A” and column “C” and return the encrypted character “C”.
  3. The key length is smaller than the message length and we have to repeat the key to compensate. So our row is “D” (the first character in our encryption key) and our column is “B”. The encrypted character is “A”

So our encryped message is "CCA".

Note: Your solution should work regardless of whether the length of the key is smaller, the same, or larger than the text.

Extra Credit: vigenereDecode()

str vigenereDecode(str inString, str key))
# INPUT: 
# inString, a string containing the message being decrypted
# key, a string defining the pattern of shifts the cipher used
# OUTPUT:
# a string containing the decrypted version of inString.
# NOTE:
# If the key is not sufficiently long to encode the message, repeat the key

Decryption operates using the same square but with different logic. The first step is still to look up the relevant row based on the key but now instead of looking up the appropriate column label, the correct column is instead found by looking inside the square to find the encrypted character. Then the original character can be found based on the label of the column. To reverse our earlier example:

  1. The encryption key begins with character “D” and our encrypted message begins with character “C”. We look up row “D” and look for “C” in the row itself. We find that “C” is our fourth and final column and has the character label of “D”, which is our original character.
  2. The key’s next character is “C” and our message is “C”. We look up row “A” and look for the letter “C”, which we find in the column labeled “C”, which is our original character.
  3. The key is not full length and we have to repeat the key to compensate. So our row is “D” (the first character) we are looking for “A” in the row. We find the “A” in the second column and so our original character is “B”.

So our original message is "DAB".