Prev - #40 Merging Two Sorted Lists | Table of Contents | Next - #42 Bubble Sort

Exercise #41: ROT 13 Encryption

rot13('Hello, world!')   'Uryyb, jbeyq!'
rot13('Uryyb, jbeyq!')   'Hello, world!'

ROT 13 is a simple encryption cipher. The name “ROT 13” is short for “rotate 13.” It encrypts by replacing letters with letters that appear 13 characters down the alphabet: A is replaced with N, B is replaced with O, C is replaced with P, and so on. If this rotation of 13 letters goes passed the end of the alphabet, it “wraps around” the Z and continues from the start of the alphabet. Thus, X is replaced with K, Y is replaced with L, Z is replaced with M, and so on. Non-letter characters are left unencrypted.

The benefit of ROT 13 is that you can decrypt the encrypted text by running it through ROT 13 encryption again. This rotates the letter 26 times, returning us to the original letter. So “Hello, world!” encrypts to “Uryyb, jbeyq!” which in turn encrypts to “Hello, world!” There is no decryption algorithm; you decrypt encrypted text by encrypting it again. The ROT 13 algorithm isn’t secure for real-world cryptography. But it can be used to obfuscate text to prevent spoiling joke punch lines or puzzle solutions.

The following shows what each of the 26 letters encrypts to with ROT 13 once (from the top row to the middle row) and twice (from the middle row to the bottom row.)

A  B  C  D  E  F  G  H  I  J  K  L  M  N  O  P  Q  R  S  T  U  V  W  X  Y  Z

▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼

N  O  P  Q  R  S  T  U  V  W  X  Y  Z  A  B  C  D  E  F  G  H  I  J  K  L  M 

▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼  ▼

A  B  C  D  E  F  G  H  I  J  K  L  M  N  O  P  Q  R  S  T  U  V  W  X  Y  Z

Exercise Description

Write a rot13() function with a text parameter that returns the ROT 13 encrypted version of text. Uppercase letters encrypt to uppercase letters and lowercase letters encrypt to lowercase letters. For example, 'HELLO, world!' encrypts to 'URYYB, jbeyq!' and 'hello, WORLD!' encrypts to 'uryyb, JBEYQ!'.

You may use the following Python functions and string methods as part of your solution: ord(), chr(), isalpha(), islower(), and isupper().

These Python assert statements stop the program if their condition is False. Copy them to the bottom of your solution program. Your solution is correct if the following assert statements’ conditions are all True:

assert rot13('Hello, world!') == 'Uryyb, jbeyq!'

assert rot13('Uryyb, jbeyq!') == 'Hello, world!'

assert rot13(rot13('Hello, world!')) == 'Hello, world!'

assert rot13('abcdefghijklmnopqrstuvwxyz') == 'nopqrstuvwxyzabcdefghijklm'

assert rot13('ABCDEFGHIJKLMNOPQRSTUVWXYZ') == 'NOPQRSTUVWXYZABCDEFGHIJKLM'

Try to write a solution based on the information in this description. If you still have trouble solving this exercise, read the Solution Design and Special Cases and Gotchas sections for additional hints.

Prerequisite concepts: strings, ord(), chr(), for loops, Boolean operators, islower(), isupper(), augmented assignment operators

Solution Design

Instead of hard-coding every letter and its encrypted form, we can rely on each letter’s Unicode code point integer. Code points were discussed in Exercise #7, “ASCII Table.” The ord() and chr() functions discussed in Exercise #7, “ASCII Table” can translate from a letter string to integer and integer to letter string, respectively.

The function starts with an encryptedText variable set to an empty string that will store the encrypted result as we encrypt each character. A for loop can loop over the text parameter to encrypt each character. If this character isn’t a letter, it’s added to the end of encryptedText as-is without encryption.

Otherwise, we can pass the letter to ord() to obtain its Unicode code point as an integer. Uppercase letters A to Z have integers ranging from 65 up to and including 90. Lowercase letters a to z have integers ranging from 97 up to and including 122. We need to reduce this by 26 to “wrap around” to the start of the alphabet.

For example, the letter 'S' has an integer 83 (because ord('S') returns 83) but adding 83 + 13 gives us 96, which is greater than the integer for Z (ord('Z') returns 90). In this case, we must subtract 26: 96 - 26 gives us the encrypted integer 70, and chr(70) returns 'F'. This is how we can determine that 'S' encrypts to 'F' in the ROT 13 cipher.

Note that while an uppercase 'Z' has the Unicode code point 90, the lowercase 'z' has the Unicode code point 122.

Special Cases and Gotchas

While you want to add 13 to the Unicode code point integers of both uppercase and lowercase letters, when you check if this addition results in a number larger than Z’s Unicode code point, you must use the correct case of Z. Otherwise, your rot13() function may determine that the lowercase 'a' (with integer 97) is past uppercase 'Z' (with integer 90) because 97 is greater than 90. You must compare lowercase rotated letters with 122 (the integer of lowercase 'z') and uppercase rotated letters with 90 (the integer of uppercase 'Z').

All non-letter characters such as numbers, spaces, and punctuation marks are added to the encrypted text unmodified. Be sure that your rot13() function doesn’t accidentally drop them from the returned string.

Now try to write a solution based on the information in the previous sections. If you still have trouble solving this exercise, read the Solution Template section for additional hints.

Solution Template

Try to first write a solution from scratch. But if you have difficulty, you can use the following partial program as a starting place. Copy the following code from https://invpy.com/rot13-template.py and paste it into your code editor. Replace the underscores with code to make a working program:

def rot13(text):

    # Create an encryptedText variable to store the encrypted string:

    encryptedText = ____

    # Loop over each character in the text:

    for character in text:

        # If the character is not a letter, add it as-is to encryptedText:

        if not character.____():

            encryptedText += ____

        # Otherwise calculate the letter's "rotated 13" letter:

        else:

            rotatedLetterOrdinal = ____(character) + 13

            # If adding 13 pushes the letter past Z, subtract 26:

            if ____.islower() and rotatedLetterOrdinal > ____:

                rotatedLetterOrdinal -= ____

            if ____.isupper() and rotatedLetterOrdinal > ____:

                rotatedLetterOrdinal -= ____

 

            # Add the encrypted letter to encryptedText:

            encryptedText += ____(rotatedLetterOrdinal)

 

    # Return the encrypted text:

    return encryptedText

The complete solution for this exercise is given in Appendix A and https://invpy.com/rot13.py. You can view each step of this program as it runs under a debugger at https://invpy.com/rot13-debug/.

Further Reading

If you are interested in writing Python programs for encryption algorithms and code breaking, my book “Cracking Codes with Python” is freely available under a Creative Commons license at https://inventwithpython.com/cracking/.

Prev - #40 Merging Two Sorted Lists | Table of Contents | Next - #42 Bubble Sort