One of my first projects in C (some years back) was to write a simple frequency analyser and a transliterator, to help me solve the silly little Ceasar ciphers in the Scene section of the local weekly. I had some fun with this (I think I even took the somewhat more difficult step of writing a word-based frequency analyser later that summer), so when I started learning Perl, I naturally looked around for something similar to play with, and decided that implementing the Playfair Cipher (as depicted in Have his Carcass) would be an appropriate amount of fun, and help me learn my way around Perl while I was at it.

Somehow, though, I never got around to finishing it. So, almost three years later, I've now dug it up and polished it up as best I can, and made it work (as far as I can tell, anyway), and now here it is, for your amusement and (perhaps) enjoyment.

#!/usr/bin/perl use strict; use warnings; use Getopt::Std; my $USAGE = "playfair.pl [-o outfile] [-i infile] [-d | -e] [ keyword [infiles +] ]"; my $opt_OK = getopts('hdeo:i:',\my %opts); if (!$opt_OK || $opts{h}) { die "Usage:\n\n$USAGE\n\n"; } my $decode = $opts{d}; # -e does nothing, being the default if ($opts{o}) { open STDOUT, '>', $opts{o} or die "Unable to open $opts{o} for output: $!\n"; } if ($opts{i} ) { open STDIN, '<', $opts{i} or die "Unable to open $opts{i} for input: $!\n"; } my $keyword = lc (shift) || "playfair"; $keyword =~ tr/j/i/; my (@square,%letters); my $i = 0; for (split (//, $keyword), "a".."i", "k".."z") { next if $letters{$_}; my ($row,$col) = (int $i/5, $i % 5); $letters{$_} = [$row, $col]; $square[$row][$col] = $_; $i++; } # But in the Latin alphabet, Jehovah begins with an I! $letters{j} = $letters{i}; my $SHIFT = $decode ? -1 : -4; # let Perl handle the wrapping for us my (@input,@output); while (<>) { push @input, lc =~ /[a-z]/g; while ( @input > 1 ) { my ($let1,$let2) = splice @input,0,2; if ($let2 eq $let1) { unshift @input, $let2; $let2 = ($let2 eq 'x') ? 'z': 'x'; } push @output, transcribe ($let1,$let2); } } if (@input) { push @output, transcribe ($input[0],($input[0] eq 'x') ? 'z': 'x') +; } $" = ''; $, = ' '; $\ = $/; @output = map {$decode ? lc : uc } "@output" =~ /[a-z]{1,5}/g; print splice @output,0,5 while @output; sub transcribe { my @in1 = @{$letters{+shift}}; my @in2 = @{$letters{+shift}}; my (@out1,@out2); if ( $in1[0] == $in2[0] ) { $out1[0] = $out2[0] = $in1[0]; $out1[1] = $in1[1] + $SHIFT; $out2[1] = $in2[1] + $SHIFT; } elsif ( $in1[1] == $in2[1] ) { $out1[1] = $out2[1] = $in1[1]; $out1[0] = $in1[0] + $SHIFT; $out2[0] = $in2[0] + $SHIFT; } else { @out1 = ($in1[0], $in2[1]); @out2 = ($in2[0], $in1[1]); } ($square[$out1[0]][$out1[1]],$square[$out2[0]][$out2[1]]); }
=pod =head1 NAME playfair.pl - an encryption/decryption tool inspired by Dorothy Sayers +. =head1 Description Encodes or decodes input according to the Playfair cipher, as describe +d in Dorothy Sayers' I<Have His Carcass>, among other places. In accord +ance with amateur cryptographic convention, ciphertext is printed in 5-char +acter uppercase groups, while decoded text is printed in lowercase 5-charact +er groups (whitespace and punctuation are lost in this process). =head1 Usage playfair.pl [-o outfile] [-i infile] [-d | -e] [ keyword [infiles] ]
=head2 Options =over 4 =item B<-e> Input should be treated as cleartext and encoded (default). =item B<-d> Input should be treated as ciphertext and decoded. =item B<-i> infile Input file (possibly useful if not specifying a keyword). See also B<infiles>, below. =item B<-o> outfile Output file. Defaults to standard output. =back =head2 Arguments =over 4 =item keyword The keyword for the cipher (in Peter Wimsey's case, this was "Royalty" +). If none is supplied, defaults to "playfair" (as in the example below). =item infiles Any number of files may be listed on the command line after the keywor +d: no break will be made in the output between input files. Overrides th +e B<-i> option.If no files are specified, the program reads from the standard input. =back =head1 Disclaimer B<NOTE WELL>: the Playfair cipher can be solved, for an arbitrary keyw +ord, with paper and pencil. It is not what you would call "secure". Any ill ef +fects accruing to the user as a result of using it as if it were are entirel +y the user's responsibility. =head1 The Playfair Cipher =head2 The Square The square is produced by entering the letters of the keyword, followe +d by the remaining letters of the alphabet (in standard order) into a 5x5 m +atrix. (I and J are considered equivalent in this cipher.) With a keyword of "Playfair", the result is this: --------------------- | P | L | A | Y | F | --------------------- | IJ| R | B | C | D | --------------------- | E | G | H | K | M | --------------------- | N | O | Q | S | T | --------------------- | U | V | W | X | Z | --------------------- B<Note>: a slightly more complicated method exists for building the sq +uare (I would guess it is the more authentic), based on a transposition of PLAYFAIR BCDEGHKM NOQSTUVW XZ (yielding a first row of C<P B N X L>). It was not used by Dorothy Sa +yers' murder victim, and hence is not used in this program, though it would +be relatively simple to implement. =head2 Encryption Rules The Playfair cipher is a digraphic cipher: rather than simply translat +ing each letter of input to a unique letter in the output, we translate pairs o +f letters. In the case where two consecutive letters are identical, or where the +cleartext has an odd number of letters, a blank of some kind must be inserted: by co +nvention, this blank is X (in the case where a blank must be inserted after an X +, we insert a Z, because it seems as reasonable a response as a double X in the cl +eartext deserves). =over 4 =item * In the most general case, each letter is replaced by the letter in its own row and in the column of the other letter in the digraph. LW -> AV RT -> DO TA -> QF =item * If the two letters of the digraph are on the same row in the chart, each should be replaced by the letter immediately to its right (wrappi +ng as necessary). OS -> QT GH -> HK TO -> NQ =item * If the two letters are in the same column, each should be replaced by +the letter immediately below it (again wrapping as necessary). GO -> OV UE -> PN =back =head2 Decryption Rules Are left as an exercise to the reader. ;-) =head1 History The Playfair Cipher was invented by Sir Charles Wheatstone in the neighborhood of 1850, and popularized by his friend Lyon, First Baron Playfair. It saw substantial use as a field cipher in the British and + American militaries over the following century (including by the celeb +rated commander of PT-109), owing to its ease of use and the modest difficul +ty it presented to cryptanalysts (field ciphers need only stand up to att +ack for a matter of hours, rather than the days or weeks of higher-level c +odes). =head1 Author ChemBoy (C<chemboy@perlmonk.org>). =cut



If God had meant us to fly, he would *never* have given us the railroads.
    --Michael Flanders

Replies are listed 'Best First'.
Re: Dubious Cryptography: Playfair Cipher à la Sayers
by hv (Prior) on Apr 21, 2003 at 12:52 UTC

    This looks very interesting. The only time I've ever come across these ciphers is as part of a crossword; a typical example would be where the solutions to some half a dozen marked clues need to be enciphered before entry into the grid, using some keyword to be determined (by looking at the checking letters, and spotting a connection with thematic elements).

    Of course, when solving such puzzles this program would be of limited use - the majority of the effort goes into finding the keyword. It might be interesting to attempt a solver, that given some (possibly partial) pairs of plain and enciphered words would attempt to find possible keywords. Since there are many possibilities even when the encipherments are fully known (since the cipher square can be rotated horizontally and vertically without changing the cipher), the aim would be to minimize the length of the keyword by shifting the longest possible set of ordered letters to the end.

    It always surprises me how little information is required to crack a Playfair cipher; just for fun, here's what was provided in a recent crossword once you'd solved all the clues:

    HANDEL => SL??HO LEHRER => OH??KN MOZART => NL??MZ CHOPIN => LK??DR
    with the additional information that the "odd man out" is "the source of the theme".

    One minor point: when used for this purpose, the program's habit of grouping letters by fives is a bit irritating; it'd be more convenient in this case for it instead to preserve the whitespace of the original text. Then you could supply "Handel Lehrer Mozart Chopin" as input and get back the ciphertext in the form "xxxxxx xxxxxx xxxxxx xxxxxx". Losing the handling of odd letters and repeats, simplifying to this main loop would achieve that:

    while (<>) { while (/\G(\W*)(\w)(\W*)(\w)(\W*)/gc) { my($out2, $out4) = transcribe($2, $4); print $1, $out2, $3, $out4, $5; } print $1 if /\G(.*\n?)$/gc; }

    Hugo
Re: Dubious Cryptography: Playfair Cipher à la Sayers
by AssFace (Pilgrim) on Apr 29, 2003 at 14:58 UTC
    If you haven't seen it yet, the Poe Cipher is a fun thing.

    I broke that... unfortunately the winner of the contest broke it 6 weeks before me and 3 or so other people on a mailing list that I ran broke it prior to me as well.
    I largely used Perl and was pretty much how I learned Perl. (the ones that solved it faster were all using C)

    It was a great deal of fun and allowed me to learn a lot about cryptography. Along the way I also learned about genetic and hillclimber algorithms, which I have as of late been using a lot in financial analysis (and from there learned Neural Nets).

    Things like this are great to me - just when I feel that I have hit a wall in things to learn - I find something new and it spawns off a series of new things to learn. Keeps it interesting.

    -------------------------------------------------------------------
    There are some odd things afoot now, in the Villa Straylight.