A basic perl script that encrypts a message with a given key using the playfair cipher. Instead of having a 2 dimensional array I use a hash and have a 2 way mapping of coordinate to character "0,0"->"a" and character to coordinate "a"->"0,0". This allowed me to easily use the same datastructure to lookup a character's position and find the character at a given coordinate.

#!path-to-perl use warnings; use strict; die "usage: playfair <'key'> <'message'>" unless(@ARGV == 2); my $key = lc($ARGV[0]); my $message = lc($ARGV[1]); my @alphabet = qw(a b c d e f g h i k l m n o p q r s t u v w x y z); my %square_hash = (); my $x; my $y; #replace all 'j's with 'i's $key =~ s/j/i/g; #build a key with unique letters (no repeats) while($key =~ s/(.)(.*)\1/$1$2/) {} print "Reduced key: $key\n"; #form the mapping my @keys = split(//,$key); $x = 0; $y = 0; for my $char (@keys) { $square_hash{$x . "," . $y} = $char; $square_hash{$char} = $x . "," . $y; $x++; if($x==5) { $x=0;$y++;} } #put in the rest of the letters for my $char (@alphabet) { next if exists($square_hash{$char}); $square_hash{$x . "," . $y} = $char; $square_hash{$char} = $x . "," . $y; $x++; if($x==5) { $x=0; $y++;} } print "5x5 playfair square:\n +-----------+\n"; for $y (0..4) { print " | "; for $x (0..4) { print $square_hash{$x . "," . $y} . " "; } print "|\n"; } print " +-----------+\n"; #change all j's to i's $message =~ s/j/i/gi; #remove spaces from the message $message =~ s/\s+//g; #insert an X in between identical letters $message =~ s/(.)(\1)/$1x$2/g; $message =~ s/(.)(\1)/$1x$2/g; #make it even length $message .= "x" if length($message)%2==1; print "Formatted message: $message\n"; #now encrypt everything my $encrypted = ""; while($message =~ s/^(.)(.)//) { my @xy1 = split(/,/,$square_hash{$1}); my @xy2 = split(/,/,$square_hash{$2}); #3 rules. 1: same row. if($xy1[0] == $xy2[0]) { #same col. move down 1 $xy1[1] = ($xy1[1]+1)%5; $xy2[1] = ($xy2[1]+1)%5; } elsif($xy1[1] == $xy2[1]) { #same row move right 1 $xy1[0] = ($xy1[0]+1)%5; $xy2[0] = ($xy2[0]+1)%5; } else { #same corners. chose element on other column same row. my $tempcol = $xy2[0]; $xy2[0] = $xy1[0]; $xy1[0] = $tempcol; } $encrypted .= $square_hash{join(",",@xy1)}; $encrypted .= $square_hash{join(",",@xy2)}; } $encrypted = uc($encrypted); print "Encrypted message E:\n$encrypted\n"; #OUTPUT # .\playfair "playfairexample" "hide the gold in the tree stump" # Reduced key: playfirexm # 5x5 playfair square: # +-----------+ # | p l a y f | # | i r e x m | # | b c d g h | # | k n o q s | # | t u v w z | # +-----------+ # Formatted message: hidethegoldinthetrexestump # Encrypted message E: # BMODZBXDNABEKUDMUIXMMOUVIF

The playfair cipher should not be used in a modern encryption scheme. It is solely a classic cipher. It is vulnerable to frequency analysis (digraph rather than monograph) yet is still quite secure if short messages are encrypted.

I hope to post a better-than-bruteforce decryption algorithm shortly.

I welcome any and all comments regarding ways I could improve my Perl scripting.

Replies are listed 'Best First'.
Re: Playfair cipher
by ww (Archbishop) on Jun 22, 2011 at 21:30 UTC
    This response may be merely intuitive... AND wrong! (I am neither a cryptographer nor a particularly good mathematician.) Nonetheless, here goes...

    It seems to me (YMMV . caveat above) that splitting duped chars by a variety of little used chars (or, maybe most frequently used chars) has certain merit... because it makes usage-frequency testing slightly more complex and may thus throw off any brute force using frequency testing.

    #!/usr/bin/perl #!/usr/bin/perl use strict; use warnings; use 5.012; # See 910956.pl (re playfair cipher) for salt, key and encoding -- # as a follow on, modify encoding with possibility of multiple # (i,e. "varying" or "inconsistent") insertions between dup letters my @range = ("Q","V","X","Y","Z",); my $seen = ""; my $message = uc(<>); $message =~ s/[^A-Z0-9]//gi; # remove punct, etc $message =~ s/j/i/gi; $message =~ s/q/O/gi; # replace [Qq] with [Oo] $message =~ s/\s+//g; # remove spaces $message .= "X" if length($message)%2==1; # make length an even va +lue my @char = split('', $message); say "\n" . "-" x10 . "\n"; for my $char(@char) { if ($seen eq ($char)) { my $i = int(rand(4)); my $letter = $range[$i]; print $letter . $char; # recode as push to encoded arra +y for later printing } else { # as five char groups print ($char); } # recode: ditto $seen = $char; } say "\n";

    The tested I/O includes (with apologies for the pre tags below)

    Quoth the Raven, 'Nevermore!' ...or three (3) or four (4) words to that generally accepted effect.
    ^            ^         ^    ^^^^^    ^^^                ^^^^ ^^^     ^^^^   ^
    # obviously, this has limitations:
    The SECRET stash is located cinq feet below ground at 41.7N, 73.4W. Its key is X734ab#@17ffyj.
    # meaning determinable from context          ^^^ ^^^
    # but the new key is corrupted...                                ^^^ ^^^ ^^
      my $message = uc(<>); $message =~ s/[^A-Z0-9]//gi; # remove punct, etc $message =~ s/j/i/gi; $message =~ s/q/O/gi; # replace [Qq] with [Oo] $message =~ s/\s+//g; # remove spaces
      • You UPPER case the contents of $message at the beginning so the use of the /i option seems strange.    Why not just use upper case letters in the pattern?
      • Your comment says "replace [Qq] with [Oo]" but that is not what your code is doing.
      • Your last substitution attempts to remove "spaces" which were already removed by your first substitution
      • And your code would be more efficient using transliteration instead of substitution:
      my $message = uc <>; $message =~ tr/A-Z0-9//cd; # remove punct, etc $message =~ tr/JQ/IO/;


      my @range = ("Q","V","X","Y","Z",); ... my $i = int(rand(4)); my $letter = $range[$i];

      Your @range array contains five elements but you are only accessing the first four of them.

      That is usually written as:

      my @range = ("Q","V","X","Y","Z",); ... my $letter = $range[ rand @range ];

        Right on all counts!

        TY and ++

        Coder (yeah, /me) abandoned all good practice by writing a little code; getting another (allegedly) bright idea; cutting/tweaking/adding functions/routines/code without careful review of previous code (and then 'rinsing, repeating').

        The initial idea may or may not be worth anything; the procedures by which this code was written deserve to be castigated far more harshly than the gentle jwkrahn did. But, gentle reader, future development should attend carefully to those wise observations.

      Thanks for your comment ww. I wanted to comment on the merit of using a group of "dummy" characters (as they are called) rather than a specific character. The reason for using a dummy character in the first place is two-fold. (1) to prevent the creation of a new rule for handling characters in the same square and (2) to prevent simpler cryptoanalysis (english is full of double letters "mm oo ee (ww? :) )" . The addition of a single dummy character satisfies both of these requirements.

      What your approach does is further confuse the relationship between duped chars and their encoding. This might make the statistical attack easier harder if the playfair cipher did not suffer from a much more significant weakness.

      This weakness is the fact that E(AB) = reverse(E(BA)) (where E = the encrypting function). This means that words like DEceivED and REceivER and DEpartED will maintain a AB(..)+BA pattern. This is how most statistical attacks of a playfair cipher begin.

      It is interesting to note that the fact that a double letter will never appear in the encrypted message actually weakens the cipher for the mere fact that a message with no double letters is extremely suggestive that the encryption is a playfair cipher.

      Thanks for your response. Your idea can be easily adapted to helping the weakness of playfair which is that anytime a AB(..)+BA pattern is found replace it with a AXB(..)+BA (or a AB(..)+BXA to make the statistical attack weaker.

      Update: Mistyped above. Text is struck out.