Enhanced Affine Encryption/Decryption Cipher in Perl

By Jari Perkimki

The typical implementation of the Affine Cipher in Perl is based on the characters from A to Z and their numeric values, using the built-in ord() and chr() functions. However, this will not work elegantly with non-English alphabets, you will need to use another approach. Here is one way to do it.

The summary

The following cipher works as follows: A given plaintext character will be given a numeric value from the plaintext alphabet. This value can be added and multiplied by the user. The result is modulo 29, or, to be exact, modulo of the length of the given alphabet if a user-defined character set is used. The result value is then applied back to the same alphabet but in a different numeric position, and the corresponding (ciphertext) character is found. Also, to finally give that professional look-and-feel to the output, use the group utility.

The syntax

Usage: ./affine.pl [options] [files]
Affine Cipher: encrypting text by arbitrary shift and multiplication.
Options:
-d       Decrypt
-f FILE  Read comma-separated characters (alphabet) from FILE
-m N     Multiply the numeric value by N. Default: 1.
-r N     Shift the value by N positions, range: -(length alphabet) to
         length alphabet. Default: 0.
         Original Caesar Cipher: N=3. ROT-13: N=13.
         N=0, N=-(length alphabet), N=(length alphabet) are equal: no shift.
-z       Use the reversed alphabet. Default: not reversed.
-v       Verbose: show the plaintext and the ciphertext.
-h       Show this help.

Files: Multiple files are accepted. No files means using STDIN.

The code

#!/usr/bin/perl
#
# Initial Release: 20 March 2006. jpe@uwasa.fi
# 26 Mar 06: Added decrypt function
# 21 Apr 06: Added support for user-defined character sets (from FILE)
#

$rot = $z = $b = $d = $verbose = $le = 0;
$byte = $plain = $cipher = $infile = "";
$mul = $i = $j = 1;
@fabc = ();

use Getopt::Long;
GetOptions ('d'   => \$d,        # decrypt
            'f=s' => \$infile,   # read abc from file
            'h'   => \$help,     # help
            'r=i' => \$rot,      # linear shift by x positions
            'm=i' => \$mul,      # multiply shift by x positions
            'z'   => \$z,        # reverse alphabet
            'v'   => \$verbose); # show plain/ciphertext

help() if $help;

if (-e $infile) {
    open (IN, $infile);
    while (<IN>) {
        chomp;
        @fabc = split /,/;
    }
    close IN;
}

$le = scalar @fabc;

if ($le > 0) {

    foreach $letter (@fabc) {
        $abc{$letter} = $i++;
        $xyz{$letter} = ($le + 1) - $j++;
    }

} else {

    foreach ('a' .. 'z', '', '', '') {
        $abc{$_} = $i++;
        $xyz{$_} = 30 - $j++;
    }

    $le = 29;

}

if ($rot > $le || $rot < -$le) { $rot = $le; }

%zyx = reverse %xyz;
%cba = reverse %abc;

undef $/;

while(<>) {

    tr/A-Z/a-z/;
    tr|/#+%&=,;:!\?\.\"\'\-<>\(\)\[\]@\\_| |;
    s/\s+//g;
    $plain = $_;

    $b = divmod($mul, $le);
    foreach $byte (split //) {

        $d ? decipher($byte) : encipher($byte);

    }

}

$verbose ? print "Plain:  $plain\nCipher: $cipher\n" : print "$cipher\n" ;

sub help {
    print <<EOF;
Usage: $0 [options] [files]
Affine Cipher: encrypting text by arbitrary shift and multiplication.
Options:
-d       Decrypt
-f FILE  Read comma-separated characters (alphabet) from FILE
-m N     Multiply the numeric value by N. Default: 1.
-r N     Shift the value by N positions, range: -(length alphabet) to
         length alphabet. Default: 0.
         Original Caesar Cipher: N=3. ROT-13: N=13.
         N=0, N=-(length alphabet), N=(length alphabet) are equal: no shift.
-z       Use the reversed alphabet. Default: not reversed.
-v       Verbose: show the plaintext and the ciphertext.
-h       Show this help.

Files: Multiple files are accepted. No files means using STDIN.
EOF
exit(1);
}

sub encipher {

    my $i = 0;
    my $j = 0;

    $i = $abc{$byte} ;

    $j = ($mul * $i + $rot) % $le;
    $j = $le if $j == 0;

    $z ? ($cipher .= $zyx{$j}) : ($cipher .= $cba{$j});

    return;
}

sub decipher {

    my $i = 0;
    my $j = 0;

    $z ? ($i = $xyz{$byte}) : ($i = $abc{$byte}) ;

    $j = ($b * ($i - $rot)) % $le;
    $j = $le if $j == 0;

    $cipher .= $cba{$j};

    return;

}

# thanks to ambrus: http://qs321.pair.com/~monkads/index.pl?node_id=525387
sub divmod {

    my $m = abs($_[1]);
    my $a = $_[0] % $m;
    my($b, $x, $y, $n) = ($m, 1, 0);

    while (0 != $a) {
        $n = int($b / $a);
        ($a, $b, $x, $y) = ($b - $n * $a, $a, $y - $n * $x, $x);
    }

    $y % $m;
}

Examples of use

1. Easy start

The easiest use -- which does absolutely nothing meaningful -- is:

./affine.pl afile

This is equivalent to

./affine.pl -r 0 -m 1 afile

where afile is the input file. You can also pipe the text to the program at all times:

cat afile | ./affine.pl [-r 0 -m 1]

Please note that you can give several files as input at the same time:

./affine.pl afile afile2 afile3 ...

There is no limit to the input files.

2. Caesar, ROT-13 and other character shifts (-r)

The Caesar rotation means shifting all characters by 3 positions. Then A becomes D, B becomes E and so on. Number 3 is added to all values of the plaintext characters. The character A has the original value of 1, so after addition it becomes 1+3 = 4 (the letter D in the same alphabet). Note that -r 0, -r 29 and -r -29 do not do any shifts!

./affine.pl -r 3 afile

The famous ROT-13 is simply:

./affine.pl -r 13 afile

or even:

cat afile | ./affine.pl -r 13

To decipher the Caesar code:

./affine.pl -r 3 -d decryptfile
or,
./affine.pl -r 3 afile | ./affine.pl -r 3 -d

produces the plaintext.

3. Make it verbose (-v)

If you want to study the plaintext and the ciphertext more closely, you can use the -v option:

./affine.pl -r 3 -v afile

Assuming the file "afile" contains the following single line,

abcdefghijklmnopqrstuvwxyz
the output will be:
Plain:  abcdefghijklmnopqrstuvwxyz
Cipher: defghijklmnopqrstuvwxyzabc

4. Multiplication (-m)

Besides addition, you can apply multiplication at the same time. This implements the formula:

    C = (m * i + r) % 29
where C is the ciphertext character, i is the numeric value of the plaintext character, m is the multiplier, and r is the shift. The corresponding Perl code is:
    $j = ($mul * $i + $rot) % 29;

Example: Multiply the numeric by 2 (-m 2) and then shift by 2 (-r 2)

Plain:      A B C  D  E  F  G  H  I  J  K  L  M  N
Numeric:    1 2 3  4  5  6  7  8  9 10 11 12 13 14
Multi (2):  2 4 6  8 10 12 14 16 18 20 22 24 26 28
Shift (2):  4 6 8 10 12 14 16 18 20 22 24 26 28 30
Cipher:     D F H  J  L  N  P  R  T  V  X  Z    A...

Note that 30 % 29 = 1 (= A).

Running affine.pl with these parameters produces:

./affine.pl -m 2 -r 2 -v afile

Plain:  abcdefghijklmnopqrstuvwxyz
Cipher: dfhjlnprtvxzacegikmoqsuwyb

Decryption

Decryption implements the formula:

    P = (m^-1 * (i - r)) % 29
where P is the plaintext character, i is the numeric value of the ciphertext character, m^-1 is the multiplicative inverse, and r is the shift. The corresponding Perl code is:
    $j = ($b * ($i - $rot)) % 29;

The variable $b is the result of the multiplicative inverse function, divmod. The divmod code comes from http://qs321.pair.com/~monkads/index.pl?node_id=525387

5. Using the reversed alphabetical (Atbash) mode (-z)

Usually we are happy with our A to character set. However, you may want to play with the reversed character set ( to A), too. Then use the option -z as follows (make it verbose to begin with):

./affine.pl -z -v afile

The result will of course be:

Plain:  abcdefghijklmnopqrstuvwxyz
Cipher: zyxwvutsrqponmlkjihgfedcba

How about reversing the reversed output? Let's see:

./affine.pl -z afile | ./affine.pl -z -v

Plain:  zyxwvutsrqponmlkjihgfedcba
Cipher: abcdefghijklmnopqrstuvwxyz

Now finally, to test decryption, look at the following example (note the use of -d = decryption):

./affine.pl -r 3 -m 13 -z afile | ./affine.pl -d -r 3 -m 13 -z -v

Plain:  naqdtgwjzmpcsfviylobreuhxk
Cipher: abcdefghijklmnopqrstuvwxyz

In this example "Plain" is actually the ciphertext, and "Cipher" the decrypted ciphertext due to the use of -d.

6. Using user-defined character set (-f FILE)

User-defined character sets can be used. They are read from a file where the characters are separated by a comma. If the option -f is not used, the software uses its built-in 29-character set.

Let us assume a file named "ba", including the following characters:

b,a,d,c,f,e,h,g,j,i,l,k,n,m,p,o,r,q,t,s,v,u,x,w,z,y,,,,,

To use the same example as in the case 5 above, now enhanced with our own character set, just enter the following:

./affine.pl -r 3 -m 13 -z -f ba afile | ./affine.pl -d -r 3 -m 13 -z -f ba -v

Plain:  dogvnyqxfiaphskzrucjbmetw
Cipher: abcdefghijklmnopqrstuvwxyz