A Feistel Network Encryption Algorithm in JavaScript

The JavaScript code below implements a Feistel network algorithm, and uses it to encrypt and decrypt some data using a randomly generated key. To see the code in action, copy it into an .html file, and open that file in a web browser that runs JavaScript.

A Feistel network forms the basis of many other encryption algorithms (for example, “Blowfish”), some of which are judged quite secure. The implementation given below, however, is only meant to be a demonstration, and should not be depended on to secure anything.  The wise old elders like to say that only a fool would “roll their own crypto”. Then again, many of those same wise elders don’t really make it super easy to get hold of a professionally implemented, thoroughly bugproofed version for a reasonable price, or if they do it’s only implemented in, like, Haskell or something.  If it was up to me I’d probably put them in a home.

<html>
<body>
<script type='text/javascript'>

// Adapted from an algorithm described at the URL
// https://en.wikipedia.org/wiki/Feistel_network.

// This code, as implemented, should by no means
// be used in any secure applications!

// main

function main()
{
	var stringToEncrypt = "One if by land, two if by sea.";

	write("stringToEncrypt is: " + stringToEncrypt);

	var bytesToEncrypt = ByteHelper.stringUTF8ToBytes
	(
		stringToEncrypt
	);

	write
	(
		"bytesToEncrypt is: " 
		+ ByteHelper.bytesToStringHexadecimal
		(
			bytesToEncrypt
		)
	);

	var keySizeInBits = 32;

	var key = Math.floor
	(	
		Math.random()
		* (Math.pow(2, keySizeInBits) - 1)
	);

	write("key is: " + key);

	var encryptor = new EncryptorFeistel
	(
		key,
		EncryptorFeistel.deriveRoundSubkeysFromKeySimple,
		EncryptorFeistel.encryptionFunctionForRoundSimple
	);

	var bytesEncrypted = encryptor.encryptBytes(bytesToEncrypt);

	write
	(
		"bytesEncrypted is: " 
		+ ByteHelper.bytesToStringHexadecimal
		(
			bytesEncrypted
		)
	);

	var bytesDecrypted = encryptor.decryptBytes(bytesEncrypted);

	write
	(
		"bytesDecrypted is: " 
		+ ByteHelper.bytesToStringHexadecimal
		(
			bytesDecrypted
		)
	);

	var stringDecrypted = ByteHelper.bytesToStringUTF8
	(
		bytesDecrypted
	);

	write("stringDecrypted is: " + stringDecrypted);
}

function write(message)
{
	document.write(message + "<br />");
}

// classes

Array.prototype.overwriteWith = function(other)
{
	this.length = 0;

	for (var i = 0; i < other.length; i++)
	{
		this[i] = other[i];
	}
}

function ByteHelper()
{}
{
	ByteHelper.BitsPerByte = 8;
	ByteHelper.BitsPerNibble = ByteHelper.BitsPerByte / 2;
	ByteHelper.ByteValueMax = Math.pow(2, ByteHelper.BitsPerByte) - 1;

	ByteHelper.bytesToStringUTF8 = function(bytesToConvert)
	{
		var returnValue = "";

		for (var i = 0; i < bytesToConvert.length; i++)
		{
			var charCode = bytesToConvert[i];
			var character = String.fromCharCode(charCode);
			returnValue += character;
		}

		return returnValue;
	}

	ByteHelper.bytesToStringHexadecimal = function(bytesToConvert)
	{
		var returnValue = "";

		var bitsPerNibble = ByteHelper.BitsPerNibble;

		for (var i = 0; i < bytesToConvert.length; i++)
		{
			var byte = bytesToConvert[i];

			for (var d = 1; d >= 0; d--)
			{
				var digitValue = byte >> (bitsPerNibble * d) & 0xF;
				var digitString = "";
				digitString += (digitValue < 10 ? digitValue : String.fromCharCode(55 + digitValue));
				returnValue += digitString;
			}

			returnValue += " ";
		}

		return returnValue;
	}

	ByteHelper.bytesToNumber = function(bytes)
	{
		var returnValue = 0;

		var bitsPerByte = ByteHelper.BitsPerByte;

		for (var i = 0; i < bytes.length; i++)
		{
			var byte = bytes[i];
			var byteValue = (byte << (bitsPerByte * i));
			returnValue += byteValue;
		}

		return returnValue;
	}

	ByteHelper.numberOfBytesNeededToStoreNumber = function(number)
	{
		var numberOfBitsInNumber = Math.ceil
		(
			Math.log(number + 1) / Math.log(2)
		);

		var numberOfBytesNeeded = Math.ceil
		(
			numberOfBitsInNumber 
			/ ByteHelper.BitsPerByte
		);

		return numberOfBytesNeeded;
	}

	ByteHelper.numberToBytes = function(number, numberOfBytesToUse)
	{
		var returnValues = [];

		if (numberOfBytesToUse == null)
		{
			numberOfBytesToUse = this.numberOfBytesNeededToStoreNumber
			(
				number
			);
		}

		var bitsPerByte = ByteHelper.BitsPerByte;

		for (var i = 0; i < numberOfBytesToUse; i++)
		{
			var byte = (number >> (bitsPerByte * i)) & 0xFF;
			returnValues.push(byte);
		}

		return returnValues;
	}

	ByteHelper.stringUTF8ToBytes = function(stringToConvert)
	{
		var returnValues = [];

		for (var i = 0; i < stringToConvert.length; i++)
		{
			var charCode = stringToConvert.charCodeAt(i);
			returnValues.push(charCode);
		}

		return returnValues;
	}

	ByteHelper.xorBytesWithOthers = function(bytes0, bytes1)
	{
		for (var i = 0; i < bytes0.length; i++)
		{
			bytes0[i] ^= bytes1[i];	
		}

		return bytes0;
	}
}

function EncryptorFeistel
(
	key, 
	deriveRoundSubkeysFromKey,
	encryptionFunctionForRound
)
{
	this.key = key;
	this.deriveRoundSubkeysFromKey = deriveRoundSubkeysFromKey;
	this.encryptionFunctionForRound = encryptionFunctionForRound;
}
{
	// static methods

	EncryptorFeistel.deriveRoundSubkeysFromKeySimple = function(keyAs32BitInteger)
	{
		var returnValues = [];

		for (var r = 0; r < 4; r++)
		{
			var subkeyForRound = 
			(
				keyAs32BitInteger
				>> (r * ByteHelper.BitsPerByte)
			) 
			& 0xFF;

			returnValues.push(subkeyForRound);
		}

		return returnValues;
	}


	EncryptorFeistel.encryptionFunctionForRoundSimple = function(right, key)
	{
		// Simple, and presumably easy to crack.

		for (var i = 0; i < right.length; i++)
		{
			right[i] = 
				(right[i] + key) 
				% ByteHelper.ByteValueMax;

		}
	}
	
	// instance methods


	EncryptorFeistel.prototype.decryptBytes = function(bytesToDecrypt)
	{
		var returnValues = this.encryptOrDecryptBytes
		(
			bytesToDecrypt,
			true // decryptRatherThanEncrypt
		);

		return returnValues;		
	}

	EncryptorFeistel.prototype.encryptBytes = function(bytesToEncrypt)
	{
		if (bytesToEncrypt.length % 2 == 1)
		{
			// hack - length must be even!
			bytesToEncrypt.push(0);
		}

		var returnValues = this.encryptOrDecryptBytes
		(
			bytesToEncrypt,
			false // decryptRatherThanEncrypt
		);

		return returnValues;
	}

	EncryptorFeistel.prototype.encryptOrDecryptBytes = function
	(
		bytesToEncryptOrDecrypt,
		decryptRatherThanEncrypt
	)
	{	
		var subkeysForRounds = this.deriveRoundSubkeysFromKey
		(
			this.key
		);
		var numberOfRounds = subkeysForRounds.length;

		var numberOfBytes = bytesToEncryptOrDecrypt.length;
		var numberOfBytesHalf = numberOfBytes / 2;

		var left = bytesToEncryptOrDecrypt.slice
		(
			0, numberOfBytesHalf
		);
		var right = bytesToEncryptOrDecrypt.slice
		(
			numberOfBytesHalf
		);

		var leftNext = left.slice(0);
		var rightNext = right.slice(0);

		for (var r = 0; r < numberOfRounds; r++)
		{
			var subkeyIndex = 
			(
				decryptRatherThanEncrypt 
				? numberOfRounds - r - 1 
				: r
			);

			var subkeyForRound = subkeysForRounds[subkeyIndex];

			// Probably some needlessly inefficient calls 
			// to overwriteWith() happening in this block.

			leftNext.overwriteWith(right);
			rightNext.overwriteWith(left);

			this.encryptionFunctionForRound
			(
				right, subkeyForRound
			);
			ByteHelper.xorBytesWithOthers
			(
				rightNext,
				right
			)

			left.overwriteWith(leftNext);
			right.overwriteWith(rightNext);	
		}

		var returnValue = [].concat(right).concat(left);

		return returnValue;
	}
}

// run

main();

</script>
</body>
</html>

Advertisements
This entry was posted in Uncategorized and tagged , , , , . Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s