Caesar Cipher in Python - Classical Cryptography

Classical cryptography and stenography are very fun to program. They are often used in various capture the flag programmer events. Classical substitution ciphers, like Caesar Cipher, are particularly fun, because they are simple enough to understand and crack with just a little bit of knowledge. One can often find puzzles, called Cryptograms or Cryptoquotes, in newspapers or online that challenge the user to figure out a popular phrase given the ciphertext version of it.

Caesar Cipher

Caesar Cipher is one of the simplest forms of substitution ciphers, because it is just a shift of the alphabet by a certain number of characters to create the ciphertext. Julius Caesar, for whom this cipher is named after, apparently used this cipher a lot with a shift of 3 (key = 3). With a key of 3, the letter 'a' becomes 'd', 'b' becomes 'e', 'c' becomes 'f', etc. You can learn more about Caesar Cipher on Wikipedia and Practical Cryptography.

Caesar Cipher

Computer Science Assignment - Caesar Cipher Class

My computer science course asked me to write a class in Python that encrypts and decrypts messages using Caesar Cipher. There are numerous ways to solve this solution in Python. I developed a few solutions to this problem, but turned in this one, which passed all tests.

import string

class CaesarCipher:
    """ Encrypt and decrypt messages using Caesar Cipher. Specify
        the number of letters to shift ( key ). All alphabetical
        characters will be encrypted and decrypted using key.
        All non-alphabetical characters will be included as-is in
        the ciphertext and plaintext. If key is not provided, it
        uses key = 3 by default.

    def __init__(self, key=3):
        """ Initializes Caesar Cipher using specified key.

        :param int key: The number of letters to shift. Default is 3.
        self.key = key % 26

        # dict used for encryption - { plaintext letter : ciphertext letter, ... }
        self.e = dict(zip(string.ascii_lowercase, string.ascii_lowercase[self.key:]
                          + string.ascii_lowercase[:self.key]))

        self.e.update(dict(zip(string.ascii_uppercase, string.ascii_uppercase[self.key:]
                               + string.ascii_uppercase[:self.key])))

        # dict used for decryption - { ciphertext letter : plaintext letter, ... }
        self.d = dict(zip(string.ascii_lowercase[self.key:] + string.ascii_lowercase[:self.key],
        self.d.update(dict(zip(string.ascii_uppercase[self.key:] + string.ascii_uppercase[:self.key],

    def encrypt(self, plaintext):
        """Converts plaintext to ciphertext.

        :param str plaintext: The message to encrypt.
        :return: The ciphertext.
        :rtype: str
        return ''.join([self.e[letter]
                        if letter in self.e else letter
                        for letter in plaintext])

    def decrypt(self, ciphertext):
        """ Converts ciphertext to plaintext.

        :param str ciphertext: The message to decrypt.
        :return: The plaintext.
        :rtype: str
        return ''.join([self.d[letter]
                        if letter in self.d else letter
                        for letter in ciphertext])

The majority of the code is Docstring documentation. I am learning to properly document functions and classes in Python so bear with me. PyCharm is assisting me quite well in this area.

I chose this solution, because I am trying to learn more about Python libraries, built-in functions, and special language features. In particular, I liked the use of string constants, zip function, dictionaries, and list comprehensions in this version. There are simpler solutions to Caesar Cipher, but this was the most fun to create.

Python String Constants

One of the really useful things I love about Python is its string constants. I find these extremely useful in my course assignments. As you can see in the CaesarCipher Class, I am using the string constants: string.ascii_lowercase and string.ascii_uppercase to build the cipher map. The Caesar Cipher only converts alphabetical characters and these constants provide a nice list of the characters. There is also string.ascii_letters, string.punctuation, and string.whitespace that I use in my unit tests to make sure non-alphabetical characters are passed through as-is and encryption is working correctly for all letters. I realize this is a basic feature of Python, but I find I use string constants a lot in Python.

Zip Function in Python

The zip function in Python is particularly useful when combined with dictionaries, but is useful anytime you have multiple iterable sequences and you want to combine them to form a single list of tuples. I am using the zip function to build the cipher map for the Caesar Cipher. For encryption, zip combines the following lists into a list of tuples for a Caesar Cipher key of 3. It does this for both lower and uppercase letters and for decryption as well, but I am just showing it for encryption of lowercase letters.

Before Zip: ['a','b','c','d', ... ,'x','y','z'] and ['d','e','f','g', ... ,'a','b','c']

After Zip: [('a','d'),('b','e'),('c','f'),('d','g'), ... ,('x','a'),('y','b'),('z','c')]

Python Dictionaries

Python dictionaries can be created from a sequence of key-value pairs, and that is exactly what the zip function is providing. I build a dictionary based on the list of tuples created by the zip function in Python. The encryption dictionary is created as such.

e = dict(('a','d'),('b','e'),('c','f'),('d','g'), ... ,('x','a'),('y','b'),('z','c'))

e = {'a':'d','b':'e','c':'f','d':'g', ... , 'x':'a','y':'b','z':'c'}

This provides the encryption lookup for each lowercase letter in the plaintext message.

List Comprehensions

List comprehensions are really cool in Python and I still don't have their use memorized. I keep a cheat sheet near my desk. The actual encryption and decryption are done using list comprehensions. Here is the list comprehension for encryption.

def encrypt(self, plaintext):
        return ''.join([self.e[letter]
                        if letter in self.e else letter
                        for letter in plaintext])

This boils down to looping through each character in the plaintext and checking to see if the character is in the encryption dictionary. If it is, it adds the encrypted value to the ciphertext. If not, it just passes the character as-is into the ciphertext. The list comprehension creates a list and ''.join(...) creates a string based on that list. Really cool!

Unit Tests for Caesar Cipher

In addition to learning about documenting my modules, classes, and functions, I am also currently learning to write unit tests and Doctests. I don't have full coverage on my CaesarCipher class, but I'm pretty confident it works well. I separated my tests into 1) key tests, 2) encryption tests, and 3) decryption tests. Key tests test that for all 25 unique keys I get the expected results. For example, Caesar Cipher with a key of 0 provides no encryption at all.

def test_key_0(self):
        c = CaesarCipher(0)
        plaintext = string.ascii_letters
        ciphertext = c.encrypt(plaintext)
        self.assertEqual(ciphertext, string.ascii_letters)

You can see where I am using a new Python string constant mentioned earlier: string.ascii_letters.

Encryption and decryption tests test to make sure those functions are working accordingly. There is some overlap here, but an example test is that punctuation is passed through as-is.

def test_encrypting_punctuation(self):
        ciphertext = self.cipher.encrypt(string.punctuation)
        self.assertEqual(ciphertext, string.punctuation)

Again, if I just pass in a string of punctuation characters for the plaintext, I should get the same output as my ciphertext.

I actually love writing unit tests, because as I was refactoring my code, my unit tests were continually making sure the correctness did not change.

About This Solution

Many of the Caesar Cipher solutions convert everything to lowercase, but I use both lowercase and uppercase so you are free to use both in the plaintext and ciphertext messages. Many solutions also remove non-alphabetical characters, like punctuation and whitespace, but I pass them through as-is. The reason for this is because I had the option in the assignment, and I thought this would be more fun when trying to solve them manually. It makes the ciphertext more like a Cryptogram or Cryptoquote and could be fun to send to people to solve.


Creating a Python class to perform encryption and decryption using the Caesar Cipher was a lot of fun. I enjoy classical cryptography and stenography, and it's fun to explore cool features in Python that make programming elegant. I have some other examples of Caesar Cipher in Python as well as other classical ciphers (rail-fence cipher, etc.) that I will share later. I also have solutions to help crack the Caesar Cipher, and that will be fun to share, too.

I hope to see you on twitter. I am @KoderDojo. Best wishes!

Posted by David Hayden
Koder Dojo
David Hayden is a professional Microsoft web developer. He mentors and tutors computer science students in C, C++, Java, and Python. In addition to writing computer science and programming tutorials at Koder Dojo, he also writes tutorials on C#, ASP.NET Core, and Azure as well as tutorials on Orchard Core.