Hashing with the SHA-256 Algorithm in JavaScript

The JavaScript code below displays a short message, hashes it with the SHA256 hash algorithm, and displays the result. To see it in action, copy it into an .html file and open that file in a web browser that runs JavaScript.

My original intent with this code was… well, actually I don’t quite remember, to tell the truth.  I think I arrived at this hash function while trying to figure out how to implement some well-known cryptographic algorithm or other.  I never did really get the cryptosystem working, but I think this was supposed to be the first step.

Even assuming no bugs in my implementation, and that should by no means be assumed, I’m not sure how reputable this algorithm is considered by cryptologists nowadays.  I think that SHA-512 is preferred to SHA-256, at the very least, but you can’t trivially amend this implementation to get there, because it would require using bigger integers than JavaScript supports.

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

// main

function main()
{
	var newline = "<br />";

	var stringToHash = "The quick brown fox jumps over the lazy dog";

	document.write("stringToHash is: " + stringToHash + newline);

	var bytesToHash = ByteHelper.stringUTF8ToBytes
	(
		stringToHash
	);

	document.write("bytesToHash is: " + bytesToHash + newline);

	var hasher = new HasherSHA256();

	var bytesHashedAs32BitIntegers = hasher.hashBytes(bytesToHash);
	var bytesHashedAsStringHexadecimal = "";
	
	for (var i = 0; i < bytesHashedAs32BitIntegers.length; i++)
	{
		bytesHashedAsStringHexadecimal += (bytesHashedAs32BitIntegers[i]>>>0).toString(16);	
	}

	document.write("bytesHashedAsStringHexadecimal is: " + bytesHashedAsStringHexadecimal + newline);
}

// classes

function BitHelper()
{
	// static class
}
{
	BitHelper.byteToBitString = function(byteToConvert)
	{
		var returnValue = "";

		var bitsPerByte = BitStream.BitsPerByte;
		for (var i = 0; i < bitsPerByte; i++)
		{
			var iReversed = bitsPerByte - i - 1;
			var bitValue = (byteToConvert >> iReversed) & 1;
			returnValue += bitValue;
			
		}

		return returnValue;
	}

	BitHelper.bytesToBitString = function(bytesToConvert)
	{
		var returnValue = "";

		for (var i = 0; i < bytesToConvert.length; i++)
		{
			var byte = bytesToConvert[i];
			var byteAsString = this.byteToBitString(byte);
			returnValue += byteAsString + ",";
		}

		return returnValue;
	}

	BitHelper.rotateInteger32BitRight = function(integerToRotate, bitsToRotateBy)
	{
		return this.rotateIntegerWithBitWidthRight
		(
			integerToRotate,
			32, // bitsInInteger
			bitsToRotateBy
		);
	}


	BitHelper.rotateIntegerWithBitWidthRight = function
	(
		integerToRotate, 
		bitsInInteger, 
		bitsToRotateBy
	)
	{
		var bitsToRotateByReversed = 
			bitsInInteger - bitsToRotateBy;

		var returnValue = 
			(integerToRotate >>> bitsToRotateBy) 
			| (integerToRotate << (bitsToRotateByReversed)); 

		return returnValue;
	}
}

function BitStream(bytes, seekToEnd)
{
	this.bytes = bytes;
	this.byteOffset = 0;
	this.bitOffsetWithinByte = 0;

	if (seekToEnd == true)
	{
		this.byteOffset = this.bytes.length;
	}

	this.bitOffsetWithinByteReversed =
		BitStream.BitsPerByte 
		- this.bitOffsetWithinByte
		- 1;
}
{
	// constants

	BitStream.BitsPerByte = 8;

	// instance methods

	BitStream.prototype.bitOffsetAdvance = function(numberOfBitsToAdvance)
	{
		var bitsPerByte = BitStream.BitsPerByte;

		var numberOfBytesToAdvance = Math.floor
		(
			numberOfBitsToAdvance / bitsPerByte
		);

		this.byteOffset += numberOfBytesToAdvance;

		this.bitOffsetWithinByte +=
			numberOfBitsToAdvance 
			- (numberOfBytesToAdvance * bitsPerByte);

		if (this.bitOffsetWithinByte >= bitsPerByte)
		{
			this.byteOffset++;
			this.bitOffsetWithinByte -= bitsPerByte;
		}

		this.bitOffsetWithinByteReversed =
			BitStream.BitsPerByte 
			- this.bitOffsetWithinByte
			- 1;		
	}

	BitStream.prototype.byteOffsetAdvanceIfNecessary = function()
	{
		if (this.byteOffset >= this.bytes.length)
		{
			this.bytes.push(0);	
		}
	}

	BitStream.prototype.readBit = function()
	{
		var byteCurrent = this.bytes[this.byteOffset];
		var bitValue = (byteCurrent >> this.bitOffsetWithinByteReversed) & 1;
		this.bitOffsetAdvance(1);
		return bitValue;
	}

	BitStream.prototype.readBitsAsInteger = function(numberOfBits)
	{
		var returnValue = 0;		

		for (var i = 0; i < numberOfBits; i++)
		{
			var byteCurrent = this.bytes[this.byteOffset];
			var bitCurrent = (byteCurrent >> this.bitOffsetWithinByte) & 1;
			var iReversed = numberOfBits - i - 1;
			var bitValueInPlace = bitCurrent << i; //Reversed;
			returnValue += bitValueInPlace;

			this.bitOffsetAdvance(1);
		}	

		return returnValue;
	}

	BitStream.prototype.readByte = function()
	{
		return this.readBitsAsInteger(BitStream.BitsPerByte);
	}

	BitStream.prototype.readBytes = function(numberOfBytesToRead)
	{
		var returnValues = [];

		for (var i = 0; i < numberOfBytesToRead; i++)
		{
			returnValues.push(this.readByte());
		}

		return returnValues;
	}

	BitStream.prototype.readIntegerUsingBitWidth = function(numberOfBitsInInteger)
	{
		var returnValue = 0;

		for (var i = 0; i < numberOfBitsInInteger; i++)
		{
			var iReversed = numberOfBitsInInteger - i - 1;
			var bitValue = this.readBit();
			var bitValueInPlace = (bitValue << iReversed);
			returnValue += bitValueInPlace;
		}

		return returnValue;	
	}

	BitStream.prototype.readInteger32Bit = function()
	{
		return this.readIntegerUsingBitWidth(32);
	}

	BitStream.prototype.writeBit = function(bitToWrite)
	{
		this.byteOffsetAdvanceIfNecessary();
		var byteCurrent = this.bytes[this.byteOffset];
		var bitValueInPlace = (bitToWrite << this.bitOffsetWithinByteReversed);
		this.bytes[this.byteOffset] += bitValueInPlace;
		this.bitOffsetAdvance(1);
	}

	BitStream.prototype.writeBits = function(bitsToWrite)
	{
		for (var i = 0; i < bitsToWrite.length; i++)
		{
			this.writebit(bitsToWrite[i]);
		}
	}

	BitStream.prototype.writeIntegerUsingBitWidth = function(integerToWrite, numberOfBitsInInteger)
	{
		for (var i = 0; i < numberOfBitsInInteger; i++)
		{
			var iReversed = numberOfBitsInInteger - i - 1;

			//var bitValue = (integerToWrite >>> iReversed) & 1;

			// hack 
			// Can't simply shift right by iReversed
			// for bit widths greater than 32,
			// because (n >>> 32 == n)!

			var temp = integerToWrite;

			for (var j = 0; j < iReversed; j++)
			{
				temp = temp >>> 1;
			}

			var bitValue = temp & 1;

			this.writeBit(bitValue);
		}	
	}
}

function ByteHelper()
{
	// static class
}
{
	ByteHelper.stringUTF8ToBytes = function(stringToConvert)
	{
		var returnValues = [];

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

		return returnValues;
	}
}

function HasherSHA256()
{
	// Adapted from pseudocode found at the URL
	// https://en.wikipedia.org/wiki/Sha256
}
{
	HasherSHA256.prototype.hashBytes = function(messageAsBytes)
	{
		var mask32Bits = 0xFFFFFFFF >>> 0;

		var _h = 
		[
			0x6a09e667,
			0xbb67ae85,
			0x3c6ef372,
			0xa54ff53a,
			0x510e527f,
			0x9b05688c,
			0x1f83d9ab,
			0x5be0cd19,
		];

		var k =
		[
			0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
			0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
			0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
			0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
			0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
			0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
			0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
			0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
		];

		var bitsPerByte = BitStream.BitsPerByte;

		var bitWidthOfMessageLengthField = 64;
		var messageLengthInBitsOriginal = 
			messageAsBytes.length * bitsPerByte;

		var messageAsBitStream = new BitStream
		(
			messageAsBytes, 
			true // seekToEnd
		);

		// end-of-message flag
		var bitWidthOfEndOfMessageFlag = 1;
		messageAsBitStream.writeBit(1);

		var chunkSizeInBits = 512;
		var chunkSizeInBytes = chunkSizeInBits / bitsPerByte;

		var numberOfMessageBitsInFinalMessageChunk =
			messageLengthInBitsOriginal
			% chunkSizeInBits;

		var numberOfBitsInPadding = 
			chunkSizeInBits 
			- 
			(
				numberOfMessageBitsInFinalMessageChunk
				+ bitWidthOfEndOfMessageFlag
				+ bitWidthOfMessageLengthField
			);

		if (numberOfBitsInPadding < 0)
		{
			numberOfBitsInPadding += chunkSizeInBits;
		} 

		for (var i = 0; i < numberOfBitsInPadding; i++)
		{
			messageAsBitStream.writeBit(0);
		}

		messageAsBitStream.writeIntegerUsingBitWidth
		(
			(messageLengthInBitsOriginal), 
			bitWidthOfMessageLengthField
		);

		messageAsBitStream = new BitStream(messageAsBytes);

		var messageLengthInBytesPadded = messageAsBytes.length;

		var messageSizeInChunks = 
			messageLengthInBytesPadded 
			/ chunkSizeInBytes;

		for (var chunkIndex = 0; chunkIndex < messageSizeInChunks; chunkIndex++)
		{
			var chunkAsBytes = messageAsBitStream.readBytes(chunkSizeInBytes);
			var chunkAsBitStream = new BitStream(chunkAsBytes);

			var numberOfEntriesInMessageSchedule = 64;
			var messageScheduleAs32BitIntegers = new Array(numberOfEntriesInMessageSchedule);
		
			for (var i = 0; i < 16; i++)
			{
				var integer32BitFromChunk = chunkAsBitStream.readInteger32Bit();
				messageScheduleAs32BitIntegers[i] = integer32BitFromChunk;
			}

			for (var i = 16; i < 64; i++)
			{
				var wMinus2 = messageScheduleAs32BitIntegers[i - 2];
				var wMinus7 = messageScheduleAs32BitIntegers[i - 7];
				var wMinus15 = messageScheduleAs32BitIntegers[i - 15];
				var wMinus16 = messageScheduleAs32BitIntegers[i - 16];

				var s0 = 
					BitHelper.rotateInteger32BitRight(wMinus15, 7)
					^ BitHelper.rotateInteger32BitRight(wMinus15, 18)
					^ (wMinus15 >>> 3);

				var s1 = 
					BitHelper.rotateInteger32BitRight(wMinus2, 17)
					^ BitHelper.rotateInteger32BitRight(wMinus2, 19)
					^ (wMinus2 >>> 10);

				var w = 
				(
					wMinus16 + s0 + wMinus7 + s1
				) & mask32Bits;


				messageScheduleAs32BitIntegers[i] = w;
			}

			var w = messageScheduleAs32BitIntegers;

			var a = _h[0];
			var b = _h[1];
			var c = _h[2];
			var d = _h[3];
			var e = _h[4];
			var f = _h[5];
			var g = _h[6];
			var h = _h[7];

			for (var i = 0; i < 64; i++)
			{
				var s1 = 
					BitHelper.rotateInteger32BitRight(e, 6) 
					^ BitHelper.rotateInteger32BitRight(e, 11) 
					^ BitHelper.rotateInteger32BitRight(e, 25);

				var ch = (e & f) ^ (~e & g);

				var s0 = 
					BitHelper.rotateInteger32BitRight(a, 2) 
					^ BitHelper.rotateInteger32BitRight(a, 13) 
					^ BitHelper.rotateInteger32BitRight(a, 22);

				var maj = (a & b) ^ (a & c) ^ (b & c);

				var temp1 = 
					h 
					+ s1 
					+ ch 
					+ k[i] 
					+ w[i]

				var temp2 = s0 + maj;

				h = g;
				g = f;
				f = e;
				e = (d + temp1)  & mask32Bits;
				d = c;
				c = b;
				b = a;
				a = (temp1 + temp2) & mask32Bits;
			}

			_h[0] += a;
			_h[1] += b;
			_h[2] += c;
			_h[3] += d;
			_h[4] += e;
			_h[5] += f;
			_h[6] += g;
			_h[7] += h;

			for (var i = 0; i < _h.length; i++)
			{
				_h[i] = _h[i] & mask32Bits;
			}

			var one = 1;
				
		} // end for each chunk

		var returnValue = _h;

		return returnValue;

	} // end method hashBytes()

} // end class Hasher

// run

main();

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


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