#!/usr/local/bin/perl
#***************************************************************
#
# Safer	- implement the SAFER algorithm and wrap it all
#		up as a simple replacement for the current encryption
#
# Written by Kevin Grumball
#
# Copyright (c) Actinic Software Ltd 1997-8
#
# This file is based on safer.c
#
# DESCRIPTION:	 block-cipher algorithm SAFER (Secure And Fast Encryption
#					  Routine) in its four versions: SAFER K-64, SAFER K-128,
#					  SAFER SK-64 and SAFER SK-128.
#
# AUTHOR:			Richard De Moliner (demoliner@isi.ee.ethz.ch)
#					  Signal and Information Processing Laboratory
#					  Swiss Federal Institute of Technology
#					  CH-8092 Zuerich, Switzerland
#
# $Revision: 18819 $
#
#***************************************************************

package ActinicSafer;

use integer;
use strict;
#
# Specify the export criteria
#
use Exporter;

@ActinicSafer::ISA = ('Exporter');
@ActinicSafer::EXPORT_OK = ('ExpandUserKey', 'EncryptBlock', 'DecryptBlock');

$ActinicSafer::SAFER_K64_DEFAULT_NOF_ROUNDS =  6;
$ActinicSafer::SAFER_K128_DEFAULT_NOF_ROUNDS = 10;
$ActinicSafer::SAFER_SK64_DEFAULT_NOF_ROUNDS =  8;
$ActinicSafer::SAFER_SK128_DEFAULT_NOF_ROUNDS = 10;

#
# Number to use
#
$ActinicSafer::SAFER_NOF_ROUNDS = $ActinicSafer::SAFER_SK128_DEFAULT_NOF_ROUNDS;

#
# Maximum allowed
#
$ActinicSafer::SAFER_MAX_NOF_ROUNDS = 13;

#
# The block length is 8 bytes
#
$ActinicSafer::SAFER_BLOCK_LEN = 8;
$ActinicSafer::SAFER_KEY_LEN  = (1 + $ActinicSafer::SAFER_BLOCK_LEN * (1 + 2 * $ActinicSafer::SAFER_MAX_NOF_ROUNDS));

#
# Are we using the strengthened form?
#
$ActinicSafer::SAFER_STRENGTHENED = 1;
$ActinicSafer::TAB_LEN = 256;										# Length of tables

#
# These tables are used for lookups. they
# are initialised in the constructor
#
#@ActinicSafer::m_ExpTab;
#@ActinicSafer::m_LogTab;
#
# We implement our own byte-based arithmetic
#
	#***************************************************************
	#
	# Add		- add two bytes
	#
	# Params:	x, y	- two byte values
	#
	# Returns:	x + y
	#
	#***************************************************************

	sub Add
		{
		my ($x, $y) = @_;
		
		return(((($x & 0xFF) + ($y & 0xFF)) & 0xFF));
		}

	#***************************************************************
	#
	# Subtract	- subtract two bytes
	#
	# Params:	x, y	- two byte values
	#
	# Returns:	x - y
	#
	#***************************************************************

	sub Subtract
		{
		my ($x, $y) = @_;
		
		return(((($x & 0xFF) - ($y & 0xFF)) & 0xFF));
		}

	#***************************************************************
	#
	# Xor		- xor two bytes
	#
	# Params:	x, y	- two byte values
	#
	# Returns:	x ^ y
	#
	#***************************************************************

	sub Xor
		{
		my ($x, $y) = @_;
		
		return(((($x & 0xFF) ^ ($y & 0xFF)) & 0xFF));
		}

	#***************************************************************
	#
	# Exp		- 45 to the power x modulo 257
	#
	# Params:	x	- byte value
	#
	# Returns:	45 ** x mod 257
	#
	#***************************************************************

	sub Exp
		{
		my $x = shift;
		
		return($ActinicSafer::m_ExpTab[($x & 0xFF)]);
		}

	#***************************************************************
	#
	# Log		- logarithm base 45
	#
	# Params:	x	- byte value
	#
	# Returns:	log x
	#
	#***************************************************************

	sub Log
		{
		my $x = shift;

		return($ActinicSafer::m_LogTab[($x & 0xFF)]);
		}

	#***************************************************************
	#
	# RotateLeft	- rotate a byte left by a number of bits
	#
	# Params:	x	- byte value
	#
	# Returns:	x rotated n places
	#
	#***************************************************************

	sub RotateLeft	 
		{
		my ($x, $n) = @_;
		my ($top, $bottom);

		$top = (($x & 0xFF) << $n) & 0xFF;
		$bottom = (($x & 0xFF) >> (8 - $n)) & 0xFF;
		return (($top | $bottom) & 0xFF);
		}

	#***************************************************************
	#
	# Pad		- pad out to nearest 8 byte boundary
	#
	# Params:	bIn		- up to 8 bytes
	#
	# Returns:	8 bytes zero filled at high end
	#
	#***************************************************************

	sub Pad
		{
		my  (@bIn) = @_;
		my 	@bOut;
		my 	$nIndex;

		for ($nIndex = 0; ($nIndex < @bIn.length) && ($nIndex < 8); $nIndex++)
			{
			$bOut[$nIndex] = $bIn[$nIndex];				# Copy bytes
			}
		while ($nIndex < 8)
			{
			$bOut[$nIndex] = 0;							# Pad with zeroes
			}
		return(@bOut);
		}

	#***************************************************************
	#
	# InitTables	- initialises the static stables used by the class
	#
	#***************************************************************

	sub InitTables
		{
		my 	($i, $ExpValue);

		$ExpValue = 1;
		for ($i = 0; $i < $ActinicSafer::TAB_LEN; $i++)
			{
			$ActinicSafer::m_ExpTab[$i] = ($ExpValue & 0xFF);
			$ActinicSafer::m_LogTab[$ActinicSafer::m_ExpTab[$i] & 0xFF] = $i;
			$ExpValue = ($ExpValue * 45) % 257;
			}
		}

	#***************************************************************
	#
	# ExpandUserKey	- builds a key for encryption/decryption
	#
	# Expands a user-selected key of length 64 bits or 128 bits to a encryption /
	# decryption key. If your user-selected key is of length 64 bits, then give
	# this key to both arguments 'userkey_1' and 'userkey_2', e.g.
	# 'Safer_Expand_Userkey(z, z, key)'. Note: SAFER K-64 and SAFER SK-64 with a
	# user-selected key 'z' of length 64 bits are identical to SAFER K-128 and
	# SAFER SK-128 with a user-selected key 'z z' of length 128 bits,
	# respectively.
	#
	#
	# I've modified this to take a single key of 128 bits because
	# it's much more convenient for me  KSG
	#
	# Input:  'userkey'  contains the 128 bits of user key.
	#
	# Output: 'key'		  contains the expanded key.
	#
	#********************************************************************************

	sub ExpandUserKey(@)
		{
		my (@userkey) = @_;
		
		my 	($i, $j);
		#
		# check the maximum permitted encryption
		#
		if ($$::g_pSetupBlob{'KEY_LENGTH'} < 128)
			{
			for ($i = $$::g_pSetupBlob{'KEY_LENGTH'} / 8; $i < 16; $i++)
				{
				$userkey[$i] = 0;
				}
			}
		my 	@ka;
		my 	@kb;
		my	$nof_rounds = $ActinicSafer::SAFER_NOF_ROUNDS;
		my	$nIndex = 0;
		my	@key;
		
		$key[$nIndex++] = $nof_rounds;
		$ka[$ActinicSafer::SAFER_BLOCK_LEN] = 0;
		$kb[$ActinicSafer::SAFER_BLOCK_LEN] = 0;

		for ($j = 0; $j < $ActinicSafer::SAFER_BLOCK_LEN; $j++)
			{
			$ka[$j] = RotateLeft($userkey[$j], 5);
			$ka[$ActinicSafer::SAFER_BLOCK_LEN] ^= $ka[$j];
			$key[$nIndex] = $userkey[$j+8];
			$kb[$j] = $key[$nIndex++];
			$kb[$ActinicSafer::SAFER_BLOCK_LEN] ^= $kb[$j];
			}
			
		for ($i = 1; $i <= $nof_rounds; $i++)
			{
			for ($j = 0; $j < $ActinicSafer::SAFER_BLOCK_LEN + 1; $j++)
				{
				$ka[$j] = RotateLeft($ka[$j], 6);
				$kb[$j] = RotateLeft($kb[$j], 6);
				}
			for ($j = 0; $j < $ActinicSafer::SAFER_BLOCK_LEN; $j++)
				{
				if ($ActinicSafer::SAFER_STRENGTHENED)
					{
					$key[$nIndex++] = ((($ka[($j + 2 * $i - 1) % ($ActinicSafer::SAFER_BLOCK_LEN + 1)] & 0xFF)
										  + $ActinicSafer::m_ExpTab[$ActinicSafer::m_ExpTab[18 * $i + $j + 1]]) & 0xFF);
					}
				else
					{
					$key[$nIndex++] = ((($ka[$j] & 0xFF)
										  + $ActinicSafer::m_ExpTab[$ActinicSafer::m_ExpTab[18 * $i + $j + 1]]) & 0xFF);
					}
				}
			for ($j = 0; $j < $ActinicSafer::SAFER_BLOCK_LEN; $j++)
				{
				if ($ActinicSafer::SAFER_STRENGTHENED)
					{
					$key[$nIndex++] = ((($kb[($j + 2 * $i) % ($ActinicSafer::SAFER_BLOCK_LEN + 1)] & 0xFF)
										  + $ActinicSafer::m_ExpTab[$ActinicSafer::m_ExpTab[18 * $i + $j + 10]]) & 0xFF);
					}
				else
					{
					$key[$nIndex++] = ((($kb[$j] & 0xFF)
										  + $ActinicSafer::m_ExpTab[$ActinicSafer::m_ExpTab[18 * $i + $j + 10]]) & 0xFF);
					}

				}
			}
		for ($j = 0; $j < $ActinicSafer::SAFER_BLOCK_LEN + 1; $j++)
			{
			$ka[$j] = $kb[$j] = 0;
			}
		return(@key);
		}


	#***************************************************************
	#
	# EncryptBlock		- encrypt a block using a key 
	#
	# Input:	@block_in -  plain-text block.
	#			@key	  -  contains the expanded key.
	#
	# Returns:	The cipher-text block.
	#
	#****************************************************************

	sub EncryptBlock
		{
		my	($a, $b, $c, $d, $e, $f, $g, $h, $t);
		my	$round;

		$a = shift; 
		$b = shift; 
		$c = shift; 
		$d = shift;
		$e = shift; 
		$f = shift; 
		$g = shift; 
		$h = shift;

		$round = shift;										# First byte is number of rounds
		if ($ActinicSafer::SAFER_MAX_NOF_ROUNDS < $round) 
			{
			$round = $ActinicSafer::SAFER_MAX_NOF_ROUNDS;
			}

		while($round-- > 0)
			{
			$a = Xor($a, shift); 
			$b = Add($b, shift); 
			$c = Add($c, shift); 
			$d = Xor($d, shift);
			$e = Xor($e, shift); 
			$f = Add($f, shift); 
			$g = Add($g, shift); 
			$h = Xor($h, shift);

			$a = Add(Exp($a), shift); 
			$b = Xor(Log($b), shift);
			$c = Xor(Log($c), shift); 
			$d = Add(Exp($d), shift);
			$e = Add(Exp($e), shift); 
			$f = Xor(Log($f), shift);
			$g = Xor(Log($g), shift); 
			$h = Add(Exp($h), shift);

			$b = Add($b, $a); $a = Add($a, $b); $d = Add($d, $c); $c = Add($c, $d); 
			$f = Add($f, $e); $e = Add($e, $f); $h = Add($h, $g); $g = Add($g, $h);
			$c = Add($c, $a); $a = Add($a, $c); $g = Add($g, $e); $e = Add($e, $g); 
			$d = Add($d, $b); $b = Add($b, $d); $h = Add($h, $f); $f = Add($f, $h);
			$e = Add($e, $a); $a = Add($a, $e); $f = Add($f, $b); $b = Add($b, $f); 
			$g = Add($g, $c); $c = Add($c, $g); $h = Add($h, $d); $d = Add($d, $h);

			$t = $b; $b = $e; $e = $c; $c = $t; $t = $d; $d = $f; $f = $g; $g = $t;
			}

		$a = Xor($a, shift); 
		$b = Add($b, shift); 
		$c = Add($c, shift); 
		$d = Xor($d, shift);
		$e = Xor($e, shift); 
		$f = Add($f, shift); 
		$g = Add($g, shift); 
		$h = Xor($h, shift);

		return($a, $b, $c, $d, $e, $f, $g, $h);
		}


	#***************************************************************
	#
	# DecryptBlock		- encrypt a block using a key 
	#
	# Input:	'@block_in'  contains the cipher-text block.
	#			'key'		 contains the expanded key.
	#
	# Returns:	The plain-text block.
	#
	#****************************************************************

	sub DecryptBlock(@@)
		{   
		my	($a, $b, $c, $d, $e, $f, $g, $h, $t);
		my	$round;
		my	$nIndex = 0;									# Index within key
		my	@block_out;										# Output value

		$a = shift; 										# First 8 entries
		$b = shift; 
		$c = shift; 
		$d = shift; 
		$e = shift; 
		$f = shift; 
		$g = shift; 
		$h = shift; 
		
		my @key = @_;										# Rest of it is the key
		$round = $key[0];
		if ($ActinicSafer::SAFER_MAX_NOF_ROUNDS < $round) 
			{
			$round = $ActinicSafer::SAFER_MAX_NOF_ROUNDS;
			}

		$nIndex += $ActinicSafer::SAFER_BLOCK_LEN * (1 + 2 * $round);
		
		$h = Xor($h, $key[$nIndex]); 
		$g = Subtract($g, $key[--$nIndex]); 
		$f = Subtract($f, $key[--$nIndex]); 
		$e = Xor($e, $key[--$nIndex]);
		$d = Xor($d, $key[--$nIndex]); 
		$c = Subtract($c, $key[--$nIndex]); 
		$b = Subtract($b, $key[--$nIndex]); 
		$a = Xor($a, $key[--$nIndex]);

		while ($round-- > 0)
			{
			$t = $e; $e = $b; $b = $c; $c = $t; $t = $f; $f = $d; $d = $g; $g = $t;

			$a = Subtract($a, $e); $e = Subtract($e, $a); $b = Subtract($b, $f); $f = Subtract($f, $b); 
			$c = Subtract($c, $g); $g = Subtract($g, $c); $d = Subtract($d, $h); $h = Subtract($h, $d);
			$a = Subtract($a, $c); $c = Subtract($c, $a); $e = Subtract($e, $g); $g = Subtract($g, $e); 
			$b = Subtract($b, $d); $d = Subtract($d, $b); $f = Subtract($f, $h); $h = Subtract($h, $f); 
			$a = Subtract($a, $b); $b = Subtract($b, $a); $c = Subtract($c, $d); $d = Subtract($d, $c); 
			$e = Subtract($e, $f); $f = Subtract($f, $e); $g = Subtract($g, $h); $h = Subtract($h, $g);

			$h = Subtract($h, $key[--$nIndex]); 
			$g = Xor($g, $key[--$nIndex]); 
			$f = Xor($f, $key[--$nIndex]); 
			$e = Subtract($e, $key[--$nIndex]);
			$d = Subtract($d, $key[--$nIndex]); 
			$c = Xor($c, $key[--$nIndex]); 
			$b = Xor($b, $key[--$nIndex]); 
			$a = Subtract($a, $key[--$nIndex]);

			$h = Xor(Log($h), $key[--$nIndex]); 
			$g = Subtract(Exp($g), $key[--$nIndex]);
			$f = Subtract(Exp($f), $key[--$nIndex]); 
			$e = Xor(Log($e), $key[--$nIndex]);
			$d = Xor(Log($d), $key[--$nIndex]); 
			$c = Subtract(Exp($c), $key[--$nIndex]);
			$b = Subtract(Exp($b), $key[--$nIndex]); 
			$a = Xor(Log($a), $key[--$nIndex]);
			}
		return($a, $b, $c, $d, $e, $f, $g, $h);
		}

#-----------------------------------------------------------------------------
#
# Flag the end of the package
#
#-----------------------------------------------------------------------------

1;

package main;
