Playfair cipher: Difference between revisions
(→{{header|REXX}}: fixed two unset variables. -- ~~~~) |
(→{{header|REXX}}: added more diagnostic output, added "possible text" (which deletes the character used for double characters in the original text), used preferred spelling of cipher. -- ~~~~) |
||
Line 361: | Line 361: | ||
=={{header|REXX}}== |
=={{header|REXX}}== |
||
Quite a bit of the REXX code deals with error checking, accepting arguments, and displaying the options used. |
Quite a bit of the REXX code deals with error checking, accepting arguments, and displaying the options used. |
||
<lang rexx>/*REXX program implements a PLAYFAIR |
<lang rexx>/*REXX program implements a PLAYFAIR cipher (encryption & decryption).*/ |
||
arg oldK |
parse arg key . 1 oldK omit _ . '(' text /*TEXT is the phrase to be used*/ |
||
if |
if key =='' | key ==',' then do; key='Playfair example.'; oldK=key " ◄───the default."; end |
||
if |
if omit=='' | omit==',' then omit='J' /*the "omitted" character. */ |
||
if |
if text='' then text='Hide the gold in the tree stump!!' /*default.*/ |
||
key =scrub(key) /*use only Latin alphabet letters*/ |
|||
⚫ | |||
newText=scrub(text) /* " " " " " */ |
|||
newX=scrub(oldX) /* " " " " " */ |
|||
if _\=='' then call err 'too many arguments specified.' |
if _\=='' then call err 'too many arguments specified.' |
||
if |
if newText=='' then call err 'TEXT is empty or has no letters' |
||
if length( |
if length(omit)\==1 then call err 'OMIT letter must be only one letter' |
||
if \datatype( |
if \datatype(omit,'M') then call err 'OMIT letter must be a Latin alphabet letter.' |
||
if pos( |
if pos(omit,text)\==0 then call err 'TEXT can''t contain the "OMIT" character: ' omit |
||
⚫ | |||
fill=space(translate('ABCDEFGHIJKLMNOPQRSTUVWXYZ',, |
fill=space(translate('ABCDEFGHIJKLMNOPQRSTUVWXYZ',,omit),0) /*elide omit*/ |
||
⚫ | |||
newKey= /* [↓] remove any duplicate chars*/ |
|||
do j=1 for length(key); _=substr(key,j,1) |
|||
if pos(_,newKey)==0 then newKey=newKey || _ |
|||
end /*j*/ |
end /*j*/ |
||
xx='X'; if omit==xx then xx='Q' /*char used for double characters*/ |
|||
if length( |
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. */ |
|||
grid=left(newKey || fill,26) /*use only the first 25 chars. */ |
|||
⚫ | |||
say ' old |
say ' old cipher: ' strip(oldK) |
||
say ' new |
say ' new cipher: ' newKey |
||
say ' |
say ' omit char : ' omit |
||
say 'double char : ' xx |
|||
⚫ | |||
⚫ | |||
say 'digram text ↔: ' digram(newText) |
|||
#=0 |
#=0 |
||
do row =1 for 5 /*build grid (individual cells).*/ |
do row =1 for 5 /*build grid (individual cells).*/ |
||
Line 395: | Line 399: | ||
end /*row*/ |
end /*row*/ |
||
say |
say |
||
cText=.playfair( |
cText=.playfair(newText, 1); say ' cipher text: ' digram(cText) |
||
pText=.playfair(cText |
pText=.playfair(cText ); say ' plain text: ' digram(pText) |
||
⚫ | |||
if length(qText)\==length(pText) then say 'possible text: ' digram(qText) |
|||
exit /*stick a fork in it, we're done.*/ |
exit /*stick a fork in it, we're done.*/ |
||
/*──────────────────────────────────one-line subroutines────────────────*/ |
/*──────────────────────────────────one-line subroutines────────────────*/ |
||
Line 415: | Line 421: | ||
return strip($) |
return strip($) |
||
/*──────────────────────────────────.PLAYFAIR subroutine────────────────*/ |
/*──────────────────────────────────.PLAYFAIR subroutine────────────────*/ |
||
.playfair: parse arg T,encrypt; i=-1; if encrypt then i=1; $= |
.playfair: parse arg T,encrypt; i=-1; if encrypt==1 then i=1; $= |
||
do k=1 while |
do k=1 while i==1; _=substr(T,k,1); if _='' then leave |
||
if _==substr(T,k+1,1) then T=left(T,k) || |
if _==substr(T,k+1,1) then T=left(T,k) || xx || substr(T,k+1) |
||
end /*k*/ |
end /*k*/ |
||
do j=1 by 2 to length(T); __=strip(substr(T,j,2)); call LR |
do j=1 by 2 to length(T); __=strip(substr(T,j,2)); call LR |
||
if length(__)==1 then __=__ || |
if length(__)==1 then __=__ || xx; call LR /*append X|Q char, rule 1*/ |
||
select |
select |
||
when rowL==rowR then __=@@(rowL, colL+i)@@(rowR, colR+i) /*rule 2*/ |
when rowL==rowR then __=@@(rowL, colL+i)@@(rowR, colR+i) /*rule 2*/ |
||
Line 431: | Line 437: | ||
'''output''' when using the default inputs: |
'''output''' when using the default inputs: |
||
<pre> |
<pre> |
||
old |
old cipher: Playfair example. ◄───the default. |
||
new |
new cipher: PLAYFIREXM |
||
omit char : J |
|||
old phrase= Hide the gold in the tree stump!! |
|||
double char : X |
|||
⚫ | |||
old text : Hide the gold in the tree stump!! |
|||
⚫ | |||
digram text ↔: HI DE TH EG OL DI NT HE TR EE ST UM P |
|||
cipher text: BM OD ZB XD NA BE KU DM UI XM MO UV IF |
|||
plain text |
plain text: HI DE TH EG OL DI NT HE TR EX ES TU MP |
||
possible text: HI DE TH EG OL DI NT HE TR EE ST UM P |
|||
</pre> |
</pre> |
||
After the usual replacements for $, @, #, and x= I ran the program on ooRexx with the following correct results: |
After the usual replacements for $, @, #, and x= I ran the program on ooRexx with the following correct results: |
Revision as of 23:12, 8 November 2013
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>
- 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: BMODZBXDNABEKUDMUIXMMOUVIFOUTPUT: ========= HI DE TH EG OL DI NT HE TR EX ES TU MP
D
<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
<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. <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 " ◄───the default."; end if omit== | omit==',' then omit='J' /*the "omitted" character. */ if text= then text='Hide the gold in the tree stump!!' /*default.*/ key =scrub(key) /*use only Latin alphabet letters*/ newText=scrub(text) /* " " " " " */ 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.' if pos(omit,text)\==0 then call err 'TEXT cant contain the "OMIT" character: ' omit upper omit /*uppercase the OMIT character.*/ fill=space(translate('ABCDEFGHIJKLMNOPQRSTUVWXYZ',,omit),0) /*elide omit*/ newKey= /* [↓] remove any duplicate chars*/
do j=1 for length(key); _=substr(key,j,1) if pos(_,newKey)==0 then newKey=newKey || _ end /*j*/
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 > 2 unique characters.' fill=space(translate(fill,,newKey),0) /*remove any cipher characters. */ grid=left(newKey || fill,26) /*use only the first 25 chars. */ say ' old cipher: ' strip(oldK) say ' new cipher: ' newKey say ' omit char : ' omit say 'double char : ' xx say ' old text : ' strip(text) say ' new text : ' newText say 'digram text ↔: ' digram(newText)
- =0
do row =1 for 5 /*build grid (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*/
say cText=.playfair(newText, 1); say ' cipher text: ' digram(cText) pText=.playfair(cText ); say ' plain text: ' digram(pText) qText=space(translate(pText,,xx),0) /*remove char used for "doubles."*/ if length(qText)\==length(pText) then say 'possible text: ' digram(qText) 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 /*──────────────────────────────────SCRUB subroutine────────────────────*/ scrub: procedure; arg stuff /* ARG capitalizes all arguments.*/ $=; do j=1 for length(stuff); _=substr(stuff,j,1)
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: parse 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) || xx || substr(T,k+1) end /*k*/ do j=1 by 2 to length(T); __=strip(substr(T,j,2)); call LR if length(__)==1 then __=__ || xx; call LR /*append X|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: Playfair example. ◄───the default. new cipher: PLAYFIREXM omit char : J double char : X old text : Hide the gold in the tree stump!! new text : HIDETHEGOLDINTHETREESTUMP digram text ↔: HI DE TH EG OL DI NT HE TR EE ST UM P cipher 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 possible text: HI DE TH EG OL DI NT HE TR EE ST UM P
After the usual replacements for $, @, #, and x= I ran the program on ooRexx with the following correct results:
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
Tcl
<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