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: _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _