In this post we’ll first analyze what we are trying to accomplish and then begin to create an extensible framework that will allow us to adapt our solver to many different kinds of ciphers.
Building the framework
Our main goal is to create an extensible interactive solver, so lets break down the similarities that all ciphers have. You’ve got to think really generic here. All ciphers have the following similarities:
- They manipulate some kind of text.
- They have an encryption algorithm.
- They have a decryption algorithm.
With these three building blocks we can start creating our base framework. Lets create a brand new folder that will contain all our source code and files that we will use. I’m going name my folder “CryptogramSolver”. Whenever we create new files or want to execute anything, it will be done from this location.
The Cipher Class
Start off by creating a brand new file called “cipher.py”. This file will contain our base class called “Cipher”. Since all ciphers will contain the building blocks from above, all our cipher classes will inherit from this base Cipher class.
Lets start off by creating our base Cipher class with the basic criteria from above.
Contents of cipher.py
class Cipher: def __init__(self): self.text = "" def decrypt(self, text=""): if text == "": return self.text else: return text def encrypt(self, text=""): if text == "": return self.text else: return text
The above code declares our Cipher class and sets the Cipher.text property to blank. The text property will contain whatever text we would like to encrypt or decrypt. If we want to encrypt the Cipher.text property we just call the Cipher.encrypt() method. The Cipher.encrypt() and Cipher.decrypt() methods will return a string that contains the encrypted or decrypted text. These methods will not modify the value that is in Cipher.text. This allows us to encrypt or decrypt the text many different times with different settings (keys, periods, etc.) without messing up our original text. Also note that we have an optional “text” argument for the Cipher.encrypt() and Cipher.decrypt(). This allows us to encrypt or decrypt text that is not currently attached to our Cipher object. We’ll discuss how this is useful in a later post.
Lets create another file that will be our little playground or sandbox if you will called “sandbox.py”. While we setup our framework we’ll use the sandbox to test our code and try out any new features we add. The “sandbox.py” file should contain the following:
Contents of sandbox.py
from cipher import Cipher con = Cipher() con.text = 'This is a test of the emergency broadcast system.' ciphertext = con.encrypt() print "Ciphertext: ", ciphertext print "Plaintext: ", con.decrypt(ciphertext)
Here we import the Cipher class from “cipher.py” and then create a new instance of our Cipher class and call it “con”. We then set the Cipher.text property and the display the returned value of Cipher.encrypt(). Lets see what we get when we execute our sandbox.py:
Ciphertext: This is a test of the emergency broadcast system. Plaintext: This is a test of the emergency broadcast system.
So Cipher.encrypt()_and _Aristocrat.decrypt() just return the Cipher.text property? That doesn’t seem very useful at all. Well, since the Cipher class is just our base class, it doesn’t have any knowledge about what kind of cipher we are using or how to interact with it. Any new classes that we create will override the Cipher.encrypt() and Cipher.decrypt() methods with their own methods that will perform the real encryption and decryption.
The Aristocrat Class
Alright! Now we can finally get to the fun stuff. Lets build our Aristocrat class. Start by creating a new file called “aristocrat.py” that contains the following:
Contents of aristocrat.py
import string from cipher import Cipher class Aristocrat(Cipher): def __init__(self): Cipher.__init__(self) self.ctkey = string.ascii_uppercase) self.ptkey = "-" * 26 def decrypt(self, text = ""): text = Cipher.decrypt(self, text) return self.process(self.ctkey, self.ptkey, text.upper()) def encrypt(self, text = ""): text = Cipher.encrypt(self, text) return self.process(self.ptkey, self.ctkey, text.lower()) def process(self, key1, key2, text): output = "" for char in text: if char in key1: output += key2[key1.index(char)] elif char.lower() in string.ascii_lowercase: output += "-" else: output += char return output
There isn’t a whole lot to our Aristocrat class yet but lets go over what we’ve got so far.
def __init__(self): Cipher.__init__(self) self.ctkey = string.ascii_uppercase self.ptkey = "-" * 26
Here we are calling the Cipher.__init__(self) so that our Aristocrat class will have all the same properties as the Cipher class. So we can use Aristocrat.text even thought it isn’t specifically in our class. That’s how class inheritance works so we won’t go into detail here as there are tons of other resources that could explain it better. Our class needs to have two different kinds of keys. We have Aristocrat.ctkey that contains the ciphertext key and Aristocrat.ptkey contains the plaintext key. Since we are initializing our keys, the Aristocrat.ctkey will start out with A-Z and the Aristocrat.ptkey will start out with twenty-six dashes. We set it up this way just as our primary goal for this class is to solve Aristocrats which means filling in the Aristocrat.ptkey with the correct plaintext characters.
If you wanted to decrypt a ciphertext letter, you would find what position it is in Aristocrat.ctkey and then use the character at the same position in Aristocrat.ptkey as the plaintext character. That is exactly what our decrypt method will do and encrypt is just the opposite.
Example: ctkey = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' ptkey = 'zyxwvutsrqponmlkjihgfedcba' ctkey = 'N' ptkey = 'm'
Now lets go over the Aristocrat.encrypt and Aristocrat.decrypt methods.
def decrypt(self, text = ""): text = Cipher.decrypt(self, text) return self.process(self.ctkey, self.ptkey, text.upper()) def encrypt(self, text = ""): text = Cipher.encrypt(self, text) return self.process(self.ptkey, self.ctkey, text.lower())
The Aristocrat.encrypt and Aristocrat.decrypt methods both call the Cipher.encrypt method to grab the text that they should use for encrypting or decrypting. We do this so that we execute all the same code that Cipher.encrypt would because if the text argument is blank, we want to use the Cipher.text property instead. Both of these methods call the same Aristocrat.process procedure with one small difference. The Aristocrat.encrypt passes in the Aristocrat.ptkey first and then Aristocrat.ctkey while the decrypt method does the opposite. This allows us to use the same method to encrypt and decrypt our text by simply swapping the keys.
Now comes the real meat of the class! The Aristocrat.process method does all the real work.
def process(self, key1, key2, text): output = "" for char in text: if char in key1: output += key2[key1.index(char)] elif char.lower() in string.ascii_lowercase: output += "-" else: output += char return output
The Aristocrat.process method goes through each character in the text argument and sees if it can find that character in key1. If it is found, it grabs the character in the same position in key2. If not found, it will display a dash if the character is A-Z or just print the character if it is anything else (ie: punctuation, spaces, etc.).
That is pretty much it. Lets change our sandbox.py file so we can use our new class.
Contents of sandbox.py
from aristocrat import Aristocrat con = Aristocrat() con.text = 'This is a test of the emergency broadcast system.' con.ptkey = 'zyxwvutsrqponmlkjihgfedcba' ciphertext = con.encrypt() print "Ciphertext: ", ciphertext print "Plaintext: ", con.decrypt(ciphertext)
Instead of using the Cipher class, we’ll use the Aristocrat class. We set the Aristocrat.ptkey to the value that we want and then display the encrypted text. Lets execute sandbox.py and see how things have changed:
python sandbox.py Ciphertext: GSRH RH Z GVHG LU GSV VNVITVMXB YILZWXZHG HBHGVN. Plaintext: this is a test of the emergency broadcast system.
Alright! Now we have a class that can encrypt and decrypt any Aristocrat cons! We can extend the Cipher class to add any extra functionality that we want all our cipher classes to have access to. In Part II we’ll finish up our framework by creating the generic CipherSolver class and the AristocratSolver class.