Categories
level 1 python

Python Affine Cipher (Substitution Cipher)

Affine ciphers are some of the most basic cryptography methods. An affine cipher is a way to encode your words into numbers. It technically falls into the category of “monoalphabetic substitution ciphers”. Like all substitution ciphers, affine ciphers have their weaknesses. I would never use this in a production environment, but it’s fun to play around with. In this post, we’re going to look at how to code up a Python affine cipher program.

The basic equation for an affine cipher is the same as that for a line. It is in the form of y = mx + b. In this case, y is the encoded value, m is the scaling value, x is the distance from the value of the letter “a”, and b is a custom offset/intercept. We are going to use a built-in function for our first affine cipher, then create a custom encoding using the y=mx + b format.

We will cover:

  • Encode a String with a Basic Affine Cipher
  • Decode a Simple Affine Cipher into a String
  • Testing Our Basic Python Affine Cipher
  • Creating a Custom Python Affine Cipher Encoding
  • Decoding a Custom Python Affine Cipher
  • How to Use a Custom Encode/Decode Affine Cipher
  • Summary of a Python Affine Cipher Program

Encode a String with a Basic Affine Cipher

To start off, we’re going to cover how to create a super basic Python affine cipher encoding. In this version, we use the built in ord function to get the ASCII value. We simply encode each letter as its ASCII value.

This encode function takes no parameters. Instead, it asks the user for a word to create an example affine cipher. After getting the input from the user, we create the encoded string, print it, and return it for later use. We test this by calling the function, which we’ll see in action later on.

# affine cipher using ascii values
def encode():
   word = input("Give a word to encode in an affine cipher: ")
   print("Your affine cipher encoded word: ")
   encoded = " ".join([str(ord(x)) for x in word])
   print(encoded)
   return encoded
 
encode()

Decode a Simple Affine Cipher into a String

Now that we’ve seen a simple encoding, let’s decode that encoding. The reason we used the ASCII value encoding with ord is simply because it’s easy to decode. Opposite of the ord function is the chr function which returns a character given an ASCII value.

The decode function takes no parameters either. Like the Python affine cipher encode function, it prompts the user. Unlike the encoding function, it asks for the encoded affine cipher – a list of numbers separated by spaces. However, the input function reads everything as a string. That’s why we have to use the int function to turn each number into an integer to feed to chr.

Once we have the decoded word, we print it out and then return it for later use. We test the decoding function for our affine cipher by simply calling it. We’ll take a look at the expected behavior below.

def decode():
   encoded_list = input("What is the encoded affine cipher? ")
   decoded = "".join(chr(int(x)) for x in encoded_list.split(" "))
   print(decoded)
   return decoded
 
decode()

Testing Our Basic Python Affine Cipher Program

We’ve already included the code for testing our basic Python affine cipher above. All we do is call the encode() and decode() functions. The image below shows the results when we cipher the word “ego”.

First the program will prompt us for a word, you can use any word you want. It then returns a series of numbers to you. Take those numbers and plug them into the decode message. The decoded word should be the same as the one you entered.

Basic Python Affine Cipher Example

Creating a Custom Affine Cipher Encoding

Now that we’ve learned how to do a basic affine cipher with ord and chr, let’s look at how to create a custom cipher. We’ll call this function, custom_encode and it takes up to five parameters. Four of these parameters are optional with default values. The only required parameter is the word being encoded.

Two of the other four parameters, m, and b represent the m and b in the linear function we talked about above. We start by creating an empty list to hold the encoded numbers. Then we loop through each of the characters and apply our custom affine cipher. Note that we use ord again to get the numerical value of our characters. 

Technically, you can subtract an ord on any character, it doesn’t have to be “a”. We use “a” as our base (a third optional parameter). The final optional parameter of our custom encoding is whether we want to return the list as a string or as a list. 

# take the distance from the letter 'a' as the intercept
# allow an entry, m, for the scaling
def custom_encode(word, m = 1, b = 0, base = 'a', return_string = False):
   encoded = []
   for char in word:
       x = ord(char) - ord(base)
       y = m * x + b
       encoded.append(y)
   if return_string:
       return ' '.join(encoded)
   else:
       return encoded

Decoding a Custom Python Affine Cipher

Now that we’ve created a custom Python affine cipher encoding, let’s create a custom decoder. The decoder will take almost the exact same parameters. The only difference is that instead of a word being the one required parameter, it is an encoding. We assume that the encoded value passed is a list. 

The other parameters to control the customization of the cipher, m, b, and base retain the same default values. Unlike the encoder though, the decoder will default to returning the value as a string.

First thing’s first, we create an empty list to hold the decoded letters. For each of the entries in the encoding list, we run the reverse affine cipher on it. That is, instead of y = mx + b, we solve for x. This gives us x = (y - b)/m. Note that we use the chr function again to get a character from an integer. Finally, we return an empty space joined version of the list as our default return value.

# x = (y - b)/m
def custom_decode(encoded, m = 1, b = 0, base = 'a', return_string = True):
   decoded = []
   for y in encoded:
       x = (y-b)//m
       x = chr(x + ord(base))
       decoded.append(x)
   if return_string:
       return ''.join(decoded)
   else:
       return decoded

How to Use a Custom Encode/Decode Affine Cipher

Now that we’ve created our own custom encoder/decoder combo, let’s look at how to use them. These functions don’t prompt the user, so we pass the values programmatically. We simply assign the output from the customer encoder to a variable, which we then pass to the decoder to get the word.

Here are three examples. Two of them show how to use the custom encoder/decoder, and the third shows how NOT to use them. The critical thing is to pass the same values for m and b to the decoder as you do to the encoder. We call this combination of values a “key”. The third example shows what happens when you use the wrong key, you get the wrong word back.

# basic
cipher = custom_encode("ego")
print(f"Encoded affine cipher is: {cipher}")
word = custom_decode(cipher)
print(f"Custom decoded affine cipher word is: {word}")
 
# customized
cipher = custom_encode("ego", m=2, b=3)
print(f"Encoded affine cipher is: {cipher}")
word = custom_decode(cipher, m=2, b=3)
print(f"Custom decoded affine cipher word is: {word}")
 
# incorrect
cipher = custom_encode("ego", m=2, b=3)
print(f"Encoded affine cipher is: {cipher}")
word = custom_decode(cipher, m=3, b=1)
print(f"Custom decoded affine cipher word is: {word}")

The code above should result in the output shown below. The first and second examples have different encodings but map to the same word. Meanwhile, the third example shows what happens when we use different keys for the encode and decode functions. 

Python Custom Affine Cipher

Summary of Python Affine Ciphers

Affine ciphers are monoalphabetic substitution ciphers. We replace a letter with a corresponding number. Traditionally, we would actually take the mod 26 values of the numbers because there are only 26 numbers in the alphabet. I chose not to do this. If you are so inclined, you can add this to the code, it may help you get more familiar with the code to edit it for your own outcomes.

We created two different Python affine ciphers. Both technically required the use of the ord and chr functions. ord converts characters to numbers and chr converts integers to characters. For the first, more basic, implementation, we simply used the ord and chr values of letters to get their value. 

In the custom implementation, we looked at how to implement the traditional y = mx + b affine cipher. For the custom encoding, we take a string as input and return the numerical value of each character in a list. For decoding, we take a list of integers as input and return the string that those encoded numbers represent.

We saw three examples of the custom encoding and decoding. Two of them show us how to use the key of m and b correctly. The third shows how we won’t get a word back when using mismatching keys.

More by the Author

I run this site to help you and others like you find cool projects and practice software skills. If this is helpful for you and you enjoy your ad free site, please help fund this site by donating below! If you can’t donate right now, please think of us next time.

Leave a ReplyCancel reply