// Emacs settings: -*- mode: Fundamental; tab-width: 4; -*-

////////////////////////////////////////////////////////////////////////////
//                                                                        //
// Javascript Cryptographic support:                                      //
//   MD5 (from the RFC 1321 text description)                             //
//   HMAC-MD5 (from RFC 2104)                                             //
//   RC4 (from "Applied Cryptography")                                    //
//                                                                        //
// See also the test code in crypto-test.php                              //
//                                                                        //
// Copyright (c) 2005-2009, Andrew Birrell                                //
//                                                                        //
////////////////////////////////////////////////////////////////////////////


// Caution: Javascript strings are in Unicode; MD5 applies to bit
// sequences and RC4 to sequences of 8-bit bytes.  The functions here use
// the bottom 8 bits of the character codes in strings, ignoring higher
// order bits.  Most likely you should ensure that your strings (keys and
// plain text) are ASCII or UTF-8 before calling these functions.
//
// Similarly, the results (when not encoded in hex) are binary sequences
// of 8-bit bytes, encoded as Unicode strings one byte per character.  These
// are legal Unicode, but typically not legal UTF-8.


//
// Support routines
//

function cryptoLShift32(x, n) {
	// Left circular shift of bottom 32 bits of x by n bits.
	// Resulting bits above 32 are forced to 0.
	//
	// Recall that Javascript numbers are 64-bit floats.
	//
	return ((x << n) | (x >>> (32-n))) & 0xFFFFFFFF;
}

function cryptoAdd32(x, y) {
	// Add mod 2^32
	return (x + y) & 0xFFFFFFFF;
}

function cryptoRawToBinary(raw) {
	// Convert an array of numbers, treated as unsigned 32 bits each, to a
	// binary string of Unicode characters [0..255], low-order byte of each
	// number first.
	//
	// Note that the result is Unicode, and most likely is not valid UTF-8
	//
	var resStr = "";
	for (i = 0; i < raw.length; i++) {
		var word = raw[i];
		for (var j = 0; j < 32; j += 8) {
			resStr += String.fromCharCode((word >>> j) & 0xFF);
		}
	}
	return resStr;
}

function cryptoBinaryToHex(raw) {
	// Convert binary string (as from cryptoRawToBinary) to hex characters.
	var resStr = "";
	var hex = "0123456789abcdef";
	for (i = 0; i < raw.length; i++) {
		var n = raw.charCodeAt(i);
		if (n < 16) resStr += "0";
		resStr += n.toString(16);
	}
	return resStr;
}


//
// MD5
//

// Public entries are md5Binary and md5Hex

function md5Rounds(a, b, fghi, x, s, t) {
	// Common subroutine in each round
	return cryptoAdd32(b, cryptoLShift32(cryptoAdd32(cryptoAdd32(a,fghi),
													 cryptoAdd32(x,t)), s));
}

function md5Round1(a, b, c, d, x, s, t) {
	return md5Rounds(a, b, (b & c) | (~b & d), x, s, t);
}

function md5Round2(a, b, c, d, x, s, t) {
	return md5Rounds(a, b, (b & d) | (c & ~d), x, s, t);
}

function md5Round3(a, b, c, d, x, s, t) {
	return md5Rounds(a, b, b ^ c ^ d, x, s, t);
}

function md5Round4(a, b, c, d, x, s, t) {
	return md5Rounds(a, b, c ^ (b | ~d), x, s, t);
}

function md5Binary(str) {
	// Return a 16-byte string, containing the MD5 hash of "str".
	//
	// I.e., the 4-number result of the raw algorithm has been serialized
	// into bytes with the low-order byte first, as specified in RFC 1321.)
	//
	// See earlier note about Unicode values above 255 in "str".
	//
	// Note that the result is Unicode, and most likely is not valid UTF-8
	//
	// Assumes str.length is less than 256 MBytes (I'm lazy)
	//

	var byteCount = str.length;
	var wordCount = (((byteCount + 8) >>> 6) * 16) + 16;
	var words = new Array(wordCount);
	var i;
	for (i = 0; i < words.length; i++) words[i] = 0;
	for (i = 0; i < byteCount; i++) {
		words[i >> 2] |= (str.charCodeAt(i) & 255) << ((i % 4) * 8);
	}
	words[byteCount >> 2] |= 0x80 << ((byteCount % 4) * 8);
	words[wordCount-2] = byteCount*8; // Low-order 32 bits of bit-count
	words[wordCount-1] = 0; // High-order part of bit-count, in principle

	var a = 0x67452301;
	var b = 0xefcdab89;
	var c = 0x98badcfe;
	var d = 0x10325476;

	for (i = 0; i < wordCount; i += 16) {

		var aa = a;
		var bb = b;
		var cc = c;
		var dd = d;

		// Round 1
		a=md5Round1(a, b, c, d, words[i],     7, 0xd76aa478);
		d=md5Round1(d, a, b, c, words[i+1],  12, 0xe8c7b756);
		c=md5Round1(c, d, a, b, words[i+2],  17, 0x242070db);
		b=md5Round1(b, c, d, a, words[i+3],  22, 0xc1bdceee);

		a=md5Round1(a, b, c, d, words[i+4],   7, 0xf57c0faf);
		d=md5Round1(d, a, b, c, words[i+5],  12, 0x4787c62a);
		c=md5Round1(c, d, a, b, words[i+6],  17, 0xa8304613);
		b=md5Round1(b, c, d, a, words[i+7],  22, 0xfd469501);

		a=md5Round1(a, b, c, d, words[i+8],   7, 0x698098d8);
		d=md5Round1(d, a, b, c, words[i+9],  12, 0x8b44f7af);
		c=md5Round1(c, d, a, b, words[i+10], 17, 0xffff5bb1);
		b=md5Round1(b, c, d, a, words[i+11], 22, 0x895cd7be);

		a=md5Round1(a, b, c, d, words[i+12],  7, 0x6b901122);
		d=md5Round1(d, a, b, c, words[i+13], 12, 0xfd987193);
		c=md5Round1(c, d, a, b, words[i+14], 17, 0xa679438e);
		b=md5Round1(b, c, d, a, words[i+15], 22, 0x49b40821);

		// Round 2
		a=md5Round2(a, b, c, d, words[i+1],   5, 0xf61e2562);
		d=md5Round2(d, a, b, c, words[i+6],   9, 0xc040b340);
		c=md5Round2(c, d, a, b, words[i+11], 14, 0x265e5a51);
		b=md5Round2(b, c, d, a, words[i],    20, 0xe9b6c7aa);

		a=md5Round2(a, b, c, d, words[i+5],   5, 0xd62f105d);
		d=md5Round2(d, a, b, c, words[i+10],  9, 0x02441453);
		c=md5Round2(c, d, a, b, words[i+15], 14, 0xd8a1e681);
		b=md5Round2(b, c, d, a, words[i+4],  20, 0xe7d3fbc8);

		a=md5Round2(a, b, c, d, words[i+9],   5, 0x21e1cde6);
		d=md5Round2(d, a, b, c, words[i+14],  9, 0xc33707d6);
		c=md5Round2(c, d, a, b, words[i+3],  14, 0xf4d50d87);
		b=md5Round2(b, c, d, a, words[i+8],  20, 0x455a14ed);

		a=md5Round2(a, b, c, d, words[i+13],  5, 0xa9e3e905);
		d=md5Round2(d, a, b, c, words[i+2],   9, 0xfcefa3f8);
		c=md5Round2(c, d, a, b, words[i+7],  14, 0x676f02d9);
		b=md5Round2(b, c, d, a, words[i+12], 20, 0x8d2a4c8a);

		// Round 3
		a=md5Round3(a, b, c, d, words[i+5],   4, 0xfffa3942);
		d=md5Round3(d, a, b, c, words[i+8],  11, 0x8771f681);
		c=md5Round3(c, d, a, b, words[i+11], 16, 0x6d9d6122);
		b=md5Round3(b, c, d, a, words[i+14], 23, 0xfde5380c);

		a=md5Round3(a, b, c, d, words[i+1],   4, 0xa4beea44);
		d=md5Round3(d, a, b, c, words[i+4],  11, 0x4bdecfa9);
		c=md5Round3(c, d, a, b, words[i+7],  16, 0xf6bb4b60);
		b=md5Round3(b, c, d, a, words[i+10], 23, 0xbebfbc70);

		a=md5Round3(a, b, c, d, words[i+13],  4, 0x289b7ec6);
		d=md5Round3(d, a, b, c, words[i],    11, 0xeaa127fa);
		c=md5Round3(c, d, a, b, words[i+3],  16, 0xd4ef3085);
		b=md5Round3(b, c, d, a, words[i+6],  23, 0x4881d05);

		a=md5Round3(a, b, c, d, words[i+9],   4, 0xd9d4d039);
		d=md5Round3(d, a, b, c, words[i+12], 11, 0xe6db99e5);
		c=md5Round3(c, d, a, b, words[i+15], 16, 0x1fa27cf8);
		b=md5Round3(b, c, d, a, words[i+2],  23, 0xc4ac5665);

		// Round 4
		a=md5Round4(a, b, c, d, words[i],     6, 0xf4292244);
		d=md5Round4(d, a, b, c, words[i+7],  10, 0x432aff97);
		c=md5Round4(c, d, a, b, words[i+14], 15, 0xab9423a7);
		b=md5Round4(b, c, d, a, words[i+5],  21, 0xfc93a039);

		a=md5Round4(a, b, c, d, words[i+12],  6, 0x655b59c3);
		d=md5Round4(d, a, b, c, words[i+3],  10, 0x8f0ccc92);
		c=md5Round4(c, d, a, b, words[i+10], 15, 0xffeff47d);
		b=md5Round4(b, c, d, a, words[i+1],  21, 0x85845dd1);

		a=md5Round4(a, b, c, d, words[i+8],   6, 0x6fa87e4f);
		d=md5Round4(d, a, b, c, words[i+15], 10, 0xfe2ce6e0);
		c=md5Round4(c, d, a, b, words[i+6],  15, 0xa3014314);
		b=md5Round4(b, c, d, a, words[i+13], 21, 0x4e0811a1);

		a=md5Round4(a, b, c, d, words[i+4],   6, 0xf7537e82);
		d=md5Round4(d, a, b, c, words[i+11], 10, 0xbd3af235);
		c=md5Round4(c, d, a, b, words[i+2],  15, 0x2ad7d2bb);
		b=md5Round4(b, c, d, a, words[i+9],  21, 0xeb86d391);

		a = cryptoAdd32(a, aa);
		b = cryptoAdd32(b, bb);
		c = cryptoAdd32(c, cc);
		d = cryptoAdd32(d, dd);
	}

	return cryptoRawToBinary([a, b, c, d]);
}

function md5Hex(str) {
	// Return the MD5 hash of str as a hex string
	//
	// See earlier note about Unicode values above 255 in "str".
	//
	return cryptoBinaryToHex(md5Binary(str));
}


//
// HMAC
//

// Public entries are hmacBinary, md5HmacBinary, and md5HmacHex

function hmacBinary(str, key, hash, hBlockSize) {
	// Return HMAC signature of str with key and hash, as binary string.
	// "hBlockSize" is the hash algorithm block size in bytes
	//
	// See earlier note about Unicode values above 255 in "str" and "key".

	var myKey = key;
	if (myKey.length > hBlockSize) myKey = hash(myKey);
	var iPad = "";
	var oPad = "";
	for (var i = 0; i < hBlockSize; i++) {
		if (i < myKey.length) {
			var c = myKey.charCodeAt(i);
			iPad += String.fromCharCode(0x36 ^ c);
			oPad += String.fromCharCode(0x5c ^ c);
		} else {
			iPad += String.fromCharCode(0x36);
			oPad += String.fromCharCode(0x5c);
		}
	}
	return hash(oPad + hash(iPad + str));
}

function md5HmacBinary(str, key) {
	// Return HMAC-MD5 signature of str with key, as binary string
	//
	// See earlier note about Unicode values above 255 in "str" and "key".
	//
	return hmacBinary(str, key, md5Binary, 64);
}

function md5HmacHex(str, key) {
	// Return HMAC-MD5 signature of str with key, as hex string
	//
	// See earlier note about Unicode values above 255 in "str" and "key".
	//
	return cryptoBinaryToHex(md5HmacBinary(str, key));
}


//
// RC4
//

var rc4s = new Array();
var rc4i;
var rc4j;

function keyRC4(key) {
	// Set the RC4 globals to start an encrypt/decrypt stream with this key
	//
	// See earlier note about Unicode values above 255 in "key".
	//
	var i;
	for (i = 0; i < 256; i++) rc4s[i] = i;
	var j = 0;
	for (i = 0; i < 256; i++) {
		j = (j + rc4s[i] + key.charCodeAt(i % key.length)) % 256;
		var t = rc4s[i]; rc4s[i] = rc4s[j]; rc4s[j] = t;
	}
	rc4i = 0;
	rc4j = 0;
}

function cryptRC4(str) {
	// Encrypt or decrypt str using previously set RC4 key
	//
	// See earlier note about Unicode values above 255 in "str".
	//
	// Note that the result is Unicode, and most likely is not valid UTF-8
	//
	var res = "";
	for (var c = 0; c < str.length; c++) {
		rc4i = (rc4i + 1) % 256;
		rc4j = (rc4j + rc4s[rc4i]) % 256;
		var t = rc4s[rc4i]; rc4s[rc4i] = rc4s[rc4j]; rc4s[rc4j] = t;
		var k = rc4s[(rc4s[rc4i] + rc4s[rc4j]) % 256];
		res += String.fromCharCode(str.charCodeAt(c) ^ k);
	}
	return res;
}

function keyRC4withIV(iv, pwd) {
	// See Fluhrer, Mantin & Shamir "Weaknesses in the key scheduling
	// algorithm of RC4" (Proc. SAC 2001), and Mironov "(Not So) Random
	// Shuffles of RC4" (Proc. CRYPTO'02).
	// To compensate for those weaknesses, we use MD5 to intermingle the
	// password and IV, and we discard the first 16*256 output bytes.
	//
	// See earlier note about Unicode values above 255 in "key".
	//
	keyRC4(md5Hex("" + iv + "." + pwd));
	for (var c = 0; c < 16*256; c++) {
		// discard one byte of RC4 output
		rc4i = (rc4i + 1) % 256;
		rc4j = (rc4j + rc4s[rc4i]) % 256;
		var t = rc4s[rc4i]; rc4s[rc4i] = rc4s[rc4j]; rc4s[rc4j] = t;
	}
}
