muonlab » F#

random .NET and web development musings

Following on from Robert Pickerings F# introduction at Progressive.Net in London last week, I was inspired to try and do something with this powerful functional language.

As an exercise I though’t I’d port some old code I wrote in Haskell several years ago, a simple Caeser Cipher cracker.

A Caeser Cipher works by shifting each letter of the alphabet by n positions. A shift of 3 would encode “ABCD” as “DEFG”. Due to this simple linear transform (y = x + c), the Caeser Cipher is incredibly easy to crack with frequency analysis. By shifting the ciphertext by 1-25 positions and comparing the frequency of each letter in the resulting plaintext candidate to a table of English letter frequencies, it is possible to easily discover the original plain text (assuming there is sufficient data to make the expected freqency distributions).

Here’s the code!

Disclaimer: This is the first F# code I’ve ever written, so don’t count on it being remotely decent. Suggestions for improvements are more than welcome!

#light

let alphabet = ['A'..'Z']

let getpos x xs = List.find_index ((=) x) xs

let let2nat c = getpos c alphabet

let nat2let i = alphabet.[i]

let shift i a =  nat2let ((let2nat a + i) % 26)

let encode i s = new string(Seq.map (fun c -> shift i c) s |> Seq.to_array)

let decode i = encode (26 - i)

let count c s = Seq.filter ((=) c) s |> Seq.length

let percent a b = (float a / float b) * 100.

let freqs ws = [for x in alphabet do 
                   yield percent (count x ws) (Seq.length ws)]

let table          = [8.2; 1.5; 2.8; 4.3; 12.7; 2.2; 2.0; 6.1; 7.0;
                        0.2; 0.8; 4.0; 2.4; 6.7; 7.5; 1.9; 0.1; 6.0;
                        6.3; 9.1; 2.8; 1.0; 2.4; 0.2; 2.0; 0.1]

let chisqr os es = 
    let chied = [for x, y in Seq.zip os es do
                       yield ((x - y) * (x - y)) / y]
    List.sum chied

let crack ws = 
    let wws = [for x in [0..25] do
                   yield chisqr (freqs (decode x ws)) table]
    let n = getpos (Seq.min wws) wws
    decode n ws
               
let ciphertext = encode 5 "THEQUICKBROWNFOXJUMPEDOVERTHELAZYDOG"

let plaintext = crack ciphertext

System.Console.WriteLine(ciphertext)
System.Console.WriteLine(plaintext)

This produces:

YMJVZNHPGWTBSKTCOZRUJITAJWYMJQFEDITL
THEQUICKBROWNFOXJUMPEDOVERTHELAZYDOG