tim.clifford.io

The blog of an aspiring technologist.

I'm a web developer.

I'm the lead developer behind The Gleaners Interface.

I got my start in web development making fast Wordpress Spinups for non-profits.

I run an irc server and spend time asking and answering questions in the popular #django and #python channels on freenode.

I live in Vermont, and love to travel.

My Github Repos

A Solution A Day

I'm a big fan of the new Problem Of the Day website, despite some of the problems bringing out my bull headed side.

Yesterday's problem was pretty straightforward, to take a string and list the frequency each character appeared in it, and then bonus points to visualize the data afterwards. The problem can be found here.

Despite the use of single-character variables, I thought my code ended up being rather elegant, as it is two functions, the second of which makes good use of both the lambda function and reversed.

def counter(input_string):
    dictionary = {}
    for l in input_string:
        if l != " ":
            dictionary[l] = dictionary.get(l, 0) + 1
    return dictionary

def graph(input_string):
    data = counter(input_string)
    tdata = data.items()
    tdata.sort(key=lambda tup: tup[1])
    for tup in reversed(tdata):
        print tup[0], "."*tup[1]

By way of forinstance re:bull-headedness, this problem asks the viewer to create a simple encryption and decryption system for the Vigenere cypher. This isn't a particularly tough problem, but then as an afterthought it asks for code designed to break an encrypted phrase, given that the key is 5 characters or less.

This is where the bull headedness comes in. The vigenere cypher isn't used to encrypt web traffic for a number of reasons, one of which is that it is a relatively weak encryption standard. How and why, I don't know, and I'm sure I could look it up and make this problem easier to solve for myself.

But would it be that hard to brute force?

5 characters isn't that many, and it would have the added bonus of having thought of a correct, working solution at the outset. Let's start with the initial solution, a cipher and decipher.

from string import ascii_lowercase, lower


def cipher(key, message):
    key = lower(key)
    message = lower(message)
    key_index = 0
    output = ""
    for letter in message:
        letter_number = ascii_lowercase.find(letter)
        key_number = ascii_lowercase.find(key[key_index])
        cipher_number = (letter_number + key_number) % 26
        output += ascii_lowercase[cipher_number]
        key_index = (key_index + 1) % len(key)
    return output

#cipher("reddit", "todayismybirthday")
##'ksgdgbjqbeqkklgdg'


def decipher(key, message):
    message = lower(message)
    key_index = 0
    key = lower(key)
    output = ""
    for cipher in message:
        cipher_number = ascii_lowercase.find(cipher)
        key_number = ascii_lowercase.find(key[key_index])
        letter_number = (cipher_number-key_number) % 26
        output += ascii_lowercase[letter_number]
        key_index = (key_index + 1) % len(key)
    return output

I wrote that code a few months ago, I think comparing it against the solution I wrote yesterday is a good example of professional growth. Next up, a simple key generation method. As 5^5 isn't really that many, I decided it was better to use literally every possible character solution rather than simply dictionary words, as proper nouns (like "REDDIT" in the linked example) would be overlooked.

import itertools


def any_letters(max_length=2):
    for i in range(1, max_length+1):
        for j in itertools.permutations(ascii_lowercase, i):
            yield "".join(j)

Ok, that seems to be working as expected. Now looking at my old code, I apparently gave up after finding a bunch of false positives based on how I tested the resulting potential plaintext. Come to think of it, proper nouns (as well as abbreviations and nonsense words) throw a pretty big wrench into the problem.

But I'll leave that for the next post.