#!/usr/local/bin/perl
#***************************************************************
#
# Encrypt	- run the Actinic encryption
#
# Written by Kevin Grumball
#
# Copyright (c) Actinic Software Ltd 1998
#
# $Revision: 18819 $
#
#***************************************************************

package ActinicEncrypt;

use <Actinic:Variable Name="ActinicSafer"/>;
use <Actinic:Variable Name="ActinicDiffie"/>;
#use ActinicSafer;
#use ActinicDiffie;

use integer;
use strict;

#***************************************************************
#
# InitEncrypt	- initialise the encryption
#
# Params:	@_	- public key as a little-endian array of bytes
#
#***************************************************************

sub InitEncrypt
	{
	ActinicDiffie::InitDiffie(@_);				# Initialise with public key
	ActinicSafer::InitTables();
	}

#***************************************************************
#
# Encrypt	- encrypt a block using a public key 
#
# Input:	 $sHeavy		- text for heavy encryption
#			 $sLight		- text for light encryption
#
# Expects:	 @g_nPublicKey	- public key to encrypt with
#			 @g_nBase		- base value (DH paramter)
#			 @g_nPrime		- Catalog prime number (DH parameter)
#
# Returns:	Output data contains the cipher-text block.
#			The first 4 bytes are a magic number indicating
#			the size of the public key and the prime/generator
#			pair. These values are hard-coded in.
#			The next n bytes are the public key
#			After that is the data.
#
#****************************************************************

sub Encrypt
	{
	my	($sHeavy, $sLight) = @_;
	my	($i, $j);									# Simple counters
	my	(@bKey, @bFixedKey, $sCipherHeavy, $sCipherLight);
	#
	@bFixedKey = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16);
	#
	# encrypt the non-essential data using Safer with our fixed key
	#
	$sCipherLight = EncryptSafer($sLight, @bFixedKey);
	#
	# check if there is any data to be heavily encrypted using Diffie-Helman
	# If there isn't, we pass 0 as the length of heavily encrypted data and
	# 0 as the public key
	#
	if(!$sHeavy)
		{
		$sCipherHeavy = "";
		#
		# Pack the whole lot up into a single string and return it
		#
		my $sHeader = pack("C4NNC16",
					$ActinicDiffie::BLOB_VERSION_2,		# BLOB header version 2.0
					$ActinicDiffie::BLOB_SAFER_SK128,	# Using SAFER_SK128
					$ActinicDiffie::BLOB_PRIME_128_1,	# Using first prime of 128 bits in our table
					$ActinicDiffie::KEY_LENGTH_128,		# 128 bit key
					0,												# 0 as Length of heavily encrypted data
					(length $sCipherLight),					# Length of lightly encrypted data
					ActinicDiffie::AsBytes(0));			# empty public key
		return(join '', $sHeader, $sCipherHeavy, $sCipherLight);
		}
	#
	# To start with, we need to generate a Diffie-Hellman
	# key for all the block encryption. To do this we
	# need the public key supplied (which should be 256 bits)
	# and the Diffie-Hellman parameters which are built in to
	# this class
	#
	my	$nMyPrivateKey = GenerateRandom($sHeavy . $sLight);	# Build random key
	my 	($nMyPublicKey, $nMyEncryptKey);
	#
	# Now our key should be thoroughly scrambled.
	# To tell the recipient what key we've used, we
	# have to build a public key and a private key.
	#
	# The public key is the Diffie-Hellman transform
	# based on the private key (the number we just generated)
	# the base and the prime number
	#
	#
	# The private key must be less than (prime-1)
	#
	my $nPrimeSubOne = ActinicDiffie::badd($ActinicDiffie::g_nPrime, '-1');	# Subtract 1
	$nMyPrivateKey = ActinicDiffie::bmod($nMyPrivateKey, $nPrimeSubOne);	# Use simple modulo

	$nMyPublicKey = ActinicDiffie::ModPow($ActinicDiffie::g_nBase, $nMyPrivateKey, $ActinicDiffie::g_nPrime);	# My public key
	#
	# Now we need the block cipher key which we get from
	# a further Diffie-Hellman transform
	#
	$nMyEncryptKey = ActinicDiffie::ModPow($ActinicDiffie::g_nPublicKey, $nMyPrivateKey, $ActinicDiffie::g_nPrime);	# Encryption key to
	
#	print "Remote public key = ", $ActinicDiffie::g_nPublicKey, " = 0x", ActinicDiffie::AsHex($ActinicDiffie::g_nPublicKey), "\n";
#	print "Base = $ActinicDiffie::g_nBase\n";
#	print "Prime = $ActinicDiffie::g_nPrime\n";
#	print "His public key =    ", $ActinicDiffie::g_nPublicKey, " = 0x", ActinicDiffie::AsHex($ActinicDiffie::g_nPublicKey), "\n";
#	print "My private key =    ", $nMyPrivateKey, " = 0x", ActinicDiffie::AsHex($nMyPrivateKey), "\n";
#	print "My public key =     ", $nMyPublicKey, " = 0x", ActinicDiffie::AsHex($nMyPublicKey), "\n";
#	print "My encryption key = ", $nMyEncryptKey, " = 0x", ActinicDiffie::AsHex($nMyEncryptKey), "\n";
	
	@bKey = ActinicDiffie::AsBytes($nMyEncryptKey);	# Get usable encryption key
	
	$sCipherHeavy = EncryptSafer($sHeavy, @bKey);	# Encrypt the heavy data with the DH key
	#
	# Pack the whole lot up into a single string and return it
	#
	my $sHeader = pack("C4NNC16",
				$ActinicDiffie::BLOB_VERSION_2,		# BLOB header version 2.0
				$ActinicDiffie::BLOB_SAFER_SK128,	# Using SAFER_SK128
				$ActinicDiffie::BLOB_PRIME_128_1,	# Using first prime of 128 bits in our table
				$ActinicDiffie::KEY_LENGTH_128,		# 128 bit key
				(length $sCipherHeavy),				# Length of heavily encrypted data
				(length $sCipherLight),				# Length of lightly encrypted data
				ActinicDiffie::AsBytes($nMyPublicKey));	# My public key
	return(join '', $sHeader, $sCipherHeavy, $sCipherLight);
	}

#***************************************************************
#
# EncryptSafer	- encrypt a block using a symmetrical key 
#
# Input:	 $sPlaintext	- plain-text string
#			 @bKey			- key as 16 byte array
#
# Returns:	Output string contains the cipher-text block.
#
#****************************************************************

sub EncryptSafer
	{
	my	($sPlaintext, @bKey) = @_;
	my	(@bExpanded, @block_in);

	@block_in = (unpack "C*", $sPlaintext);
	@bExpanded = ActinicSafer::ExpandUserKey(@bKey);
	#
	# The amount of output data will include the input data
	# length rounded up to the next 8 bytes
	#
	my	$nCipherLength = (int(($#block_in + 7) / 8) * 8); 
	my	(@bCipher, @bIn, @bOut, $nPos, $i);
	my	$nOutPos = 0;								# Cipher text buffer
	#
	# We now encrypt the data in 8 byte blocks and
	# write it to the output array
	#
	for ($nPos = 0; $nPos <= $#block_in; $nPos = $nPos + 8)
		{
		#
		# If we have 8 more bytes, then we process them
		#
		if (($nPos + 7) <= $#block_in)
			{
			@bIn = @block_in[$nPos .. ($nPos+7)];	# We can take 8 bytes
			}
		#
		# If not, we take what we have and pad them out to 8 bytes
		# with zeroes
		#
		else
			{
			@bIn = ActinicSafer::Pad(@block_in[$nPos .. $#block_in]);	# Pad out to 8 bytes
			}
		@bOut = ActinicSafer::EncryptBlock(@bIn, @bExpanded);		# Encode it
		for ($i = 0; $i < 8; $i++)
			{
			$bCipher[$nOutPos++] = $bOut[$i];		# Store encrypted stream
			}
		}
	#
	# Pack the whole lot up into a single string and return it
	#
	return(pack("C*", @bCipher));
	}

#***************************************************************
#
# DecryptSafer	- decrypt a block using a symmetrical key 
#
# Input:	 $sEncryptedtext	- encrypted-text string
#			 @bKey			- key as 16 byte array
#
# Returns:	Output string contains the decrypted text block.
#
#****************************************************************

sub DecryptSafer
	{
	my	($sEncryptedtext, @bKey) = @_;
	my	(@bExpanded, @block_in);

	@block_in = (unpack "C*", $sEncryptedtext);
	@bExpanded = ActinicSafer::ExpandUserKey(@bKey);
	#
	# The amount of output data will include the input data
	# length rounded up to the next 8 bytes
	#
	my	$nCipherLength = (int(($#block_in + 7) / 8) * 8); 
	my	(@bCipher, @bIn, @bOut, $nPos, $i);
	my	$nOutPos = 0;								# Cipher text buffer
	#
	# We now decrypt the data in 8 byte blocks and
	# write it to the output array
	#
	for ($nPos = 0; $nPos <= $#block_in; $nPos = $nPos + 8)
		{
		#
		# If we have 8 more bytes, then we process them
		#
		if (($nPos + 7) <= $#block_in)
			{
			@bIn = @block_in[$nPos .. ($nPos+7)];	# We can take 8 bytes
			}
		#
		# If not, we take what we have and pad them out to 8 bytes
		# with zeroes
		#
		else
			{
			@bIn = ActinicSafer::Pad(@block_in[$nPos .. $#block_in]);	# Pad out to 8 bytes
			}
		@bOut = ActinicSafer::DecryptBlock(@bIn, @bExpanded);		# Decode it
		for ($i = 0; $i < 8; $i++)
			{
			$bCipher[$nOutPos++] = $bOut[$i];		# Store decrypted stream
			}
		}
	#
	# Pack the whole lot up into a single string and return it
	#
	return(pack("C*", @bCipher));
	}

#***************************************************************
#
# GenerateRandom	- build a reasonable random key
#
# Params:	$sData	- data to be encrypted
#
# Returns:	Random number as a big integer
#
#***************************************************************

sub GenerateRandom()
	{
	#
	# Now we need a reasonable random number to seed
	# the random number generator. The standard constructor
	# uses the current time in milliseconds, but this isn't
	# really random enough for us and it might be predicted.
	#
	# To improve matters, we take the entire datastream
	# that we're going to encypt and we pass it through
	# the SAFER algorithm which gives very high bit diffusion
	# at a very low code cost (since we already have it
	# coded right here)
	#
	#
	# Start by generating a pseudo-random 128 bit key using the
	# built-in random number generator
	#
	my	($sData) = @_;
	my	($i, $nPos, $nOffset);
	my	(@bKey, @bExpanded);						# SAFER keys
	my	(@bIn, @bOut);

	srand (time() ^ ($$ + ($$ << 15)));				# Load random number generator
	for ($i = 0; $i < 16; $i++)						# Build a 16 byte key
		{
		$bKey[$i] = int(rand 256) & 0xFF;			# Generate 1 byte at a time
		}
	#
	# Now run SAFER on each block in turn and combine the starting key
	# on each cycle with the encrypted data
	#
	@bExpanded = ActinicSafer::ExpandUserKey(@bKey);	# Expand key
	my @block_in = unpack("C*", $sData);			# Unpack into an array
	
#	print "Length of data = $#block_in\n";
	
	for ($nPos = 0; $nPos < $#block_in; $nPos += 8)
		{
		#
		# If we have 8 more bytes, then we process them
		#
		if (($nPos + 7) <= $#block_in)
			{
			@bIn = $block_in[$nPos .. ($nPos+7)];	# We can take 8 bytes
			}
		#
		# If not, we take what we have and pad them out to 8 bytes
		# with zeroes
		#
		else
			{
			@bIn = ActinicSafer::Pad($block_in[$nPos .. $#block_in]);	# Pad out to 8 bytes
			}
			
		@bOut = ActinicSafer::EncryptBlock(@bIn, @bExpanded);		# Encode it
		for ($i = 0; $i < 8; $i++)
			{
			$bKey[$i + $nOffset] ^= $bOut[$i];		# Combine with running value
			#
			# If we only have 8 bytes of input data then we need to re-use
			# those bytes
			#
			if ($#block_in < 8)						# Will only be one block to use
				{
				$bKey[$i+8] ^= $bOut[$i];			# Combine with running value
				}
			}
		#
		# If we have enough, we flip between the lower
		# 8 bytes and upper 8 bytes of the key
		#
		$nOffset ^= 8;								# Toggle between offset of 0 and 8
		}
				
	return(ActinicDiffie::FromBytes(@bKey));		# Convert to a big integer
	}
1;
