Automating the NSA Cryptochallenge

So I’ve been having some writer’s block recently and wanted to write about something fun!!

The “Fun Stuff” category is reserved for solving problems using code, that otherwise would be tedious to do. Or maybe sometimes for pranks or something.

Today’s post will be aimed at the NSA Cryptochallenge puzzles.

The cryptogram is hosted here, and is a great time killer for those who like puzzles.

If you’re not familiar with a cryptogram, it’s a word puzzle which has every letter swapped out for another one. For example, a cryptogram that says, “QB JY HVJX BT AWBPF VHS B INLX VKPNJVPBHG PQBHGT”, translates to, “HI MY NAME IS FRITZ AND I LOVE AUTOMATING THINGS”. Each “Q” is replaced with an “H”, each “B” is replaced with an “I”, etc.

Really helps create some brain wrinkles trying to figure out what the words are supposed to be.

For the NSA cryptochallenge, a conspicuous feature about it is that it’s online, which means it can be automated. And there are also probably some online utilities that help with decoding cryptograms too.

Let’s do it. But first let’s do it manually.

First, go to the site–the instructions say you can either type the letter replacements, or click the buttons in order to make replacements.

Go ahead and click “PLAY”. Once you arrive at the next screen, you’ll have the puzzle, along with the controls at the bottom, that look like a keyboard. The puzzle for this week looks like this: cryptochallenge puzzle

Per the instructions on the previous page, you can either type a pair of letters to make a replacement, with the first letter being the one to replace, and the second being what you want to replace it with, OR, you can do the same thing by clicking the buttons themselves.

Try a few replacements and see what it does. See if you can solve the puzzle too, might as well, right? 🙂

Next let’s work on automating it. There’s a previous post which describes the standard code I use for any UI-based automation written in Watir, in a section called “Boilermaker” (and there are also installation instructions if this is your first dip into the Watir). We’ll start by putting that in our code:

require 'rubygems'
require 'watir-webdriver'

def go_to_url(url)
 load_link($timeout_length){ $browser.goto url }

def load_link(waittime)
 Timeout::timeout(waittime) do
 rescue Timeout::Error => e
 fail "Page load timed out: #{e}"

def initialize_browser(type=:chrome)
 client =
 client.timeout = 600
 $browser = type, :http_client => client
 $timeout_length = 30 
 # $browser.driver.manage.window.maximize 

def close_browser
 # if there's a browser instance open, close it
 $browser.close if not $browser == nil

Next, let’s fire up a browser instance and go straight to the puzzle site itself, skipping past the opening screen:


Ok, next step is we need to get the encrypted message. We do that by looking for characteristics about the letters in the message itself, and seeing if we can collect them.

Right-click one of the letters of the message and let’s take a look at the DOM:

cryptochallenge domIt appears the characters in the puzzle all have a class of “class1”.

So in our code, we can iterate through all the elements with that class attribute, and collect all the characters into a string.

Let’s put that code in there and print out the string and see what we have:

message = ""
$browser.elements(:xpath => '//div[@class="class1"]').each do |character|
   message += character.text
puts message

The “puts message” at the end will print out the string we collated. It says:


Well… hm. It’s kind of run together. Apparently spaces aren’t handled quite right, as if they’re treated like empty characters and nothing’s done with them.

So let’s add a little more logic and rerun and see what we get:

message = ""
$browser.elements(:xpath => '//div[@class="class1"]').each do |character|
   if character.text == ""
      message += " "
      message += character.text
puts message



It’s better but still not quite right. Some words are getting run together. For example, OXJ WKZZKIHQOKJD are supposed to be two words but they’re getting stuck together. Likely this is because it’s a newline on the screen, but in the DOM it’s not treated as such.

Let’s look back at the DOM near the long word “WKZZKIHQOKJD” and see if there’s a characteristic we can tag on, to clean up this input:

cryptochallenge clear

Hey there we go, there’s a style of “clear:both;” that I bet is treated like some kind of newline character, or at least distinguishes between the encrypted text, and the text that would show up once replacements are made.

So let’s add just a little more logic and add a space every time we see that style:

message = ""
$browser.elements(:xpath => '//div[@class="class1"]').each do |character|
	style = character.attribute_value("style").to_s
	message += " " if style.include?("clear")
	if character.text == ""
		message += " "
		message += character.text
puts message

This yields:


There we go, much better.

Now we’re really gonna cheat, and use another site to decrypt the string. Let’s head on over to the site that does this:


Then let’s locate the element where we put in the encrypted message, by doing a right-click and Inspect Element on that textbox:

quipqiup puzzle textarea

which resolves to the following in code:

$browser.textarea(:xpath => '//textarea[@id="ciphertextId"]').when_present.set(message)

We’ll do the same thing for the Solve button on the far right–this yields the following code:

$browser.element(:xpath => '//input[@value="Solve"]')

When we solve the message, this site offers up the best matched solution at the top of the list in a table that appears at the bottom. So let’s paste the message in and see what solution comes up, and then grab the locator for that solution so we can harvest the text from it:

k xspj a wmjsa

i have a dream
Happy Martin Luther King, PR. day!

Well shoot. So the first hit there has “PR” instead of “JR”. I guess this site isn’t 100% all the time. But I’m gonna take it anyway. 

solution = $browser.element(:xpath => "//div[@id='sol0']").when_present.text.to_s

So now we have the message and the solution. Next step is to figure out what replacements to make. And we only need to make replacements on letters, not punctuation or spaces.

First let’s make the substitutions for any non-capital characters, for a blank. But also let’s split both of these results into their own arrays too:

message_array = message.gsub(/[^A-Z]/, "").split("")
solution_array = solution.gsub(/[^A-Z]/, "").split("")

The reason for splitting these into arrays is that Ruby has a function called “zip” that lets you iterate through two arrays simultaneously. We could of course write our own iterator, but this is quicker to write and is a little more elegant.

Now we can zip through both arrays and click the appropriate buttons to make replacements. The locator for the buttons is just an id with the letter in double quotes, like this:

$browser.element(:xpath => '//*[@id="A"]')

The zip loop would then be: do |character, replacement|
	$browser.element(:xpath => '//*[@id="' + character + '"]')
	$browser.element(:xpath => '//*[@id="' + replacement + '"]')

If we run this code, notice that it’s… kinda slow. It’s actually making the same replacement multiple times as it runs across that letter in these two arrays.

So let’s speed it up! How about making a hash that holds the replacements instead? We can recycle the zip loop we just made, for stocking the replacement hash: do |character, replacement|
   replacement_hash[character] = replacement

Then we just iterate through the replacement hash instead, and make the replacements that way:

replacement_hash.each do |character, replacement|
	$browser.element(:xpath => '//*[@id="' + character + '"]')
	$browser.element(:xpath => '//*[@id="' + replacement + '"]')

If you run this, you should notice a drastic improvement in how fast it fills out the solution.

Once done, we click the Submit button to finish the puzzle:

$browser.element(:xpath => '//input[@alt="Submit"]')

The final time’s been penalized with 10 seconds since there was a goof in there (PR. instead of JR.) but I’ll take that ding for the sake of having automated this 🙂

Next Steps

What did you learn from this post? What improvements can you make on the code? Can you solve the cryptogram in Ruby itself, without relying on another site? Would that make the solution faster?

Happy automating!
– Fritz

Welp, if the NSA wasn’t following me before, they probably are now. But so can you! 🙂

If you like reading stuff on here, consider clicking the button in the right sidebar. Anytime a new post is made, you’ll get an update. I appreciate your readership and thanks for stopping by!


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s