Welcome Guest [Log In] [Register]
Welcome to Crypto. We hope you enjoy your visit.


You're currently viewing our forum as a guest. This means you are limited to certain areas of the board and there are some features you can't use. If you join our community, you'll be able to access member-only sections, and use many member-only features such as customizing your profile, sending personal messages, and voting in polls. Registration is simple, fast, and completely free.


Join our community!


If you're already a member please log in to your account to access all of our features:

Username:   Password:
Add Reply
Cracking Loki's Adfgvx Ciphertext (part 1)
Topic Started: May 24 2006, 11:49 AM (234 Views)
rot13
Elite member
[ *  *  *  *  * ]
Since we haven't done an ADFGVX here before, and this was the first time I ever tried one, I thought I'd share my approach. Hopefully someone has a better way.

The first goal is to untangle the transposition into the correct letter pairs. I decided to go with an Index of Coincidence approach. It allowed me to at least identify the likely pairings, although it did not necessarily mean that the letters were all in the right order. The idea is pretty simple - if you try to match together columns that don't match, the distribution of pairs should be a little more even. When you match the right columns together, the frequency of unique pairs should be closer to English, so the IC should be a good bit higher.

I hacked together a simple program in D (my new fave language for crypto tools) to try all possible permutations for key lengths from 3 to 8 (you can make it do more, but since 9 takes a good bit longer and I know now that the key length is 8, I dropped it back down). Here is the program:

Code:
 

import std.stdio;
import std.stream;

char[] best_result;
double best_ic = 0;
int best_depth;

char[] load_letters(char[] filename)
{
   char[] chars_in = cast(char[])std.file.read(filename);
   char[] chars_out;

   foreach (ch; chars_in)
   {
       if (((ch >= 'A') && (ch <= 'Z')) ||
           ((ch >= 'a') && (ch <= 'z')))
       {
           chars_out ~= ch;
       }
   }
   return chars_out;
}

int adfgvx_letter_to_number(char ch)
{
   if ((ch == 'A') || (ch == 'a')) return 0;
   if ((ch == 'D') || (ch == 'd')) return 1;
   if ((ch == 'F') || (ch == 'f')) return 2;
   if ((ch == 'G') || (ch == 'g')) return 3;
   if ((ch == 'V') || (ch == 'v')) return 4;
   if ((ch == 'X') || (ch == 'x')) return 5;
}

double compute_ic(char[] letters)
{
   int counts[36] = 0;
   int all_counts;

   for (int i=0; i < letters.length; i += 2)
   {
       int val = adfgvx_letter_to_number(letters[i]) * 6 +
                   adfgvx_letter_to_number(letters[i+1]);
       counts[val]++;
       all_counts++;
   }

   int sum = 0;
   for (int i=0; i < 36; i++)
   {
       sum += counts[i] * (counts[i]-1);
   }

   return (cast(double) sum) / (cast(double) (all_counts*(all_counts-1)));
}

void adfgvx_untramp(int[] order, int depth, int max_depth, char[] letters)
{
   if (depth == max_depth)
   {
       // If order is the original key permutation, reverse tells how
       // to get back to that permutation
       int reverse[20];
       for (int i=0; i < max_depth; i++)
       {
           reverse[order[i]] = i;
       }
       int num_per_row = letters.length / max_depth;
       int num_extra = letters.length % max_depth;

       char[] results[20];

       int pos = 0;

       for (int i=0; i < max_depth; i++)
       {
           // Grab the letters from the text
           results[reverse[i]] = letters[pos..pos+num_per_row];
           pos += num_per_row;

           // If this is one of the columns that had an extra letter, grab it
           if (reverse[i] < num_extra)
           {
               results[reverse[i]] ~= letters[pos++];
           }
       }


       // Put the columns back together row-by-row
       char[] result;
       for (int row=0; row < num_per_row; row++)
       {
           for (int col=0; col < max_depth; col++)
           {
               result ~= results[col][row];
           }
       }

       // Add in the columns that had extra letters
       for (int col=0; col < num_extra; col++)
       {
           result ~= results[col][num_per_row];
       }

       double ic = compute_ic(result);
       if (ic > best_ic)
       {
           writefln("Best ic now %f at depth %d", ic, max_depth);
           best_ic = ic;
           best_result = result;
           best_depth = max_depth;
       }
   }
   else
   {

   OrderLoop:
       for (int i=0; i < max_depth; i++)
       {
           for (int j=0; j < depth; j++)
           {
               if (order[j] == i) continue OrderLoop;
           }
           order[depth] = i;
           adfgvx_untramp(order, depth+1, max_depth, letters);
       }
   }
}

int main(char[][] args)
{
   char[] letters = load_letters(args[1]);

   writefln("Loaded %d letters", letters.length);
   int[20] order = -1;

   for (int i=3; i < 9; i++)
   {
       adfgvx_untramp(order, 0, i, letters);
   }
   writefln("Best key length = %d", best_depth);
   writefln("Best ic = %f", best_ic);
   writefln("Best order = ");
   for (int i=0; i < best_result.length; i += 2)
   {
       writef("%s%s ", best_result[i], best_result[i+1]);
   }
   writefln();

   return 0;
}


I run this program against the challenge text and this is the output:
Code:
 

Loaded 1168 letters
Best ic now 0.039105 at depth 3
Best ic now 0.039663 at depth 3
Best ic now 0.041331 at depth 4
Best ic now 0.043058 at depth 4
Best ic now 0.043451 at depth 6
Best ic now 0.043751 at depth 6
Best ic now 0.044021 at depth 8
Best ic now 0.044474 at depth 8
Best ic now 0.045690 at depth 8
Best ic now 0.047417 at depth 8
Best ic now 0.047622 at depth 8
Best ic now 0.048985 at depth 8
Best ic now 0.050136 at depth 8
Best ic now 0.054095 at depth 8
Best ic now 0.066566 at depth 8
Best ic now 0.074755 at depth 8
Best key length = 8
Best ic = 0.074755
Best order =
AX VX FA DD VX DV AX VX FA DD DG AF VV FF VD DV VX FF DD AX DD FA VV DV VV VX GA DV DD DG VX DV VF AA FV VD AF GX VX AA DV XF DV VV VF FA VX XA VF VX VF AA DD VA FV VF FA FV VX DD DD XF VD DD AX VX FA DD DG AF VV FF VD DV VX FF DD AX DD FA VV DV VV VX AX XF VD DD AX VX VX DD VF XF DG DV AF VA VV DD DD XF DD DG VF AA GX GX AA DD DD VX VX FF GA VF VX DD DD AX VX DG DD AX DV FA AX VX VV DD VX AF VX DV FF DD AX XF AA AF VX AX GF VF AF VF VX VD DD AX AF FA FF DG VX VV DV AX AV VX VV DV DD VV AX FF VV AF DD AV AA DG VV AX AF DV AA FF VA VF XF DD VX AF XA AX FA VV VX XA DD AX DV VV FF AF DV AF DV VA DD FA VV DV GX VX VX DV DG DD VD VF VF VV DD VA DD FA VV DV VV VX AX XF XF VF DG DD AV DD VV DV AF VV GA VD VD XA DD FF VD DG DV DD AX VX AF AX GX VA VF VF DD GV XA FF DV AV FF VD DV VV FF AF VF AA DD VA AF XF AX VX VV XA XA FA AX VX VV DD AF DV AF FF VA DV FA DV DG AF GX FF AA XA AF GV XV GX VX DV AX VX GX DD VV DV VA VX VA VF VD DD AV VX DV DD DD AA DV XF VA VV FF DV DV DD FF VD VF VD DV FA VX VX DD GX VF VX GV VF GX AV AA DV VV DD VX VF DD AX DV FA AA VX VA DV VX DD VX VF DD AX AF FA FF DG DV VV FF VD AF XF AX VV FF DD VF VX VF GF VD AF VD AF AX VX DG DD GF DD AF VF AF AA GA VD FA VV VX XA AX VX XV DD GX DV VX GX DG XA DD VD DV FF DV GA VD AF VX VV AF AX DV VA FF VD DD AX DV AX VX FF FV VF XV GX XF DV XV DV DD AX AX VX VD DD DD XF VX VD VF VX AX VX FA DD DV DD VX VV FA VV VX XA VF VV VD VF DV AX VX FF FA VF DV DD DV VX DD DG DG VX DV DD VF VX DD DG VV GX VX DD DD AX XF XV XA VF FF GX DV AX DD XD VF VX VD DG AF AX VX VA AV VF DD AF DD AA DV VV VV AX AF DV VX FF DD AX DV FA AF VX DD VV VD DD XF VF DD AX AX VX VX DV VV AF DD VD VX AF DD AX VF DG DD VD AX VX VD AF VD GA DG VF AX VX VF DD AX VX DG DD DV AX VD VV FV VF AF DG VD DD VV FF


Since people might want to try cracking it from here, I'll put the rest of my steps in a second posting.

Offline Profile Quote Post Goto Top
 
1 user reading this topic (1 Guest and 0 Anonymous)
« Previous Topic · General · Next Topic »
Add Reply