Encryption is the process of encoding information into an alternate form that is unintelligible. Decryption is the process of decoding that alternate form to get the original information.
Encryption is used to protect private information as it is transmitted through channels that can be monitored, like radio communication and the Internet. Most web sites, apps, and servers communicate over secure channels, meaning that they encrypt the data before sending and decrypt after receiving.
In this tutorial, you will start with some basic encryption concepts using one of the oldest and most simple forms of encryption, the Caesar cipher. Above is a picture of the Caesar cipher with a key of 5 applied to the first two characters in “HELLO”. The encrypted version of the H E in HELLO is M J. Can you complete the last three characters in the ciphertext?
This tutorial will guide you through writing Python scripts that can encrypt and decrypt with the Caesar cipher and some other simple ciphers.
You will need:
Complete these tutorials first:
You will be able to write scripts that use simple ciphers to encrypt and decrypt characters, words, and even complete messages. You will also have gained familiarity with keys, ciphers, and other basic cryptography terms. Along the way, you will apply some of the string manipulation skills introduced in the Strings Primer.
After this, you will also be ready to move on to the next tutorials (coming soon!)
The shift cipher is a simple form of encryption that has been used since at least the days of the Roman Empire. Julius Caesar’s encrypted letters are the first widely known use of a shift cipher. A shift cipher is a type of substitution cipher that replaces each letter in a message with an adjacent letter from the alphabet one, two, three, or ten or more letters away.
If you answered that the ciphertext from the previous page is MJQQT, you are correct! See how the key of 5 is applied? Starting with H at the beginning of the plaintext, count 5 letters to the right in the alphabet and use that character (M) in the ciphertext version.
What about decryption? If the key is 5 and your ciphertext is MJQQT, count 5 letters earlier in the alphabet for each letter. For example, the plaintext H in HELLO is 5 letters to the left of the ciphertext letter M from MJQQT.
What happens with a key of 5 when you get to a letter like X? No problem, just start over at A. …but make sure to count the jump from Z to A as a letter. For example, if the plaintext letter is X, then the five character shift is Y Z A B C, and the ciphertext letter is C. In other words, when you run out of characters, restart at the beginning. This is called a circular shift.
Now that you can see how to encrypt characters with the Caesar cipher, let’s try it!
Next, show a friend how to decrypt it:
Alright, are you ready to write a script that does what you just did by hand?
Before getting started, think about this set of steps as a conversion between by-hand to pseudo code for programming.
Now let's go!
A script that does the Caesar cipher has to start by finding a character in an alphabet. In this example, you will write a script that creates an alphabet string and finds the index of a certain character. In other words, it will tell you that A has an index of 0, B has an index of 1, and so on, up through Z, which has an index of 25.
# find_letter_in_alphabet from microbit import * sleep(1000) alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" letter = "M" index = alpha.find(letter) print("letter =", letter, "index =", index) print()
When the script runs, the terminal should print index = 12 since that is the index of M in the string alpha.
Every cipher begins with an alphabet, so the script starts by creating a string named alpha, which contains all the uppercase letters in the alphabet.
alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
The .find() method is something available to each string in Python. .find() searches the string for whatever text is passed to it from inside the parentheses, and it returns the index where the text is first located. Remember that the index numbers in a string start at zero. So, the first character A has an index of 0, B has an index of 1, and so on, through Z with an index of 25. In this counting system, M has an index of 12 in the alpha string. So, letter = "M" followed by alpha.find(letter) returns 12.
“…and now, for something completely different!”
You might have already experimented with the .find method in Try This: Find the Substring [7]. It had examples of finding the starting index values of words like "Arthur" and "run", and even used the index to decide what to do next. In this encryption example, the alpha string’s .find() method is instead used to locate a single letter within a string.
This next script will encrypt individual letters with the Caesar cipher. As-written it encrypts the letter M with a key of 5.
# caesar_encrypt_letter from microbit import * sleep(1000) key = 5 letter = "M" alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" index = alpha.find(letter) print("key =", key) print("letter =", letter, ", index =", index) index = index + key index = index % 26 result = alpha[index] print("new index =", index, ", result =", result) print()
What if a script receives a Caesar encrypted character and a key? Do you need to write another script? The answer is no. To decrypt a ciphertext letter to plaintext, just use the negative of the key that encrypted it. So, if a ciphertext character was encrypted with 5, it can be decrypted with -5.
Remember how a plaintext letter M encrypted to a ciphertext letter R when the key was 5? To decrypt a ciphertext letter of R with a key of 5, just run it through the same Caesar cipher with a key of -5. The result will be the plaintext character M.
Once your script knows the index of a character in a string, it can simply add the key to the index and use the result to get the ciphertext character.
The first four statements are the same as the previous activity. So when key = 5 and the plaintext letter = M, alpha.find(letter) returns 12, which gets stored in index. Now for the Caesar shift. index = index + key evaluates to index = 12 + 5, which is 17. With result = alpha[index], that’s result = alpha[17]. Since alpha[17] returns "R", that’s the ciphertext character the result variable stores.
Remember the circular shift? That’s when the next character after Z in a Caesar shift wraps around to A. For example, with a plaintext letter of X and a key of 5, we want a ciphertext letter of C.
This circular shift is made possible by index = index % 26. The % operator is called the modulus operator, and it returns what’s left over from an integer division operation. Unlike floating point division where 19 / 5 = 3.8, integer division’s quotient result would be 3 with a remainder of 4. Integer division always rounds down, so 19 // 5 = 3. The remainder is 19 % 5 = 4. That remainder of 4 is another way of expressing the 0.8 part of 3.8 because 0.8 * 5 = 4. Another way to think about the remainder is that 3 * 5 = 15, which is 4 below the original numerator of 19.
After index = index + key, 24 is still Y, and 25 is still Z, but 26 isn’t in the alphabet. In fact, alpha[26] would cause an exception in the script by making it look for a character that’s not there. 26 needs to be changed to 0, 27 needs to be changed to 1, 28 needs to be changed to 2 and so on… That’s exactly what index = index % 26 does, as you can see from the right column in this table. It also works fine for values below 26.
Integer quotient, integer remainder
24 // 26 = 0 24 % 26 = 24
25 // 26 = 0 25 % 26 = 25
26 // 26 = 1 26 % 26 = 0
27 // 26 = 1 27 % 26 = 1
28 // 26 = 1 28 % 26 = 2
29 // 26 = 1 29 % 26 = 3
Here is an example with a key of 5 and the plaintext letter X. See how the result of 28 % 26 is 2? The letter C in the alpha string has an index of 2, and that’s the correct result if the script starts with X and a key of 5.
Let’s say your script receives the ciphertext letter R and an encryption key of 5. How would it decrypt to get back to the plaintext letter M?
The answer is to use the same algorithm, but reverse the sign of the key so that it’s -5. The index of R is 17, so index = index + key would be index = 17 + -5 = 12. That’s the index of M, and that’s how the script can use the negative value of a key to decrypt from a ciphertext character back to the original plaintext character.
The ROT-13 cipher is a special case of the Caesar cipher where the characters are shifted right 13 places. Are you ready to create a ROT-13 cipher? Hint: key = 13!
Modifying the key = and letter = statements to encrypt and re-flashing a script to get a ciphertext character isn’t very efficient. It would be much easier for your script to pass a function, a key, and a word to a function and let it encrypt or decrypt the whole string. Getting there can be broken into three steps:
Another reason for setting it up this way is that you can replace a Caesar cipher function with a different encryption function, and the main script might only need one line modified to get it to work.
In this example, the terminal will prompt you for a key and a letter, and then display the Caesar cipher result. Previously, the encryption key was hard-coded as 5. With this script, you can enter 5, or 13 for ROT-13, or any other value you decide to use. Next, enter the letter to encrypt, and the script displays the ciphertext result in the terminal.
# caesar_terminal_letters from microbit import * sleep(1000) print("Set your keyboard to CAPS LOCK.") print() while True: text = input("Enter key: ") key = int(text) letter = input("Enter a letter: ") letter = letter.upper() alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" index = alpha.find(letter) index = index + key index = index % 26 result = alpha[index] print("result =", result) print()
The techniques for building this script make use of:
Instead of hard coding key = 5 and letter = "M", like in caesar_cipher_letter, this script makes it work during runtime with:
text = input("Enter key: ") key = int(text) letter = input("Enter a letter: ")
Before doing the Caesar cipher, it uses letter = letter.upper() which changes any lower-case letter to upper-case. …just in case you forgot to set your keyboard’s CAPS LOCK.
These steps from the previous example sketch can be performed in one line:
index = alpha.find(letter) index = index + key index = index % 26
Here’s how it would look:
index = ( alpha.find(letter) + key ) % 26
This more compact format will be used in the next example script.
This script places the Caesar cipher in a loop. This will allow you to type entire words for encrypting/decrypting! It still also works with single characters if that’s all you want to encrypt.
# caesar_terminal_words from microbit import * sleep(1000) print("Set your keyboard to CAPS LOCK.") print() while True: text = input("Enter key: ") key = int(text) word = input("Enter character(s) in A...Z range: ") alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" result = "" for letter in word: letter = letter.upper() index = ( alpha.find(letter) + key ) % 26 result = result + alpha[index] print("result:", result) print()
Inside the while True loop, the script stores a number you enter into an int variable named key. Then, it stores a word you type in a string variable named word.
text = input("Enter key: ") key = int(text) word = input("Enter character(s) in A...Z range: ")
In addition to the alpha (alphabet) string, a second empty string named result is created to store the result.
alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" result = ""
After that, a for… loop goes through each letter in the plaintext string. After making sure the letter is upper-case, it applies the Caesar cipher with two lines. The first finds the index of the new character with index = ( alpha.find(letter) + key ) % 26. The second adds the character to whatever is already in the ciphertext string with result = result + alpha[index].
for letter in word: letter = letter.upper() index = ( alpha.find(letter) + key ) % 26 result = result + alpha[index]
Before repeating the loop, the ciphertext is printed to the terminal.
print("result: ", result)
Why move the Caesar cipher routine to a function? One advantage would be that you can swap it out with other, better encryption functions, or even function/method calls to a module. As an example, in the next activity, you will replace the caesar function with another one called ascii_shift. After the function swap, your script will only need one line changed!
# caesar_cipher_function from microbit import * ''' Function converts plaintext to ciphertext using key ''' def caesar(key, word): alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" result = "" for letter in word: letter = letter.upper() index = ( alpha.find(letter) + key ) % 26 result = result + alpha[index] return result ''' Script starts from here... ''' sleep(1000) print("Set your keyboard to CAPS LOCK.") print() while True: text = input("Enter key: ") key = int(text) letters = input("Enter character(s) in A...Z range: ") result = caesar(key, letters) print("result:", result) print()
The functionality is identical to the previous example (caesar_terminal_words).
See how all that script has to do is use result = caesar(key, letters) to encrypt the word?
Your script now gets the ciphertext from this one line:
result = caesar(key, word)
The caesar function has two parameters, key and word. Keep in mind that word could also be ciphertext that you are converting back with a negative key value. Inside the function, it does all the same steps as caesar_terminal_words, and then returns the result.
def caesar(key, word): alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" result = "" for letter in word: letter = letter.upper() index = ( alpha.find(letter) + key ) % 26 result = result + alpha[index] return result
The simple ciphers we examine in this tutorial are called monoalphabetic substitution ciphers, where one character always maps to some other letter in the adjusted alphabet.
In addition to the Caesar cipher, you will see names like Atbash, Affine, Beaufort, Hill, and others. Here, you can experiment with Atbash, mixed alphabet, and a very useful variation on the Caesar shift cipher that uses ASCII characters.
The Caesar cipher works well as an introduction to ciphers, but it’s not overly practical. With only 25 keys and every word separated by a space, it’s definitely one of the easiest ciphers to crack. The Caesar cipher is also not a very good fit for encrypting radio data since CAPS LOCK letters with no other characters or spaces would make a script to send an encrypted version of this dictionary really difficult:
{'start' : 3, 'after' : 'Liftoff! '}
The ASCII Shift Cipher works on all printable characters, including spaces, so that dictionary string would be no problem to encrypt and decrypt with ASCII Shift. Although it’s still considered very weak in the encryption world, 93 different keys is still more secure than 25.
# ascii_shift_cipher from microbit import * ''' Function converts plaintext to ciphertext using key ''' def ascii_shift(key, text): result = "" for letter in text: ascii = ( ord(letter) + key - 32 ) % 94 + 32 result = result + chr(ascii) return result ''' Script starts from here... ''' sleep(1000) while True: text = input("Enter key: ") key = int(text) text = input("Enter printable character(s): ") result = ascii_shift(key, text) print("result:", result) print()
Assuming you are just making use of the function, all your script has to do to encrypt some text is this:
result = ascii_shift(key, text)
Like the caesar function, ascii_shift also has arguments for a key and text parameters that accept strings. Unlike the caesar function, the string you pass to text can contain all printable characters including spaces, digits, punctuation, and other keyboard symbols. The ascii_shift function also has a for letter in text loop that does the shift operation on each character and adds it to a result string, which was declared as an empty string variable before the loop.
def ascii_shift(key, text): result = "" for letter in text: ascii = ( ord(letter) + key - 32 ) % 94 + 32 result = result + chr(ascii) return result
Inside that function, the main difference is this statement:
ascii = ( ord(letter) - 32 + key ) % 94 + 32
That single statement does the same job as these five:
ascii = ord(letter)
ascii = ascii – 32
ascii = ascii + key
ascii = ascii % 94
ascii = ascii + 32
Unlike the Caesar cipher, which started with characters indexed as 0…25, the printable ASCII characters are in a range of 32…126. The modulus operator trick doesn’t work right if your alphabet doesn’t start with an index of 0. So, it has to subtract 32 before ascii = ascii % 94, then add 32 afterwards. Here is a breakdown of each step:
Another encryption example is the substitution cipher. With a substitution cipher, each character in an alphabet maps to a cryptabet with different characters in the same position. The simplest example of this is the Atbash or reverse-alphabet cipher.
Alphabet: ABCDEFGHIJKLMNOPQRSTUVWXYZ
Cryptabet: ZYXWVUTSRQPONMLKJIHGFEDCBA
# substitution_cipher_atbash from microbit import * # Atbash cipher. def atbash(text): alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" crypta = "ZYXWVUTSRQPONMLKJIHGFEDCBA" result = "" for letter in text: letter = letter.upper() index = alpha.find(letter) result = result + crypta[index] return result # The script starts executing statements from here. sleep(1000) print("Set your keyboard to CAPS LOCK.") print() while True: plaintext = input("Enter a CAPS LOCK string: ") result = atbash(plaintext) print("result =", result)
Your code has to use result = atbash(plaintext) to get the ciphertext—no key required.
The atbash function has one parameter, text. The built-in key is the cryptabet, a reverse alphabet in this case. Like the other cipher functions, this one also declares an empty string named result to store the result.
def atbash(text):
alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
crypta = "ZYXWVUTSRQPONMLKJIHGFEDCBA"
result = ""
Inside the loop that generates the ciphertext, it starts the same way, by finding the index of the letter in the alphabet with index = alpha.find(letter). What’s different is ciphertext = ciphertext + crypta[index]. This grabs the character with the same index in the cryptabet, and adds it to the result.
for letter in text: letter = letter.upper() index = alpha.find(letter) result = result + crypta[index] return result
Example: Let’s say that letter is E. It’s index in the alphabet is 4. The character at crypta[4] is V, so V would be added to the result string.
Here is an alphabet that also includes digits, a space and some characters that would be useful for making a dictionary.
Alphabet: abcdefghijklmnopqrstuvwxyz 123456789:,{}'
Your Cryptabet: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
Links
[1] https://python.microbit.org/v/2
[2] https://learn.parallax.com/tutorials/robot/cyberbot/software-setup-microbit
[3] https://learn.parallax.com/tutorials/robot/cyberbot/writing-microbit-programs
[4] https://learn.parallax.com/tutorials/robot/cyberbot/computer-microbit-talk
[5] https://learn.parallax.com/tutorials/robot/cyberbot/strings-characters-primer
[6] https://learn.parallax.com/tutorials/robot/cyberbot/dictionary-primer
[7] https://learn.parallax.com/tutorials/robot/cyberbot/strings-characters-primer/compare-find-check/try-find-substring
[8] http://learn.parallax.com/tutorials/robot/cyberbot/computer-microbit-talk/input-messages
[9] http://learn.parallax.com/tutorials/robot/cyberbot/writing-microbit-programs/count-and-repeat
[10] https://learn.parallax.com/node/2269