CIT 590 Assignment 3: Playfair Cipher
Spring 2013, David Matuszek

Purposes of this assignment:

General idea of the assignment:

You are probably familiar with the idea of "secret codes," such as the cryptograms published in some newspapers. A cipher is a more secure way of making secret messages. In this assignment you will write a program to encipher and decipher secret messages according to a modified version of the Playfair cipher. (For more about the Playfair cipher, see the Wikipedia article.)

The standard Playfair cipher encodes only 25 letters (excluding 'J'), and omits all spacing, punctuation, and capitalization, so that a decoded message lookssomthinglikethis, but it's easy for a person to construct the cipher matrix. Our version will require a computer, or will require the human to memorize or look up the ASCII sequence of characters, but decoding will be almost perfect.

Name your files playfair.py and playfair_test.py. You may have additional Python files, if you wish.

The general algorithms

Here's how to construct a modified Playfair cipher.

  1. Choose a keyword or key phrase, for example, "Barack H. Obama" (not including the quotes)
  2. If a character occurs more than once in the keyword, keep only the first occurrence. (So"Barack H. Obama"becomes"Barck H.Obm".)
  3. Put the characters of the key phrase into a 10 by 10 matrix (implement this as a list of lists).
    The matrix you are working with The list representation of the matrix
    B a r c k space H . O b
    m                           
                                 
                                 
                                 
                                 
                                 
                                 
                                 
                                 
    [['B', 'a', 'r', 'c', 'k', ' ', 'H', '.', 'O', 'b'],
     ['m', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
     [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
     [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
     [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
     [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
     [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
     [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
     [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
     [' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ']]
  4. Fill in the rest of the matrix with the unused ASCII characters null, tab, line feed, vertical tab, carriage return, and the characters chr(32) to chr(127).
    The matrix you are working with The list representation of the matrix
    B a r c k space H . O b
    m NUL TAB LF VT CR ! " # $
    % & ' ( ) * + , - /
    0 1 2 3 4 5 6 7 8 9
    : ; < = > ? @ A C D
    E F G I J K L M N P
    Q R S T U V W X Y Z
    [ \ ] ^ _ ` d e f g
    h i j l n o p q s t
    u v w x y z { | } ~
    [[ 'B', 'a', 'r', 'c', 'k', ' ', 'H', '.', 'O', 'b'],
     [ 'm', NUL, TAB,  LF,  VT,  CR, '!', '"', '#', '$'],
     [ '%', '&', ''', '(', ')', '*', '+', ',', '-', '/'],
     [ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
     [ ':', ';', '<', '=', '>', '?', '@', 'A', 'C', 'D'],
     [ 'E', 'F', 'G', 'I', 'J', 'K', 'L', 'M', 'N', 'P'],
     [ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'],
     [ '[', '\', ']', '^', '_', '`', 'd', 'e', 'f', 'g'],
     [ 'h', 'i', 'j', 'l', 'n', 'o', 'p', 'q', 's', 't'],
     [ 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~']]

Programming notes:

Here's how to encode a message, using the matrix:

  1. Take the next two characters of the message. We can't let them be both the same, so:
    1. If only one character remains, use NUL as the second character.
    2. If the two characters are the same, use NUL instead of the second character. (The second character will be the first character of the next group of two.)
    3. If the two characters are both NUL, skip one of them (and use the next character along as the second letter) This is highly unlikely to happen, but we should allow for it.
  2. If the two characters are in the same column, encode each one with the character below it, wrapping around if necessarly. (That is, if a character is in the bottom row, encode it with one in the top row.) For example, Bu become mB, while ab becomes rB.
  3. If the two characters are in the same row, encode each one with the character to its right, wrapping around if necessarly. (That is, if a character is in the rightmost column, encode it with one in the leftmost column.) For example, ht become ih, while av becomes NULa.
  4. If the two characters are in different rows and different columns, they form a rectangle. Replace each character with the character in the same row but in the other corner of the rectangle. For example, as becomes Oi.
  5. Save the result on a file as a string of characters. Just return the result from your encode function.

Example:

  Programming in Python is fun!!                 # message to be enciphered
  Pr og ra mNUL   mi   ng spacei nspace Py th on spacei sspace fu n!  !NUL   # characters two at a time
  Gb t` cr NULTAB NULh t_ ao     ok     J~ hi po ao     oO     [} pVT "TAB   # encoded two at a time
  Gbt`crNULTABNULht_aookJ~hipoaooO[}pVT"TAB      # enciphered message

Here's how to decode a message, using the matrix:

Example:

  Gbt`crNULTABNULht_aookJ~hipoaooO[}pVT"TAB    # enciphered message
  Gb t` cr NULTAB NULh t_ ao     ok     J~ hi po ao     oO     [} pVT "TAB   # pairs of characters 
  Pr og ra mNUL   mi   ng spacei nspace Py th on spacei sspace fu n!  !NUL   # decoded two at a time
Programming in Python is fun!! # deciphered message, after removing NULs

Your assignment:

Using Test-Driven Design, create a program named playfair.py. It should have at least these two methods:

def encode(plainTextMessage, secretPhrase)
Encodes a plainTextMessage, using the secretPhrase. Both the message and the secret phrase must be composed of valid ASCII characters (chr(32) to chr(126), plus LF, etc.).
def decode(encodedMessage, secretPhrase)
Decodes an encodedMessage, using the secretPhrase. Both the encoded message and the secret phrase must be composed of valid ASCII characters, and the encoded message should contain an even number of characters. When decoding, all occurences of the special character NUL should be discarded.
 

A main method is not required (the above functions can be called directly from the REPL), but you can write one if you wish; use the same if __name__ trick as in the other assignments.

Every method you write should have one or more unit tests, and none of them should do any I/O. [Possible exception: A main method is not required (the above functions can be called directly from the REPL), but you can write one if you wish; use the same if __name__ trick as in the other assignments.]

I have two "top level" tests for you; these will be the last tests you will be able to pass, since they depend on everything else working perfectly.

import playfair
import unittest

class TestPlayfair(unittest.TestCase):

   def testEncode(self):
       message1 = 'Programming in Python is fun!!'
       message2 = 'Amazingly few discotheques provide jukeboxes.'

       secretPhrase = 'Barack H. Obama'
       self.assertEquals('Gbt`cr\x00\t\x00ht_aookJ~hipoaooO[}p\x0b"\t',
                         playfair.encode(message1, secretPhrase))
       self.assertEquals(':" vjo^tzkgfzr\\plOphq[h|fqHo javefroyBg.lzfqa"',
                         playfair.encode(message2, secretPhrase))

       secretPhrase = "George Herbert Walker Bush"
       self.assertEquals("MHr GB|&njftszitN{H\ttcsz$sy#c'&k",
                         playfair.encode(message1, secretPhrase))
       self.assertEquals('Efsqjpeuzg`gzofjkirWlHya lWigrz``rHisBotrw l0s',
                         playfair.encode(message2, secretPhrase))

   def testDecode(self):
       message1 = 'Programming in Python is fun!!'
       message2 = 'Amazingly few discotheques provide jukeboxes.'

       secretPhrase = 'Barack H. Obama'
       self.assertEquals(message1,
          playfair.decode('Gbt`cr\x00\t\x00ht_aookJ~hipoaooO[}p\x0b"\t',
                                secretPhrase))
       self.assertEquals(message2,
          playfair.decode(':" vjo^tzkgfzr\\plOphq[h|fqHo javefroyBg.lzfqa"',
                                secretPhrase))

       secretPhrase = "George Herbert Walker Bush"
       self.assertEquals(message1,
          playfair.decode("MHr GB|&njftszitN{H\ttcsz$sy#c'&k",
                                secretPhrase))
       self.assertEquals(message2,
          playfair.decode('Efsqjpeuzg`gzofjkirWlHya lWigrz``rHisBotrw l0s',
                                secretPhrase))

unittest.main()

Due date

Before 6am Friday February 1. Zip (don't use .rar) and submit your .py files to Canvas. No other form of submission will be accepted.