Over a million developers have joined DZone.
Platinum Partner

Fun with Python and Silly Ciphers

· Web Dev Zone

The Web Dev Zone is brought to you in partnership with Mendix.  Discover how IT departments looking for ways to keep up with demand for business apps has caused a new breed of developers to surface - the Rapid Application Developer.

When I was a kid, I was really into secret codes and ciphers. I thought they were all kinds of fun. My mom thought it would be fun to use some of the ciphers I was so enamored with in treasure hunts for special occasions, like birthdays. She would take something like a Cryptograph Wheel and create codes with it that my brother and I would have to decode with our own wheel to find a gift or another clue. We used stuff where numbers would represent letters (a=1, b=2, c=3) or we would use a sliding scale where you move the alphabet one letter over so A=B, C=D, D=E, etc. Sometimes we’d create a code stick where you get a long string of paper and wrap it around a pencil and then write a message of the paper. It’s pretty much impossible to read when it’s unwrapped.

Anyway, I decided to create a silly cipher program with wxPython where I could input a string and have it convert it to something else. I also wanted my program to decode it too. Now you can’t really make a program that can use a Cryptograph Wheel or a code stick, but for number codes or slide scales, that’s extremely easy.

Creating an Encoding GUI


Creating the actual GUI is a piece of cake. It takes a little more work to code up the back end where you have to actually parse the string and change it into something else. For this exercise, I created 4 encoders and 3 decoders. The 5 encoders encode strings into numbers, ASCII (which is a different set of numbers), L33t (just for fun), and Hex. For the decoders, I decode everything except for L33t. Let’s take a moment to study the code for the GUI:

import controller
import wx
class CipherPanel(wx.Panel):
    def __init__(self, parent):
        wx.Panel.__init__(self, parent=parent)
        choices = ["", "Alpha2Num", "ASCII", "L33t", "Hex"]
        decode_choices = ["", "ASCII", "Hex", "Num2Alpha"]
        self.mainSizer = wx.BoxSizer(wx.VERTICAL)
        toLbl = wx.StaticText(self, label="Translate into:")
        self.toCbo = wx.ComboBox(self, value=choices[1], choices=choices)
        self.add2HSizer([toLbl, self.toCbo])
        fromLbl = wx.StaticText(self, label="Translate from:")
        self.fromCbo = wx.ComboBox(self, value="", choices=decode_choices)
        self.add2HSizer([fromLbl, self.fromCbo])
        txtLbl = wx.StaticText(self, label="Enter text to encode or decode:")
        self.originalTxt = wx.TextCtrl(self, style=wx.TE_MULTILINE)
        self.translatedTxt = wx.TextCtrl(self, style=wx.TE_MULTILINE)
        encodeBtn = wx.Button(self, label="Encode")
        encodeBtn.Bind(wx.EVT_BUTTON, self.onEncode)
        decodeBtn = wx.Button(self, label="Decode")
        decodeBtn.Bind(wx.EVT_BUTTON, self.onDecode)
        # layout widgets
        self.mainSizer.Add(txtLbl, 0, wx.ALL, 5)
        self.mainSizer.Add(self.originalTxt, 1, wx.EXPAND|wx.ALL, 5)
        self.mainSizer.Add(self.translatedTxt, 1, wx.EXPAND|wx.ALL, 5)
        self.add2HSizer([encodeBtn, decodeBtn])
    def add2HSizer(self, widgets):
        Add widgets to a horizontal sizer
        sizer = wx.BoxSizer(wx.HORIZONTAL)
        for widget in widgets:
            sizer.Add(widget, 0, wx.ALL|wx.CENTER, 5)
        if isinstance(widgets[0], wx.Button):
            self.mainSizer.Add(sizer, 0, wx.CENTER)
    def onDecode(self, event):
        Decodes what's in the original text box to the encoding 
        specified and puts the result in the bottom text box
        from_value = self.fromCbo.GetValue()
        value_to_decode = self.originalTxt.GetValue()
        if from_value == "Hex":
            new_value = value_to_decode.decode("hex")
        elif from_value == "ASCII":
            value_to_decode = [int(i) for i in value_to_decode.split()]
            new_value = controller.convertFromASCII(value_to_decode)
        elif from_value == "Num2Alpha":
            value_to_decode = value_to_decode.split()
            new_value = controller.convertFromNumbers(value_to_decode)
    def onEncode(self, event):
        Encodes what's in the original text box to the encoding 
        specified and puts the result in the bottom text box
        to_value = self.toCbo.GetValue()
        value_to_encode = self.originalTxt.GetValue()
        if to_value == "Hex":
            new_value = value_to_encode.encode("hex")
        elif to_value == "ASCII":
            new_value = controller.convertToASCII(value_to_encode)
        elif to_value == "L33t":
            new_value = controller.convertToLeet(value_to_encode)
        elif to_value == "Alpha2Num":
            new_value = controller.convertToNumbers(value_to_encode)
class CipherFrame(wx.Frame):
    def __init__(self):
        title = "Cipher Creator / Translator"
        size = (800,600)
        wx.Frame.__init__(self, None, title=title, size=size)
        panel = CipherPanel(self)
if __name__ == "__main__":
    app = wx.App(False)
    frame = CipherFrame()

We should probably take a couple moments and break this down. As you probably noticed in the screenshot earlier, this GUI has a couple of ComboBoxes, a couple multiline TextCtrls and two buttons. The ComboBoxes control what we’re encoding or decoding to. We have imported a mysterious module called “controller” which houses all the code that does the encoding. We’ll look at that in a minute. First, we need to look at a couple of the functions in this piece. In the onEncode method, we should how to grab the value we’re going to encode and pass it to the appropriate controller function. For the onDecode method, we sometimes have to do a little pre-processing before we pass the data on to the controller. You can see an example in the ASCII portion of the conditional where we have to create a list of integers or in the Num2Alpha section where we need to create a list of numbers. Once you understand what’s going on here, feel free to continue to the next piece of code below.

def convertToASCII(string):
    output = []
    for letter in string:
        output.append( ord(letter) )
    return " ".join([str(i) for i in output])
def convertToCeasar(string):


    Shifts the alphabet 3 places such that A becomes X,
    B becomes Y, etc
    ceasar_dict = {"a": "x", 
                   "b": "y",
                   "c": "z",
                   "d": "a",
                   "e": "b",
                   "f": "c",
                   "g": "d",
                   "h": "e",
                   "i": "f",
                   "j": "g",
                   "k": "h",
                   "l": "i",
                   "m": "j",
                   "n": "k",
                   "o": "l",
                   "p": "m",
                   "q": "n",
                   "r": "o",
                   "s": "p",
                   "t": "q",
                   "u": "r",
                   "v": "s",
                   "w": "t",
                   "x": "u",
                   "y": "v",
                   "z": "w"}
    new_string = ""
    for char in string:
        if char == ' ':
            new_string += ' '
            new_string += ceasar_dict[char.lower()]
    return new_string
def convertToLeet(string):
    leet_dict = {"a":"4", "b":"8", "e":"3", "l":"1",
                 "o":"0", "s":"5", "t":"7"}
    new_string = ""
    for letter in string:
        if letter.lower() in leet_dict:
            letter = leet_dict[letter.lower()]
        new_string += letter
    return new_string
def convertToNumbers(string):
    Convert a string to numbers where a=1, b=2, c=3, etc
    keys = "abcdefghijklmnopqrstuvwxyz"
    values = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11',
              '12', '13', '14', '15', '16', '17', '18', '19', '20', 
              '21', '22', '23', '24', '25', '26']
    num_dict = dict(zip(keys,values))
    new_string = ""
    for letter in string:
        if letter.lower() in num_dict:
            new_string += num_dict[letter.lower()] + " "
    return new_string
def convertFromASCII(data):
    Convert from ASCII
    s = ""
    if isinstance(data, str):
        for item in data.split():
            s += chr(int(item))
        # can also convert a list of integers
        for item in data:
            s += chr(item)
    return s
def convertFromCeasar(string):
    Converts string from Ceasar to normal
    unceasar_dict = {"x": "a",
                     "y": "b",
                     "z": "c",
                     "a": "d",
                     "b": "e",
                     "c": "f",
                     "d": "g",
                     "e": "h",
                     "f": "i",
                     "g": "j",
                     "h": "k",
                     "i": "l",
                     "j": "m",
                     "k": "n",
                     "l": "o",
                     "m": "p",
                     "n": "q",
                     "o": "r",
                     "p": "s",
                     "q": "t",
                     "r": "u",
                     "s": "v",
                     "t": "w",
                     "u": "x",
                     "v": "y",
                     "w": "z"}
    new_string = ""
    for char in string:
        if char == ' ':
            new_string += ' '
            new_string += unceasar_dict[char.lower()]
    return new_string
def convertFromNumbers(data):
    Convert a list of numbers to letters where 1=a, 2=b, 3=c, etc
    keys = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11',
            '12', '13', '14', '15', '16', '17', '18', '19', '20', 
            '21', '22', '23', '24', '25', '26']
    values = "abcdefghijklmnopqrstuvwxyz"
    num_dict = dict(zip(keys,values))
    new_string = ""
    for char in data:
        val = num_dict[char]
        new_string += val
    return new_string
if __name__ == "__main__":
    x = convertToASCII("I love you")
    y = convertToLeet("I love you")
    print x
    new_x = [int(i) for i in x.split()]
    print convertFromASCII(new_x)
    print convertFromASCII(x)
    x = convertToCeasar("Meeting tomorrow at station")
    print "Meeting tomorrow at station =>", x
    print "%s =>" % x, convertFromCeasar(x)
    t = convertToNumbers("Python rules")
    print "Python rules => " + t
    print "%s => " % t, convertFromNumbers(t.split())

You may notice that I have included other examples in the controller that aren’t currently hooked into the GUI. For example, I have a converter function in there to convert strings to Ceasar, which is a popular cipher where the alphabet is shifted 3 places in one direction or the other. Anyway, one of the nicest bits about this code is that we didn’t need to import anything. It all works with just normal Python! To convert to ASCII, we use Python’s builtin ord. For most of the others, we use dictionaries to map the values. At the bottom of the script, we have a few test cases to check and make sure it is converting the strings as we expect it to. These should probably be made into real unit tests, but for a quick and dirty check, these work great!

Wrapping Up

There are several other codes I’d like to add to this at some point, such as Morse code. I also like the codes where the message is hidden in the text, such as every first letter (or last) in a poem spells our something or where every letter at the beginning of a sentence spells something. Those were always fun. I’ve included a few links at the end of this article so you can read about other fun codes and ciphers. Have fun coding!

Additional Reading

Download the Source!

The Web Dev Zone is brought to you in partnership with Mendix.  Learn more about The Essentials of Digital Innovation and how it needs to be at the heart of every organization.


Published at DZone with permission of Mike Driscoll , DZone MVB .

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}