Caesar Cipher in Python

5/5 - (1 vote)

Caesar Cipher is a simple encryption and obfuscation method. It’s a straightforward substitution cipher that replaces each plain text character with the obfuscated character obtained by shifting the plain text character a fixed number of positions in the alphabet.

What is Cryptography?

Before we introduce our first cryptographic algorithm, Caesar’s cipher, let’s first introduce cryptography as a scientific and industrial discipline.

💡 Definition: Cryptography is the study of secure communications techniques that allow only the sender and recipient of a message to view its contents. The term is derived from the Greek word kryptos, which means hidden.

Source: Kaspersky

Here’s another less formal definition:

💡 Definition: Cryptography provides for secure communication in the presence of malicious third-parties – known as adversaries. Encryption uses an algorithm and a key to transform an input (i.e., plaintext) into an encrypted output (i.e., ciphertext).

Source: Synopsis

Getting Acquainted With the Terms in Cryptography

By this point, we begin to notice certain similarities or keywords.

(1) The first thing to notice is that both definitions emphasized secure communication, i.e. a communication whose content is unavailable to anyone other than the sender and receiver(s) of the message. The content is at the center of communication, as it is being carried in the form of a message.

(2) The second thing to notice is that we have the first party, i.e., the sender of the message; a second party (or more than one), i.e., the recipient(s) of the message; and possibly a third party, i.e., any unintended/uninvited recipient of the message, also known as an adversary.

An adversary is generally considered an unintended recipient of the message who might misuse the content of the message with any malicious intent.

(3) The third thing to notice from the definitions are mentions of a key, an algorithm, a plaintext message, and a ciphertext message.

A key is a changeable component, usually a number or a sequence of symbols that drive the encryption algorithm.

An encryption algorithm is represented as a series of computational steps which transform an input content (e.g., non-binary text or other binary content), by applying a key, to a secure ciphertext message.

The ciphered content is practically unreadable and, as such, suitable for transfer over an insecure information system to its intended recipient. The intended recipient holds a key that enables him to apply a reverse algorithm and decipher the ciphertext message back to the original, plaintext message.

It is assumed that a third party doesn’t hold the key and is unable to retrieve the plaintext message content in any other way, so the message is available only to the intended recipient and therefore secure.

From a practical point of view, absolute security may not be achieved as there are attack methods that might enable a third party to break the ciphered message and retrieve the original content, but their existence depends on the strength of a specific algorithm, which we will discuss at a later point.

What is Caesar’s Cipher?

Caesar’s cipher is a simple cryptographic algorithm that uses substitution, i.e., systematic replacement of each symbol from the original, plaintext message with another, predetermined symbol.

According to popular belief, it is said it was used by the Roman emperor Julius Caesar in his private correspondence.

Due to the algorithm’s simplicity, Caesar’s cipher in the modern era is not used as a standalone method of encryption but still finds its place as a component in more complex cryptographic systems, such as the ROT13 system or Vigenere cipher.

Besides that, it is a nice and simple algorithm to start our journey into cryptography.

How Does Caesar’s Cipher Work?

Caesar’s cipher algorithm construction begins by defining a set of unique symbols, which we will refer to as the alphabet.

Note: if an alphabet is also defined by order of its symbols, which is not a common case with Caesar’s cipher, it is said that the cipher algorithm uses monoalphabetic substitution.

For example, alphabets a1 = {A, B, C} and a2 = {A, C, B} are treated as different alphabets because the algorithm would produce different outputs for each of them.

We’ll take a closer look at the effect of symbol ordering a bit later.

Besides the alphabet in Caesar’s cipher, we will also introduce a key, i.e., a number that represents an offset in the symbol substitution.

This might sound a bit complicated, but it’s very straightforward: it just means how many symbols in the alphabet we have to skip before reaching the corresponding output symbol.

It is always helpful to take a look at an example: with an alphabet defined as al = {X, R, G, A, F, T, I} and a key k = 5, a plaintext pt = “GRAFITTIX” would be encrypted to ciphertext ct = “XIRGFAAFT”.

For symbols closer to the end of the alphabet than the length of a key, we would just continue counting from the beginning of the alphabet.

Such example is a symbol “F” that reaches the end of the alphabet after three skips: “F”, “T”, “I”, and continues symbol skipping for two more symbols from the start of the alphabet: “X”, “R”, and finally lands on the symbol “G”.

We will see how to simplify the skipping process by a calculation in our source code.

Once we have defined and shared the key with our second parties (the recipients), we can start exchanging secret messages.

In the era of Julius Caesar, literacy was not widespread, and ciphertexts were not so easily deciphered.

However, now in the modern era, Caesar’s cipher is no longer considered strong enough. Therefore, we will take a slight detour and have a look at the cipher with a monoalphabetic ordering.

Python Caesar Cipher

Here we’ll take a look at our source code and see how the magic happens. The comments are here to help in understanding particular ideas and choices in each of the algorithm steps.

def caesars_cipher(message, key=3, operation='encrypt'):
    # Performs an operation, either 'encrypt' or 'decrypt'.
    if operation.lower() not in ('encrypt', 'decrypt'):
        return message

    # Constructs our alphabet of symbols.
    alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890 !?.'

    # Sets the encryption/decryption key.
    key = key % len(alphabet)

    # Initializes the store for encrypted/decrypted message.
    translated = ''

    # Processes each symbol in the message according to the operation and the alphabet.
    for symbol in message:
        # Just some housekeeping - the object initialization.
        shifted_index = -1

        # Note: Only symbols in the 'alphabet' can be processed.
        if symbol in alphabet:
            symbol_index = alphabet.find(symbol)

            # Performs the actual operation.
            if operation == 'encrypt':
                shifted_index = symbol_index + key
            elif operation == 'decrypt':
                shifted_index = symbol_index - key

            # Handles possible overstepping.
            if shifted_index >= len(alphabet):
                shifted_index = shifted_index - len(alphabet)
            elif shifted_index < 0:
                shifted_index = shifted_index + len(alphabet)

            translated = translated + alphabet[shifted_index]
        else:
            # Leaves the symbol untouched if it's outside the alphabet:
            translated = translated + symbol

    return translated


original_message = 'Finxter rules!'
caesars_key = 55

print(f'Original message = {original_message}')
ciphertext = caesars_cipher('Finxter rules!', caesars_key, 'encrypt')
print(f'Ciphertext = {ciphertext}')
plaintext = caesars_cipher(ciphertext, caesars_key, 'decrypt')
print(f'Plaintext = {plaintext}')

The Backstage Math

If we consider an alphabet of aN symbols, the number of ordered alphabets is aN! (! is a factorial operation) and there is aN! possible ciphertexts of the same plaintext.

Here we have to notice that having a key with a monoalphabetic substitution would have no effect. This is because it is always possible to generate an alphabet with an ordering that would exactly match Caesar’s cipher with a key.

In other words, each Caesar’s cipher with a specific key can be generalized by exactly one monoalphabetic substitution.

If we take the regular English alphabet of 26 symbols, the number of possible alphabets with unique orderings would amount to 26! ≈ 4 * 1026 possible ciphertexts (that’s 4 with 26 zeroes!).

Note: If you’re wondering why is this number so large, just consider the following: there are 26 letters in the English alphabet, hence 26 possibilities in picking the first letter. In the next round, for each of these possibilities, there are 25 possibilities in picking the second letter (since the first letter has already been picked). Going all the way, that’s 26 (first pick) * 25 (second pick) * 24 (third pick) * … * 1 (26th – last pick) = 26! ≈ 4 * 1026.

Now we can see that monoalphabetic substitution represents a superset of Caesar’s cipher, and since Caesar’s cipher uses an alphabet with typical alphabetical ordering, it drastically reduces the number of possible ciphertexts to only aN-1, which is, in our case, only 25.

With that in mind, Caesar’s ciphertext can easily be attacked by several approaches, such as a brute-force attack or frequency analysis.

Conclusion

We learned about Caesar’s Cipher, a simple encryption and decryption algorithm, in this article.

  • First, we made a gentle intro to cryptography.
  • Second, we encountered some of the fundamental terms in cryptography.
  • Third, we got acquainted with Caesar’s Cipher.
  • Fourth, we explained how Caesar’s Cipher works.
  • Fifth, we took a glance at the source code.
  • Sixth, we sneaked in the backstage and saw some traces of math behind the algorithm.

Learn More: ROT13 in Python

ROT13 is a simple encryption method. It shifts each character of the clear text string 13 positions forward in the alphabet.

ROT13 Explanation Gif

This Python one-liner does ROT13 encryption for you:

cleartxt = "berlin"
abc = "abcdefghijklmnopqrstuvwxyz"
secret = "".join([abc[(abc.find(c)+13)%26] for c in cleartxt])
print(secret)
# oreyva

You can learn more in our full article here:

The article also comes with a video explanation of the one-liner—check it out!