CS303E Homework 10

Instructor: Dr. Bill Young
Due Date: Monday, November 2, 2026 at 11:59pm

Copyright © William D. Young. All rights reserved.

The Playfair cipher (PFC) is a manual symmetric encryption technique. The scheme was invented in 1854 by Charles Wheatstone, but bears the name of Lord Playfair who promoted its use. The technique encrypts pairs of letters (digrams), instead of single letters. It is significantly harder to break than the simple substitution ciphers popular at that time. You can find information on the Playfair cipher on Wikipedia: Playfair Cipher. For this assignment, you won't be implementing the entire cipher, but only parts.

PFC requires generating a 5x5 key from a user-supplied keyword. You can see such a key in the picture above. It also works best if the plaintext (text to be encrypted) consists purely of uppercase letters, without any double-letter pairs. In this assignment, you'll write functions to generate a PFC key from a user-supplied keyword, to turn an arbitrary ASCII text into a suitable plaintext for encryption by PFC, and to locate a letter in the key.

Your Assignment

Part 1: The first part of the assignment is to generate a PFC key from a user-supplied keyword. The keyword can be an arbitrary string. This involves several steps, indicated by the functions shown below:
def makeKeyword( string ):
    """Given an arbitrary string, return a string consisting of its
    unique letters in order and in uppercase. Discard non-letters and
    duplicates. """

def keyLetterList( keyword ):
    """Return a list of the letters in keyword, followed by all other
    letters from the alphabet (excluding 'J').  Assuming that keyword
    was produced by makeKeyword(), this should be a list of 25 unique 
    uppercase letters. """

def makeKey( string ):
    """Given a keyword, make a Playfair Cipher key.  A PFC key is a
    5x5 array with successive unique letters from the input key word
    followed by the remaining letters of the alphabet. """

def showKey( key ):
    """Display a PFC key as a 5x5 grid of uppercase letters."""
Here's some sample output to show how these functions should work.
>>> string = '123@#$*playa aa fa ir   89$%#@19'
>>> keyword = makeKeyword( string )
>>> keyword
'PLAYFIR'
>>> letters = keyLetterList( keyword )
>>> letters
['P', 'L', 'A', 'Y', 'F', 'I', 'R', 'B', 'C', 'D', 'E', 'G', 'H', 'K', 'M', 'N', 'O', 'Q', 'S', 'T', 'U', 'V', 'W', 'X', 'Z']
>>> key = makeKey( string )
>>> key
[['P', 'L', 'A', 'Y', 'F'], ['I', 'R', 'B', 'C', 'D'], ['E', 'G', 'H', 'K', 'M'], ['N', 'O', 'Q', 'S', 'T'], ['U', 'V', 'W', 'X', 'Z']]
>>> showKey( key )
P  L  A  Y  F  
I  R  B  C  D  
E  G  H  K  M  
N  O  Q  S  T  
U  V  W  X  Z  
>>> 
In my implementation makeKey( string ) actually calls makeKeyword() and keyLetterList() as steps toward creating the PFC key. showKey is just to display the key after creating it. You can define other subsidiary functions if you like, but must implement at least the functions above.

Part 2: As noted before, PFC works best if the plaintext (text to be encrypted) consists purely of uppercase letters, without any double-letter pairs. In this part of the assignment, you'll take an arbitrary ASCII text inputText and massage it to create a suitable input text for PFC. This involves the following steps:

  1. make inputText uppercase;
  2. remove any non-letters;
  3. if two successive letters are identical (e.g. "LL") break them up by adding a fill character between;
  4. finally, if the resulting text's length is odd, pad on the end with the fill character.
This can all be done in one function, but you're welcome to write helper functions if you like. Here's the header of the function I wrote:
def makePlayfairInputText( inputText, fillCharacter ):
    """Take an arbitrary ASCII text and massage it to make it into
    suitable input for encryption by PFC. That means to:
    1. turn it into uppercase;
    2. remove non-letters;
    3. separate double-letter pairs with the fill character;
    4. if the resulting text has odd length, pad on the right with the 
       fill character.

    """
Here's how this looks in operation; we're using 'X' as the fill character.
>>> text = """Fourscore and seven years ago ago our fathers 
brought forth on this continent, a new nation, conceived 
in Liberty, and dedicated to the proposition that all 
men are created equal."""
>>> newText = makePlayfairInputText( text, 'X' )
>>> newText
'FOURSCOREANDSEVENYEARSAGOFOURSCOREANDSEVENYEARSAGOXOURFATHERSBROUGHTFORTHONTHISCONTINENTANEWNATIONCONCEIVEDINLIBERTYANDXDEDICATEDTOTHEPROPOSITIONTHATALXLMENARECREATEDEQUALX'
>>> string
'123@#$*playa aa fa ir   89$%#@19'
>>> makePlayfairInputText( string, 'X' )
'PLAYAXAXAFAIRX'
>>> 
BTW: it's possible that this fails if the fill character itself occurs as a double letter digram in the text. Using 'X' this might happen if the text includes words like "antivaxxer", "doxxing", or "looksmaxxing." In a production version, I'd check if the repeating character was the same as the fill character and use a different fill character, maybe 'Q', in that case. You don't have to do this.

Part 3: The final part is writing a function to look for a character in the key. Remember that the key is a 5x5 list. You should return a tuple (row, col) where the character occurs in the key. If the character is 'J', return the location of 'I' in the key. Here's the header:

def findCharacterInKey( ch, key ):
    """Return the tuple (row, col) where ch.upper() appears in key.
    Print an error message if it's not in the key. Treat 'J' as 'I'."""
Here is some sample behavior of this function:
>>> showKey( key )
P  L  A  Y  F  
I  R  B  C  D  
E  G  H  K  M  
N  O  Q  S  T  
U  V  W  X  Z  
>>> findCharacterInKey( 'Z', key )
(4, 4)
>>> findCharacterInKey( 'Q', key )
(3, 2)
>>> findCharacterInKey( '$', key )
Character not in key: $
>>> findCharacterInKey( 'a', key )
(0, 2)
>>> findCharacterInKey( 'j', key )
(1, 0)
>>> 

Encrypting and Decrypting

You don't have to implement encryption or decryption, but it's a good exercise. For those of you interested, here's the description from Wikipedia of how you use the Playfair Cipher to encrypt and decrypt. Here they're using 'X' as the fill character.
To encrypt a message, one would break the message into digrams (groups of 2 letters) such that, for example, "HelloWorld" becomes "HE LL OW OR LD". These digrams will be substituted using the key table. Since encryption requires pairs of letters, messages with an odd number of characters usually append an uncommon letter, such as "X", to complete the final digram. The two letters of the digram are considered opposite corners of a rectangle in the key table. To perform the substitution, apply the following 4 rules, in order, to each pair of letters in the plaintext:
  1. If both letters are the same (or only one letter is left), add an "X" after the first letter. Encrypt the new pair and continue. Some variants of Playfair use "Q" instead of "X", but any letter, itself uncommon as a repeated pair, will do.
  2. If the letters appear on the same row of the table, replace them with the letters to their immediate right respectively (wrapping around to the left side of the row if a letter in the original pair was on the right side of the row).
  3. If the letters appear on the same column of the table, replace them with the letters immediately below respectively (wrapping around to the top side of the column if a letter in the original pair was on the bottom side of the column).
  4. If the letters are not on the same row or column, replace them with the letters on the same row respectively but at the other pair of corners of the rectangle defined by the original pair. The order is important; the first letter of the encrypted pair is the one that lies on the same row as the first letter of the plaintext pair.
To decrypt, use the inverse (opposite) of the two shift rules, selecting the letter to the left or upwards as appropriate. The last rule remains unchanged, as the transformation switches the selected letters of the rectangle to the opposite diagonal, and a repeat of the transformation returns the selection to its original state. The first rule can only be reversed by dropping any extra instances of the chosen insert letter, generally "X"s or "Q"s, that do not make sense in the final message when finished.

Turning in the Assignment:

The program should be in a file named Playfair.py. Submit the file via Canvas before the deadline shown at the top of this page. Submit it to the assignment 10 in the assignments sections by uploading your Python file.

Your file must compile and run before submission. It must also contain a header with the following format:

# Assignment: HW10
# File: Playfair.py
# Student: 
# UT EID:
# Course Name: CS303E
# 
# Date:
# Description of Program: 

Programming Tips:

Using functions: I have described the values of functions in several of these programming tips. Still I'm getting questions like: do we need to have auxiliary functions (or functions at all)? To me, that's like asking your English teacher, who just assigned you a 5-page paper: "Do I have to have paragraphs?"

Functions are one of your most useful tools for managing the complexity of code. Strictly speaking, you can get by without them. But your code is likely to be an unreadable, unmaintainable jumble. Coding something as complicated as the Playfair Cipher without using functions would be crazy. Students often think that writing functions is a waste of time; they're wrong. Just the increase in clarity is well worth the effort.