wxPython – Having Fun with Silly Ciphers

Posted by Mike on April 26th, 2013 filed in Cross-Platform, Python

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

cipher_trans

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):
        """Constructor"""
        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])
 
        self.SetSizer(self.mainSizer)
 
    #----------------------------------------------------------------------
    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)
        else:
            self.mainSizer.Add(sizer)
 
    #----------------------------------------------------------------------
    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)
        else:
            return
 
        self.translatedTxt.SetValue(new_value)
 
    #----------------------------------------------------------------------
    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)
 
        self.translatedTxt.SetValue(new_value)
 
########################################################################
class CipherFrame(wx.Frame):
    """"""
 
    #----------------------------------------------------------------------
    def __init__(self):
        """Constructor"""
        title = "Cipher Creator / Translator"
        size = (800,600)
        wx.Frame.__init__(self, None, title=title, size=size)
        panel = CipherPanel(self)
        self.Show()
 
#----------------------------------------------------------------------
if __name__ == "__main__":
    app = wx.App(False)
    frame = CipherFrame()
    app.MainLoop()

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 convertToCaesar(string):
    """
    http://www.wikihow.com/Create-Secret-Codes-and-Ciphers
    Shifts the alphabet 3 places such that A becomes X,
    B becomes Y, etc
    """
    caesar_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 += ' '
        else:
            new_string += caesar_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))
    else:
        # can also convert a list of integers
        for item in data:
            s += chr(item)
    return s
 
#----------------------------------------------------------------------
def convertFromCaesar(string):
    """
    Converts string from Caesar to normal
    """
    uncaesar_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 += ' '
        else:
            new_string += uncaesar_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 = convertToCaesar("Meeting tomorrow at station")
    print "Meeting tomorrow at station =>", x
    print "%s =>" % x, convertFromCaesar(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 Caesar, 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!

Print Friendly

  • xtian

    this would be really great in conjunction with Al Sweigart’s book Hacking Secret Ciphers with Python. Have you seen it?

    http://inventwithpython.com/blog/2013/04/15/hacking-secret-ciphers-with-python-released/

  • http://www.blog.pythonlibrary.org/ Mike Driscoll

    Actually, no I hadn’t seen that one. There are a couple of Python hacking books on Amazon that I keep hoping to review, but haven’t gotten around to. I’ll take a look at the one you mentioned.

  • http://www.blog.pythonlibrary.org/ Mike Driscoll

    Yeah, the code uses lowercase for variable names. Everywhere else, I capitalize it though. Glad you enjoyed the article!

  • Robin Dunn

    Python used to have an extension module called rotor, which emulated the Enigma device used in World War 2. Basically it was like multiple crypto wheels with adjustable offsets, all tied together where the result of translating one character was fed in to the next wheel, and so on, and the initial positions of the wheels was set by a code of the day, or something like that. Anyway, it was kind of fun but since it only foiled by-hand decryptions and wasn’t very secure when you put a little software on the job it was removed from Python in 2.4 (I think) so as to not mislead people that it was secure enough for modern use.

  • http://www.blog.pythonlibrary.org/ Mike Driscoll

    Really? Wow! I hadn’t heard about that.

  • http://www.blog.pythonlibrary.org/ Mike Driscoll

    Oh…you know, I didn’t think that looked right, but Firefox’s spell check didn’t catch it yesterday. I fixed the post, although not the downloadable code. Thanks!

  • Viktor Varietta

    Awesome article!

  • Adele

    I remember how much into secret codes I was :) Now I realize I should’ve looked into that and become a programmer, but it’s a little too late now. I should’ve dreamt big, as it says in this song. P.S. Robin Dunn, that’s pretty interesting what you said, I had no idea about that!