Caesar (Shift) Cipher

The Caesar cipher, also called the shift cipher, takes a message and “shifts” each letter by a fixed number of positions in the alphabet. For a positive key, encryption shifts the letters forward, while decryption shifts them backward.

Encryption

We restrict our attention to capital letters only, A through Z. Shifting each letter forward by one, wrapping Z back to A, defines a rule for changing plaintext into ciphertext.

\[ \text{A} \to \text{B},~~\text{B} \to \text{C},~~\text{C} \to \text{D},~~\ldots,~~\text{Z} \to \text{A}. \]
HELLO WORLD     # plaintext
IFMMP XPSME     # ciphertext

Alternatively, we could choose to shift each letter forward by two positions.

\[ \text{A} \to \text{C},~~\text{B} \to \text{D},~~\text{C} \to \text{E},~~\ldots,~~\text{Z} \to \text{B}. \]
HELLO WORLD     # plaintext
JGNNQ YQTNF     # ciphertext

In general, each letter can be shifted by \(n\) positions for some \(n \in \mathbb{Z}\). First, assign the letters to consecutive integers, for example,

\[ \text{A} = 0,~~\text{B} = 1,~~\text{C} = 2,~~\ldots,~~\text{Z} = 25. \]

With \(n\) fixed, add \(n\) to each letter’s integer value and reduce the result modulo \(26\); that is, take the remainder after dividing by \(26\). This reduction produces an integer from \(0\) through \(25\).

\[ \text{A} \to n\,\operatorname{mod}\,26,~~ \text{B} \to (1 + n)\,\operatorname{mod}\,26,~~ \text{C} \to (2 + n)\,\operatorname{mod}\,26,~~ \ldots,~~ \text{Z} \to (25 + n)\,\operatorname{mod}\,26. \]

Finally, translate the result back to a letter. The integer \(n\) is the cipher key: a different choice of \(n\) leads to a different cipher. Only its value modulo \(26\) matters: \(n\) and \(n + 26k\) produce the same cipher for any integer \(k\in\mathbb{Z}\).

For example, let \(n = 12\). The letter \(\text{T}\) is initially assigned the value of \(19\), and

\[ (19 + n)\,\operatorname{mod}\,26 ~=~ 31\,\operatorname{mod}\,26 ~=~ 5. \]

Because \(\text{F}\) corresponds to the value \(5\), the key \(n = 12\) gives the rule \(\text{T}\to\text{F}\).

The String Module

The standard-library string module defines several strings of characters that are useful for processing text.

Constant

Contents

string.ascii_letters

"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"

string.ascii_lowercase

"abcdefghijklmnopqrstuvwxyz"

string.ascii_uppercase

"ABCDEFGHIJKLMNOPQRSTUVWXYZ"

string.digits

"0123456789"

string.punctuation

Punctuation characters, such as !, ?, ,, and .

string.whitespace

Whitespace characters, including space, tab \t, and newline \n.

For the cipher described above, string.ascii_uppercase is the alphabet the cipher is defined over.

import string

print(string.ascii_uppercase)
print(len(string.ascii_uppercase))
ABCDEFGHIJKLMNOPQRSTUVWXYZ
26

Problem 3

Write a function named shift_encrypt_letter() that takes a single-character string and an integer \(n\). If the character is an uppercase letter A-Z, encrypt the letter using the Caesar cipher with key \(n\). Otherwise, return the character unchanged.

Test Cases

print(shift_encrypt_letter("A", 12))
print(shift_encrypt_letter("D", -3))
print(shift_encrypt_letter("T", 0))
print(shift_encrypt_letter("A", 7))
print(shift_encrypt_letter("!", 5))
M
A
T
H
!

Problem 4

Write a function named shift_encrypt() that accepts a string of plaintext (with one or more characters) and an integer \(n\). Encrypt the plaintext using the Caesar cipher with key \(n\). Leave characters other than A-Z unchanged.

Hint: Join the results of a list comprehension.

Test Cases

print(shift_encrypt("HELLO WORLD!", 1))
print(shift_encrypt("HELLO WORLD!", 2))
print(shift_encrypt("HELLO WORLD!", 18))
IFMMP XPSME!
JGNNQ YQTNF!
ZWDDG OGJDV!
print(shift_encrypt(seuss.upper(), 3))
L DP VDP. L DP VDP. VDP L DP.
WKDW VDP-L-DP! WKDW VDP-L-DP! L GR QRW OLNH WKDW VDP-L-DP!
ZRXOG BRX OLNH JUHHQ HJJV DQG KDP?
L GR QRW OLNH WKHP, VDP-L-DP.
L GR QRW OLNH JUHHQ HJJV DQG KDP.

Decryption

Decrypting a Caesar-ciphered message undoes the shift applied during encryption. Since encryption shifts every letter forward by \(n\), decryption shifts every letter backward by the same amount.

Problem 5

Write a function named shift_decrypt() that accepts a string of ciphertext (with one or more characters) and an integer \(n\). Decrypt the ciphertext using the Caesar cipher with key \(n\).

Hint: Can you do this with a call to shift_encrypt() from Problem 4?

Test Cases

print(shift_decrypt("JGNNQ YQTNF!", 1))
print(shift_decrypt("JGNNQ YQTNF!", 2))
print(shift_decrypt("JGNNQ YQTNF!", 3))
IFMMP XPSME!
HELLO WORLD!
GDKKN VNQKC!

The following test checks that decryption undoes encryption for a random key \(n\).

import random

x = "THE QUICK BROWN FOX JUMPED OVER THE LAZY DOG"
n = random.randint(0, 25)
print(shift_decrypt(shift_encrypt(x, n), n) == x)
True

Problem 6

When the key \(n\) used to encrypt a message is unknown, one approach to decryption is to try each of the \(26\) distinct shifts until the decrypted result looks like readable English. This so-called brute-force approach is practical for the Caesar cipher because the number of possibilities is small.

Decrypt the coded message KYV TRBV ZJ R CZV by calling shift_decrypt() with different values of the key \(n\) and reading each result. Store the value of the key \(n\) that leads to decryption in a variable named problem6_key.

Using the approach from Problem 6, can you decrypt the messages from the beginning of the lab?