Playfair cipher: Difference between revisions

From Rosetta Code
Content added Content deleted
m (→‎{{header|REXX}}: flushed out a couple of program comments, cleaned up an assignment, added a comment. -- ~~~~)
(→‎{{header|REXX}}: re-worked the Playfair algorithm to account for the use of doubled chars in the source, and massage the decrypted encryption to account for extra "X"es. -- ~~~~)
Line 362: Line 362:
Quite a bit of the REXX code deals with error checking, accepting arguments, and displaying the options used, and displaying input and output.
Quite a bit of the REXX code deals with error checking, accepting arguments, and displaying the options used, and displaying input and output.
<br>For ease of viewing and comparing, the output is in capitalized digraphs (which are really ''digrams'') as well as the original input(s).
<br>For ease of viewing and comparing, the output is in capitalized digraphs (which are really ''digrams'') as well as the original input(s).
<br>Thanks to Walter Pachl, this program is now sensitive of using a suitable ''double character'' when &nbsp; '''x''' &nbsp; is present in the cipher key.
<br>Thanks to Walter Pachl, this program is now sensitive of using a suitable ''double character'' when &nbsp; '''X''' &nbsp; is present in the cipher key.
<br>Also added a a fair amount of code to massage the decrypted encryption to remove doubled &nbsp; '''X'''es &nbsp; so as to match the original text.
<lang rexx>/*REXX program implements a PLAYFAIR cipher (encryption & decryption).*/
<lang rexx>/*REXX program implements a PLAYFAIR cipher (encryption & decryption).*/
parse arg key . 1 oldK omit _ . '(' text /*TEXT is the phrase to be used*/
parse arg key . 1 oldK omit _ . '(' text /*TEXT is the phrase to be used*/
Line 374: Line 375:
if length(omit)\==1 then call err 'OMIT letter must be only one letter'
if length(omit)\==1 then call err 'OMIT letter must be only one letter'
if \datatype(omit,'M') then call err 'OMIT letter must be a Latin alphabet letter.'
if \datatype(omit,'M') then call err 'OMIT letter must be a Latin alphabet letter.'
@abc='abcdefghijklmnopqrstuvwxyz'; @abcU=@abc
upper omit @abcU /*uppercase OMIT char & alphabet.*/
if pos(omit,newText)\==0 then call err 'TEXT can''t contain the "OMIT" character: ' omit
if pos(omit,newText)\==0 then call err 'TEXT can''t contain the "OMIT" character: ' omit
fill=space(translate(@abcU,, omit), 0) /*elide OMIT char from alphabet. */
upper omit /*uppercase the OMIT character.*/
xx='X'; if omit==xx then xx='Q' /*char used for double characters*/
fill=space(translate('ABCDEFGHIJKLMNOPQRSTUVWXYZ',,omit),0) /*elide omit*/
xx='X'; if omit==xx then xx='Q' /*char used for double characters*/
if length(newKey)<3 then call err 'cipher key is too short, must be ≥3 unique characters.'
if length(newKey)<3 then call err 'cipher key is too short, must be > 2 unique characters.'
fill=space(translate(fill,,newKey),0) /*remove any cipher characters. */
fill=space(translate(fill,,newKey),0) /*remove any cipher characters. */
grid=newKey || fill /*only first 25 chars are used.*/
grid=newKey || fill /*only first 25 chars are used.*/
say ' old cipher: ' strip(oldK) ; padL=14+2; pad=left('',padL)
say 'old cipher key: ' strip(oldK) ; padL=14+2; pad=left('',padL)
say ' new cipher: ' newKey ; padX=left('',padL,'')
say 'new cipher key: ' newKey ; padX=left('',padL,"")'Playfair'
say ' omit char: ' omit
say ' omit char: ' omit /* [↓] lowercase of double char.*/
say ' double char: ' xx
say ' double char: ' xx ; Lxx=translate(xx, @abc, @abcU)
say ' original text: ' strip(text)
say ' original text: ' strip(text) /* [↓] doubled version of Lxx. */
say ' new text: ' newText
say ' cleansed text: ' newText ; LxxLxx=Lxx || Lxx
#=0 /*number of grid characters used.*/
#=0
do row =1 for 5 /*build grid (individual cells).*/
do row =1 for 5 /*build array of individual cells*/
do col=1 for 5; #=#+1; @.row.col=substr(grid,#,1)
do col=1 for 5; #=#+1; @.row.col=substr(grid,#,1)
if row==1 then @.0.col=@.1.col
if row==1 then @.0.col=@.1.col
if col==5 then do; @.row.6=@.row.1; @.row.0=@.row.5; end
if col==5 then do; @.row.6=@.row.1; @.row.0=@.row.5; end
if row==5 then do; @.6.col=@.1.col; @.0.col=@.5.col; end
if row==5 then do; @.6.col=@.1.col; @.0.col=@.5.col; end
end /*col*/
end /*col*/
end /*row*/
end /*row*/


cText=.Playfair(newText, 1); call show 'cipher' , cText
eText=.Playfair(newText, 1); call show 'encypted' , eText
pText=.Playfair(cText ); call show 'plain' , pText
pText=.Playfair(eText ); call show 'plain' , pText
qText=space(translate(pText,,xx),0) /*remove char used for "doubles."*/
qText=changestr(xx ||xx,pText,Lxx) /*change doubled doublechar─►sing*/
qText=changestr(Lxx||xx,qText,LxxLxx) /*change Xx ──► lowercase dblChar*/
qText=space(translate(qText,,xx),0) /*remove char used for "doubles."*/
upper qText /*reinstate the use of upperchars*/
if length(qText)\==length(pText) then call show 'possible', qText
if length(qText)\==length(pText) then call show 'possible', qText
say ' original text: ' newText; say /*··· and show the original text.*/
say ' original text: ' newText; say /*··· and show the original text.*/
if qtext==newText then say padx 'Playfair encryption─►decryption─►encryption worked.'
if qtext==newText then say padx 'encryption──► decryption──► encryption worked.'
exit /*stick a fork in it, we're done.*/
exit /*stick a fork in it, we're done.*/
/*──────────────────────────────────one─line subroutines───────────────────────────*/
/*──────────────────────────────────one─line subroutines───────────────────────────────*/
@@: parse arg Xrow,Xcol; return @.Xrow.Xcol
@@: parse arg Xrow,Xcol; return @.Xrow.Xcol
err: say; say '***error!***' arg(1); say; exit 13
err: say; say '***error!***' arg(1); say; exit 13
LR: rowL=row(left(__,1)); colL=_; rowR=row(right(__,1)); colR=_; return length(__)
LR: rowL=row(left(__,1)); colL=_; rowR=row(right(__,1)); colR=_; return length(__)
row: ?=pos(arg(1),grid); _=(?-1)//5+1; return (4+?)%5
row: ?=pos(arg(1),grid); _=(?-1)//5+1; return (4+?)%5
show: arg x,y; say; say right(x 'text: ',padL) digram(y); say pad space(y,0); return
show: arg ,y; say; say right(arg(1) 'text: ',padL) digram(y); say pad space(y,0); return
/*──────────────────────────────────SCRUB subroutine────────────────────*/
/*──────────────────────────────────SCRUB subroutine────────────────────*/
scrub: procedure; arg xxx,unique; xxx=space(xxx,0) /*ARG caps all args*/
scrub: procedure; arg xxx,unique; xxx=space(xxx,0) /*ARG caps all args*/
Line 424: Line 429:
.Playfair: arg T,encrypt; i=-1; if encrypt==1 then i=1; $=
.Playfair: arg T,encrypt; i=-1; if encrypt==1 then i=1; $=
do k=1 while i==1; _=substr(T,k,1); if _==' ' then leave
do k=1 while i==1; _=substr(T,k,1); if _==' ' then leave
if _==substr(T,k+1 ,1) then T=left(T,k) || xx || substr(T,k+1)
if _==substr(T,k+1 ,1) then T=left(T,k) || Lxx || substr(T,k+1)
end /*k*/
end /*k*/
upper T
do j=1 by 2 to length(T); __=strip(substr(T,j,2))
do j=1 by 2 to length(T); __=strip(substr(T,j,2))
if LR()==1 then __=__ || xx; call LR /*append X or Q char, rule 1*/
if LR()==1 then __=__ || xx; call LR /*append X or Q char, rule 1*/
Line 438: Line 444:
'''output''' when using the default inputs:
'''output''' when using the default inputs:
<pre>
<pre>
old cipher: Playfair example. ◄───using the default.
old cipher key: Playfair example. ◄───using the default.
new cipher: PLAYFIREXM
new cipher key: PLAYFIREXM
omit char: J
omit char: J
double char: X
double char: X
original text: Hide the gold in the tree stump!!
original text: Hide the gold in the tree stump!!
new text: HIDETHEGOLDINTHETREESTUMP
cleansed text: HIDETHEGOLDINTHETREESTUMP


CIPHER text: BM OD ZB XD NA BE KU DM UI XM MO UV IF
encypted text: BM OD ZB XD NA BE KU DM UI XM MO UV IF
BMODZBXDNABEKUDMUIXMMOUVIF
BMODZBXDNABEKUDMUIXMMOUVIF


PLAIN text: HI DE TH EG OL DI NT HE TR EX ES TU MP
plain text: HI DE TH EG OL DI NT HE TR EX ES TU MP
HIDETHEGOLDINTHETREXESTUMP
HIDETHEGOLDINTHETREXESTUMP


POSSIBLE text: HI DE TH EG OL DI NT HE TR EE ST UM P
possible text: HI DE TH EG OL DI NT HE TR EE ST UM P
HIDETHEGOLDINTHETREESTUMP
HIDETHEGOLDINTHETREESTUMP
original text: HIDETHEGOLDINTHETREESTUMP
original text: HIDETHEGOLDINTHETREESTUMP


════════════════Playfair encryption──► decryption──► encryption worked.
════════════════ Playfair encryption─►decryption─►encryption worked.
</pre>
After the usual replacements for $, @, #, and x= I ran the program on ooRexx with the following correct results:
<pre> old cypher= Playfair example
new cypher= PLAYFIREXM
old phrase= Hide the gold in the tree stump!!
new phrase= HIDETHEGOLDINTHETREESTUMP
new digram= HI DE TH EG OL DI NT HE TR EE ST UM P

cypher text= BM OD ZB XD NA BE KU DM UI XM MO UV IF
plain text= HI DE TH EG OL DI NT HE TR EX ES TU MP
</pre>
</pre>
'''output''' when using the input of: &nbsp; <tt> stuvw x (myteest </tt>
'''output''' when using the input of: &nbsp; <tt> stuvw x (myteest </tt>
<pre>
<pre>
old cipher: stuvw
old cipher key: stuvw
new cipher: STUVW
new cipher key: STUVW
omit char: X
omit char: X
double char: Q
double char: Q
original text: myteest
original text: myteest
new text: MYTEEST
cleansed text: MYTEEST


CIPHER text: NR WB ZB TU
encypted text: NR WB ZB TU
NRWBZBTU
NRWBZBTU


PLAIN text: MY TE QE ST
plain text: MY TE QE ST
MYTEQEST
MYTEQEST


POSSIBLE text: MY TE ES T
possible text: MY TE ES T
MYTEEST
MYTEEST
original text: MYTEEST
original text: MYTEEST


════════════════Playfair encryption──► decryption──► encryption worked.
════════════════ Playfair encryption─►decryption─►encryption worked.
</pre>
</pre>



Revision as of 23:47, 9 November 2013

Playfair cipher is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Implement a Playfair cipher encryption and decryption.

The user must be able to choose J = I or no Q in the alphabet. The output of the encrypted and decrypted message must be in capitalized digraphs, separated by spaces.

Output example: HI DE TH EG OL DI NT HE TR EX ES TU MP.

C++

<lang cpp>#include <iostream>

  1. include <string>

using namespace std;

class playfair { public:

   void doIt( string k, string t, bool ij, bool e )
   {

createGrid( k, ij ); getTextReady( t, ij, e ); if( e ) doIt( 1 ); else doIt( -1 ); display();

   }

private:

   void doIt( int dir )
   {

int a, b, c, d; string ntxt; for( string::const_iterator ti = _txt.begin(); ti != _txt.end(); ti++ ) { if( getCharPos( *ti++, a, b ) ) if( getCharPos( *ti, c, d ) ) { if( a == c ) { ntxt += getChar( a, b + dir ); ntxt += getChar( c, d + dir ); } else if( b == d ){ ntxt += getChar( a + dir, b ); ntxt += getChar( c + dir, d ); } else { ntxt += getChar( c, b ); ntxt += getChar( a, d ); } } } _txt = ntxt;

   }
   void display()
   {

cout << "\n\n OUTPUT:\n=========" << endl; string::iterator si = _txt.begin(); int cnt = 0; while( si != _txt.end() ) { cout << *si; si++; cout << *si << " "; si++; if( ++cnt >= 26 ) cout << endl, cnt = 0; } cout << endl << endl;

   }
   char getChar( int a, int b )
   {

return _m[ (b + 5) % 5 ][ (a + 5) % 5 ];

   }
   bool getCharPos( char l, int &a, int &b )
   {

for( int y = 0; y < 5; y++ ) for( int x = 0; x < 5; x++ ) if( _m[y][x] == l ) { a = x; b = y; return true; }

return false;

   }
   void getTextReady( string t, bool ij, bool e )
   {

for( string::iterator si = t.begin(); si != t.end(); si++ ) { *si = toupper( *si ); if( *si < 65 || *si > 90 ) continue; if( *si == 'J' && ij ) *si = 'I'; else if( *si == 'Q' && !ij ) continue; _txt += *si; } if( e ) { string ntxt = ""; size_t len = _txt.length(); for( size_t x = 0; x < len; x += 2 ) { ntxt += _txt[x]; if( x + 1 < len ) { if( _txt[x] == _txt[x + 1] ) ntxt += 'X'; ntxt += _txt[x + 1]; } } _txt = ntxt; } if( _txt.length() & 1 ) _txt += 'X';

   }
   void createGrid( string k, bool ij )
   {

if( k.length() < 1 ) k = "KEYWORD"; k += "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; string nk = ""; for( string::iterator si = k.begin(); si != k.end(); si++ ) { *si = toupper( *si ); if( *si < 65 || *si > 90 ) continue; if( ( *si == 'J' && ij ) || ( *si == 'Q' && !ij ) )continue; if( nk.find( *si ) == -1 ) nk += *si; } copy( nk.begin(), nk.end(), &_m[0][0] );

   }
   string _txt; char _m[5][5];

};

int main( int argc, char* argv[] ) {

   string key, i, txt; bool ij, e;
   cout << "(E)ncode or (D)ecode? "; getline( cin, i ); e = ( i[0] == 'e' || i[0] == 'E' );
   cout << "Enter a en/decryption key: "; getline( cin, key ); 
   cout << "I <-> J (Y/N): "; getline( cin, i ); ij = ( i[0] == 'y' || i[0] == 'Y' );
   cout << "Enter the text: "; getline( cin, txt ); 
   playfair pf; pf.doIt( key, txt, ij, e ); return system( "pause" );

}</lang>

Output:

(E)ncode or (D)ecode? e Enter a en/decryption key: playfair example I <-> J (Y/N): y Enter the text: Hide the gold in the tree stump

OUTPUT: ========= BM OD ZB XD NA BE KU DM UI XM MO UV IF


(E)ncode or (D)ecode? d Enter a en/decryption key: playfair example I <-> J (Y/N): y Enter the text: BMODZBXDNABEKUDMUIXMMOUVIF

OUTPUT: ========= HI DE TH EG OL DI NT HE TR EX ES TU MP

D

Translation of: Python

<lang d>import std.stdio, std.array, std.algorithm, std.range, std.ascii,

      std.conv, std.string, std.regex;

string unique(in string s) pure /*nothrow*/ {

   string result;
   foreach (immutable char c; s)
       if (!result.canFind(c))
           result ~= c;
   return result;

}

struct Playfair {

   string from, to;
   string[string] enc, dec;
   this(in string key, in string from_ = "J", in string to_ = null) {
       this.from = from_;
       if (to_.empty)
           this.to = (from_ == "J") ? "I" : "";
       auto m = _canonicalize(key ~ uppercase)
                .unique
                .chunks(5)
                .map!text
                .array;
       auto I5 = 5.iota;
       foreach (const R; m)
           foreach (immutable i, immutable j; cartesianProduct(I5, I5))
               if (i != j)
                   enc[[R[i], R[j]]] = [R[(i + 1) % 5], R[(j+1) % 5]];
       foreach (immutable r; I5) {
           const c = m.transversal(r).array;
           foreach (immutable i, immutable j; cartesianProduct(I5, I5))
               if (i != j)
                   enc[[c[i], c[j]]] = [c[(i + 1) % 5], c[(j+1) % 5]];
       }
       foreach (i1, j1, i2, j2; cartesianProduct(I5, I5, I5, I5))
           if (i1 != i2 && j1 != j2)
               enc[[m[i1][j1], m[i2][j2]]] = [m[i1][j2], m[i2][j1]];
       dec = enc.byValue.zip(enc.byKey).assocArray;
   }
   private string _canonicalize(in string s) const /*pure*/ {
       return s.toUpper.removechars("^A-Z").replace(from, to);
   }
   string encode(in string s) const {
       return _canonicalize(s)
              .matchAll(r"(.)(?:(?!\1)(.))?".regex)
              //.map!(m => enc[m[0].leftJustify(2, 'X')])
              .map!(m => cast()enc[m[0].leftJustify(2, 'X')])
              .join(" ");
   }
   string decode(in string s) const /*pure*/ {
       return _canonicalize(s)
              .chunks(2)
              //.map!dec
              .map!(p => cast()dec[p.text])
              .join(" ");
   }

}

void main() {

   const pf = Playfair("Playfair example");
   immutable orig = "Hide the gold in...the TREESTUMP!!!";
   writeln("Original: ", orig);
   immutable enc = pf.encode(orig);
   writeln(" Encoded: ", enc);
   writeln(" Decoded: ", pf.decode(enc));

}</lang>

Output:
Original: Hide the gold in...the TREESTUMP!!!
 Encoded: BM OD ZB XD NA BE KU DM UI XM MO UV IF
 Decoded: HI DE TH EG OL DI NT HE TR EX ES TU MP

Perl 6

<lang perl6># Instantiate a specific encoder/decoder.

sub playfair( $key,

             $from = 'J',

$to = $from eq 'J' ?? 'I' !! ) {

   sub canon($str) { $str.subst(/<-alpha>/,, :g).uc.subst(/$from/,$to,:g) }
   # Build 5x5 matrix.
   my @m = canon($key ~ ('A'..'Z').join).comb.uniq.map:

-> $a,$b,$c,$d,$e { [$a,$b,$c,$d,$e] }

   # Pregenerate all forward translations.
   my %ENC = gather {

# Map pairs in same row. for @m -> @r { for ^@r X ^@r -> \i,\j { next if i == j; take @r[i] ~ @r[j] => @r[(i+1)%5] ~ @r[(j+1)%5]; } }

# Map pairs in same column. for ^5 -> $c { my @c = @m.map: *.[$c]; for ^@c X ^@c -> \i,\j { next if i == j; take @c[i] ~ @c[j] => @c[(i+1)%5] ~ @c[(j+1)%5]; } }

# Map pairs with cross-connections. for ^5 X ^5 X ^5 X ^5 -> \i1,\j1,\i2,\j2 { next if i1 == i2 or j1 == j2; take @m[i1][j1] ~ @m[i2][j2] => @m[i1][j2] ~ @m[i2][j1]; }

   }
   # Generate reverse translations.
   my %DEC = %ENC.invert;
   return

anon sub enc($red) { my @list = canon($red).comb(/(.) (.?) <?{ $1 ne $0 }>/); ~@list.map: { .chars == 1 ?? %ENC{$_~'X'} !! %ENC{$_} } }, anon sub dec($black) { my @list = canon($black).comb(/../); ~@list.map: { %DEC{$_} } } }

my (&encode,&decode) = playfair 'Playfair example';

my $orig = "Hide the gold in...the TREESTUMP!!!"; say " orig:\t$orig";

my $black = encode $orig; say "black:\t$black";

my $red = decode $black; say " red:\t$red";</lang>

Output:
 orig:	Hide the gold in...the TREESTUMP!!!
black:	BM OD ZB XD NA BE KU DM UI XM MO UV IF
  red:	HI DE TH EG OL DI NT HE TR EX ES TU MP

Python

Translation of: Perl 6

<lang python>from string import ascii_uppercase from itertools import product from re import findall

def uniq(seq):

   seen = {}
   return [seen.setdefault(x, x) for x in seq if x not in seen]

def partition(seq, n):

   return [seq[i : i + n] for i in xrange(0, len(seq), n)]


"""Instantiate a specific encoder/decoder.""" def playfair(key, from_ = 'J', to = None):

   if to is None:
       to = 'I' if from_ == 'J' else 
   def canonicalize(s):
       return filter(str.isupper, s.upper()).replace(from_, to)
   # Build 5x5 matrix.
   m = partition(uniq(canonicalize(key + ascii_uppercase)), 5)
   # Pregenerate all forward translations.
   enc = {}
   # Map pairs in same row.
   for row in m:
       for i, j in product(xrange(5), repeat=2):
           if i != j:
               enc[row[i] + row[j]] = row[(i + 1) % 5] + row[(j + 1) % 5]
   # Map pairs in same column.
   for c in zip(*m):
       for i, j in product(xrange(5), repeat=2):
           if i != j:
               enc[c[i] + c[j]] = c[(i + 1) % 5] + c[(j + 1) % 5]
   # Map pairs with cross-connections.
   for i1, j1, i2, j2 in product(xrange(5), repeat=4):
       if i1 != i2 and j1 != j2:
           enc[m[i1][j1] + m[i2][j2]] = m[i1][j2] + m[i2][j1]
   # Generate reverse translations.
   dec = dict((v, k) for k, v in enc.iteritems())
   def sub_enc(txt):
       lst = findall(r"(.)(?:(?!\1)(.))?", canonicalize(txt))
       return " ".join(enc[a + (b if b else 'X')] for a, b in lst)
   def sub_dec(encoded):
       return " ".join(dec[p] for p in partition(canonicalize(encoded), 2))
   return sub_enc, sub_dec


(encode, decode) = playfair("Playfair example") orig = "Hide the gold in...the TREESTUMP!!!" print "Original:", orig enc = encode(orig) print "Encoded:", enc print "Decoded:", decode(enc)</lang>

Output:
Original: Hide the gold in...the TREESTUMP!!!
Encoded: BM OD ZB XD NA BE KU DM UI XM MO UV IF
Decoded: HI DE TH EG OL DI NT HE TR EX ES TU MP

REXX

Quite a bit of the REXX code deals with error checking, accepting arguments, and displaying the options used, and displaying input and output.
For ease of viewing and comparing, the output is in capitalized digraphs (which are really digrams) as well as the original input(s).
Thanks to Walter Pachl, this program is now sensitive of using a suitable double character when   X   is present in the cipher key.
Also added a a fair amount of code to massage the decrypted encryption to remove doubled   Xes   so as to match the original text. <lang rexx>/*REXX program implements a PLAYFAIR cipher (encryption & decryption).*/ parse arg key . 1 oldK omit _ . '(' text /*TEXT is the phrase to be used*/ if key == | key ==',' then do; key='Playfair example.'; oldK=key " ◄───using the default."; end if omit== | omit==',' then omit='J' /*the "omitted" character. */ if text= then text='Hide the gold in the tree stump!!' /*default.*/ newKey =scrub(key , 1) /*scrub old cipher key──► newKey */ newText=scrub(text ) /* " " text ──► newText*/ if _\== then call err 'too many arguments specified.' if newText== then call err 'TEXT is empty or has no letters' if length(omit)\==1 then call err 'OMIT letter must be only one letter' if \datatype(omit,'M') then call err 'OMIT letter must be a Latin alphabet letter.' @abc='abcdefghijklmnopqrstuvwxyz'; @abcU=@abc upper omit @abcU /*uppercase OMIT char & alphabet.*/ if pos(omit,newText)\==0 then call err 'TEXT cant contain the "OMIT" character: ' omit fill=space(translate(@abcU,, omit), 0) /*elide OMIT char from alphabet. */ xx='X'; if omit==xx then xx='Q' /*char used for double characters*/ if length(newKey)<3 then call err 'cipher key is too short, must be ≥3 unique characters.' fill=space(translate(fill,,newKey),0) /*remove any cipher characters. */ grid=newKey || fill /*only first 25 chars are used.*/ say 'old cipher key: ' strip(oldK)  ; padL=14+2; pad=left(,padL) say 'new cipher key: ' newKey  ; padX=left(,padL,"═")'Playfair' say ' omit char: ' omit /* [↓] lowercase of double char.*/ say ' double char: ' xx  ; Lxx=translate(xx, @abc, @abcU) say ' original text: ' strip(text) /* [↓] doubled version of Lxx. */ say ' cleansed text: ' newText  ; LxxLxx=Lxx || Lxx

  1. =0 /*number of grid characters used.*/
          do row   =1  for 5          /*build array of individual cells*/
             do col=1  for 5;     #=#+1;       @.row.col=substr(grid,#,1)
             if row==1  then      @.0.col=@.1.col
             if col==5  then do;  @.row.6=@.row.1;  @.row.0=@.row.5;  end
             if row==5  then do;  @.6.col=@.1.col;  @.0.col=@.5.col;  end
             end   /*col*/
          end      /*row*/

eText=.Playfair(newText, 1); call show 'encypted' , eText pText=.Playfair(eText ); call show 'plain' , pText qText=changestr(xx ||xx,pText,Lxx) /*change doubled doublechar─►sing*/ qText=changestr(Lxx||xx,qText,LxxLxx) /*change Xx ──► lowercase dblChar*/ qText=space(translate(qText,,xx),0) /*remove char used for "doubles."*/ upper qText /*reinstate the use of upperchars*/ if length(qText)\==length(pText) then call show 'possible', qText say ' original text: ' newText; say /*··· and show the original text.*/ if qtext==newText then say padx 'encryption──► decryption──► encryption worked.' exit /*stick a fork in it, we're done.*/ /*──────────────────────────────────one─line subroutines───────────────────────────────*/ @@: parse arg Xrow,Xcol; return @.Xrow.Xcol err: say; say '***error!***' arg(1); say; exit 13 LR: rowL=row(left(__,1)); colL=_; rowR=row(right(__,1)); colR=_; return length(__) row:  ?=pos(arg(1),grid); _=(?-1)//5+1; return (4+?)%5 show: arg ,y; say; say right(arg(1) 'text: ',padL) digram(y); say pad space(y,0); return /*──────────────────────────────────SCRUB subroutine────────────────────*/ scrub: procedure; arg xxx,unique; xxx=space(xxx,0) /*ARG caps all args*/ $=; do j=1 for length(xxx); _=substr(xxx,j,1)

         if unique==1  then  if  pos(_,$)\==0  then iterate   /*unique?*/
         if datatype(_,'M')  then $=$||_     /*only use Latin letters. */
         end   /*j*/

return $ /*──────────────────────────────────DIGRAM subroutine───────────────────*/ digram: procedure; parse arg x; $=; do j=1 by 2 to length(x)

                                          $=$ || substr(x,j,2)' '
                                          end   /*j*/

return strip($) /*──────────────────────────────────.PLAYFAIR subroutine────────────────*/ .Playfair: arg T,encrypt; i=-1; if encrypt==1 then i=1; $=

       do k=1  while  i==1;     _=substr(T,k,1);     if _==' '  then leave
       if _==substr(T,k+1 ,1)   then T=left(T,k) || Lxx || substr(T,k+1)
       end   /*k*/

upper T

 do j=1  by 2  to length(T);    __=strip(substr(T,j,2))
 if LR()==1  then __=__ || xx;  call LR    /*append X or Q char, rule 1*/
   select
   when rowL==rowR  then __=@@(rowL,  colL+i)@@(rowR,  colR+i) /*rule 2*/
   when colL==colR  then __=@@(rowL+i,colL  )@@(rowR+i,colR)   /*rule 3*/
   otherwise             __=@@(rowL,  colR  )@@(rowR,  colL)   /*rule 4*/
   end   /*select*/
 $=$ || __
 end   /*j*/

return $</lang> output when using the default inputs:

old cipher key:  Playfair example.    ◄───using the default.
new cipher key:  PLAYFIREXM
     omit char:  J
   double char:  X
 original text:  Hide the gold in the tree stump!!
 cleansed text:  HIDETHEGOLDINTHETREESTUMP

 encypted text:  BM OD ZB XD NA BE KU DM UI XM MO UV IF
                 BMODZBXDNABEKUDMUIXMMOUVIF

    plain text:  HI DE TH EG OL DI NT HE TR EX ES TU MP
                 HIDETHEGOLDINTHETREXESTUMP

 possible text:  HI DE TH EG OL DI NT HE TR EE ST UM P
                 HIDETHEGOLDINTHETREESTUMP
 original text:  HIDETHEGOLDINTHETREESTUMP

════════════════Playfair encryption──► decryption──► encryption worked. 

output when using the input of:   stuvw x (myteest

old cipher key:  stuvw
new cipher key:  STUVW
     omit char:  X
   double char:  Q
 original text:  myteest
 cleansed text:  MYTEEST

 encypted text:  NR WB ZB TU
                 NRWBZBTU

    plain text:  MY TE QE ST
                 MYTEQEST

 possible text:  MY TE ES T
                 MYTEEST
 original text:  MYTEEST

════════════════Playfair encryption──► decryption──► encryption worked.

Tcl

Works with: Tcl version 8.6

<lang tcl>package require TclOO

oo::class create Playfair {

   variable grid lookup excluder
   constructor {{keyword "PLAYFAIR EXAMPLE"} {exclude "J"}} {

# Tweaking according to exact operation mode if {$exclude eq "J"} { set excluder "J I" } else { set excluder [list $exclude ""] }

# Clean up the keyword source set keys [my Clean [append keyword "ABCDEFGHIJKLMNOPQRSTUVWXYZ"]]

# Generate the encoding grid set grid [lrepeat 5 [lrepeat 5 ""]] set idx -1 for {set i 0} {$i < 5} {incr i} {for {set j 0} {$j < 5} {} { if {![info exist lookup([set c [lindex $keys [incr idx]]])]} { lset grid $i $j $c set lookup($c) [list $i $j] incr j } }}

# Sanity check if {[array size lookup] != 25} { error "failed to build encoding table correctly" }

   }
   # Worker to apply a consistent cleanup/split rule
   method Clean {str} {

set str [string map $excluder [string toupper $str]] split [regsub -all {[^A-Z]} $str ""] ""

   }
   # These public methods are implemented by a single non-public method
   forward encode my Transform 1
   forward decode my Transform -1
   # The application of the Playfair cypher transform
   method Transform {direction message} {

# Split message into true digraphs foreach c [my Clean $message] { if {![info exists lookup($c)]} continue if {[info exist c0]} { lappend digraphs $c0 [expr {$c0 eq $c ? "X" : $c}] unset c0 } else { set c0 $c } } if {[info exist c0]} { lappend digraphs $c0 "Z" }

# Encode the digraphs set result "" foreach {a b} $digraphs { lassign $lookup($a) ai aj lassign $lookup($b) bi bj if {$ai == $bi} { set aj [expr {($aj + $direction) % 5}] set bj [expr {($bj + $direction) % 5}] } elseif {$aj == $bj} { set ai [expr {($ai + $direction) % 5}] set bi [expr {($bi + $direction) % 5}] } else { set tmp $aj set aj $bj set bj $tmp } lappend result [lindex $grid $ai $aj][lindex $grid $bi $bj] }

# Real use would be: return [join $result ""] return $result

   }

}</lang> Demonstrating: <lang tcl>Playfair create cypher "Playfair Example" set plaintext "Hide the gold in...the TREESTUMP!!!" set encoded [cypher encode $plaintext] set decoded [cypher decode $encoded] puts "Original: $plaintext" puts "Encoded: $encoded" puts "Decoded: $decoded"</lang>

Output:
Original: Hide the gold in...the TREESTUMP!!!
Encoded:  BM OD ZB XD NA BE KU DM UI XM KZ ZR FT
Decoded:  HI DE TH EG OL DI NT HE TR EX ST UM PZ