// =====================================================================================================================
// Description:	Simple ARCFOUR encryption/decryption tool
//		using Secure Hash Algorithm 1 (SHA1) for checking correctness of decrypted result.
//
// Copyright (c) 2005 Henk Reints, http://henk-reints.nl
//
// Version:	26-Oct-2005c
//
// References:
//	- FIPS PUB 180-2: Secure Hash Standard (SHS)
//		http://csrc.nist.gov/publications/fips/fips180-2/fips180-2withchangenotice.pdf
//
//	- RFC3174: US Secure Hash Algorithm 1 (SHA1)
//		http://www.rfc-editor.org/rfc/rfc3174.txt
//
//	- ARCFOUR: A Stream Cipher Encryption Algorithm "Arcfour"
//		http://www.mozilla.org/projects/security/pki/nss/draft-kaukonen-cipher-arcfour-03.txt
//
// Example:	see http://henk-reints.nl/jscript/htm/hrcryptodemo.htm
//
// Multiple passphrases:
//	it is possible to use multiple passphrases as follows:
//		cypher = HRcrypto.encrypt(txt,key1,key2,...)
//	where each key argument can be either a passphrase or an Array of passphrases. for decryption the same
//	passphrases must be given IN THE SAME ORDER (decrypt will use them from last to first, whereas encrypt uses
//	them from first to last). multiple passphrases can be used for stronger encryption or for securing data in
//	such a way that always two (or more) persons need to be involved for decryption (which is a means to prevent
//	single persons to perform malicious operations on secure data).
//	KEEP IN MIND that arcfour is its own inverse, so when using two identical passphrases there will be no
//	encryption at all! THIS library does not check for that condition.
//	A possible solution for that problem is to prefix every key with its own sequence number before passing them
//	to encrypt/decrypt, thus forcing the keys to be different.
//
// Notes:
// - Arcfour is considered a quite good encryption algorithm, as long as a strong passphrase is used.
// - SHA1 is used to enable decryption checking by encoding both the text and its SHA1 and on decryption both the text
//   and this SHA1 are decoded and then a recomputed SHA1 of the decoded text is compared to the decoded SHA1.
// - The main functions to be used here are "encrypt" and "decrypt", the others are intended for internal use only.
// - Many function given here are more or less copied from my hr$binstring.js library.
// =====================================================================================================================

function hrcryptoObject()	// constructor for the hrcrypto object
{
// ---------------------------------------------------------------------------------------------------------------------
// next code initialises a string containing the Unicode translation of byte values in the range 128 - 159, which is
// implicitly translated by Windows during file i/o. If, in the future, the Unicode table gets modified then the table
// below needs an update as well (producing a compatibility problem for those who don't keep their PC up to date...)
// ---------------------------------------------------------------------------------------------------------------------
	for (var ByteTable = "", i = 0; i < 128; ByteTable += String.fromCharCode(i++) );
	ByteTable+=unescape("%u20AC%81%u201A%u0192%u201E%u2026%u2020%u2021%u02C6%u2030%u0160%u2039%u0152%8D%u017D%8F%90"
			+"%u2018%u2019%u201C%u201D%u2022%u2013%u2014%u02DC%u2122%u0161%u203A%u0153%9D%u017E%u0178")
	for (var i = ByteTable.length; i < 256; ByteTable += String.fromCharCode(i++) );
// ---------------------------------------------------------------------------------------------------------------------
// next is the list of characters used for Base64 encoding:
// ---------------------------------------------------------------------------------------------------------------------
	Base64chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" 
// ---------------------------------------------------------------------------------------------------------------------
// conversion methods needed internally:
// ---------------------------------------------------------------------------------------------------------------------
	function byteToChar(b)
	{	return ByteTable.charAt(b&255)
	}
	function charToByte(c)
	{	var i = ByteTable.indexOf(c.charAt(0))
		return (i < 0 ? c.charCodeAt(0)&255 : i)
	}
	function byteToHex(b)
	{	return ("0"+b.toString(16)).slice(-2)
	}
	function charToHex(c)
	{	return byteToHex(charToByte(c))
	}
// ---------------------------------------------------------------------------------------------------------------------
// conversion methods needed internally for SHA1 computation:
// ---------------------------------------------------------------------------------------------------------------------
	function bigEndianWordToString(val)
	{	var v = (val < 0 ? val + 0x100000000 : val)
		for (var r = "", i = 0; i < 4; r = byteToChar(v&255) + r, v >>>= 8, i++);
		return r
	}
	function stringToBigEndianWords(txt)
	{	for (var r = [], i = 0, k = 0; i < txt.length; )
		{	for (var v = 0, j = 0; j < 4; v *= 256, v += charToByte(txt.charAt(i++)), j++);
			r[k++] = v
		}
		return r
	}
// ---------------------------------------------------------------------------------------------------------------------
// next section is the core of this package: ARCFOUR encrypton/decryption and the Secure Hash Algorithm 1.
// the quintessence of arcfour is:
// loop #1:	init an "internal state" table with numbers 0 through 255
// loop #2:	shuffle this internal state using the key
// loop #3:	keep on shuffling the state, producing a series of pseudorandom numbers,
//		then XOR each pseudorandom number to the next character of the text to be encrypted.
// since XOR is its own reverse the same is done for decryption.
// the "hrcfour" method is a variant of the "official" arcfour. The latter uses only the first 256 characters of the
// key, hrcfour uses the entire key whatever its length is, and it performs a double initial randomisation of the
// internal state.
// About long keys: I think it is better to iterate arcfour with multiple short keys instead of running it once with a
// single very long key, since with a long key each original character is masked once only and with multiple keys it is
// masked several times, so the degree of obfuscation can expected to be better. But keep in mind that arcfour is its
// own reverse, so using the same key twice results in no encryption at all!
// ---------------------------------------------------------------------------------------------------------------------
	function arcfour(txt,key)
	{	var r = "", Lx = txt.length, Lk = key.length, c,i,j,k,m,n, s = [], t, M = 255, C = M+1
		for (i = 0; i < C; s[i] = i++);
		for (j = 0, m = 0; m < C; m++)
		{	k=key.charCodeAt(m%Lk), i=m&M, j+=s[i]+k,j&=M, t=s[i],s[i]=s[j],s[j]=t
		}
		for (i = 0 ,j = 0, m = 0; m < Lx; m++)
		{	i++,i&=M, j+=s[i],j&=M, t=s[i],s[i]=s[j],s[j]=t, k=s[i]+s[j],n=s[k&M]
			r += byteToChar(charToByte(txt.charAt(m))^n)
		}
		return r
	}
	function hrcfour(txt,key)
	{	var r = "", Lx = txt.length, Lk = key.length, c,i,j,k,m,n, s = [], t, M = 255, C = M+1
		for (i = 0; i < C; s[i] = i++);
		for (n = 0; n < 2; n++) for (j = 0, m = 0; m < C || m < Lk; m++)
		{	k=key.charCodeAt(m%Lk), i=m&M, j+=s[i]+k,j&=M, t=s[i],s[i]=s[j],s[j]=t
		}
		for (i = 0 ,j = 0, m = 0; m < Lx; m++)
		{	i++,i&=M, j+=s[i],j&=M, t=s[i],s[i]=s[j],s[j]=t, k=s[i]+s[j],n=s[k&M]
			r += byteToChar(charToByte(txt.charAt(m))^n)
		}
		return r
	}
	function sha1(txt)
	{	var m = 0x100000000, L=txt.length, L1=L%64, L0=L-L1, L2=64-L1, len=L*8, llo=len%m, lhi=(len-llo)/m
		var p = (byteToChar(128)+(new Array(64)).join(byteToChar(0))).slice(0,1+((959-(len&511))&511)>>>3)
		p += bigEndianWordToString(lhi) + bigEndianWordToString(llo)
		var N = (L+p.length)/4, M=0xffffffff, t,X,A,B,C,D,E, k,k4,W,w
		var H0 = 0x67452301, H1 = 0xefcdab89, H2 = 0x98badcfe, H3 = 0x10325476, H4 = 0xc3d2e1f0
		var K0 = 0x5a827999, K1 = 0x6ed9eba1, K2 = 0x8f1bbcdc, K3 = 0xca62c1d6
		for (k = 0; k < N; k += 16)
		{ k4 = k<<2
		  W=stringToBigEndianWords(k4<L0? txt.substr(k4,64) : k4<L? txt.slice(k4)+p.slice(0,L2) : p.slice(k4-L))
		  for (t = 16; t < 80; w = W[t-3]^W[t-8]^W[t-14]^W[t-16], W[t++] = (w<<1)|(w>>>31) );
		  A=H0, B=H1, C=H2, D=H3, E=H4, t=0
		  for(;t<20;){X=(((A<<5)|(A>>>27))+((B&C)^((~B)&D))   +E+W[t++]+K0)&M,E=D,D=C,C=(B<<30)|(B>>>2),B=A,A=X}
		  for(;t<40;){X=(((A<<5)|(A>>>27))+(B^C^D)            +E+W[t++]+K1)&M,E=D,D=C,C=(B<<30)|(B>>>2),B=A,A=X}
		  for(;t<60;){X=(((A<<5)|(A>>>27))+((B&C)^(B&D)^(C&D))+E+W[t++]+K2)&M,E=D,D=C,C=(B<<30)|(B>>>2),B=A,A=X}
		  for(;t<80;){X=(((A<<5)|(A>>>27))+(B^C^D)            +E+W[t++]+K3)&M,E=D,D=C,C=(B<<30)|(B>>>2),B=A,A=X}
		  H0 = (H0 + A) & M; H1 = (H1 + B) & M; H2 = (H2 + C) & M; H3 = (H3 + D) & M; H4 = (H4 + E) & M;
		}
		return	bigEndianWordToString(H0)+bigEndianWordToString(H1)
		+	bigEndianWordToString(H2)+bigEndianWordToString(H3)+bigEndianWordToString(H4)
	}
// ---------------------------------------------------------------------------------------------------------------------
// main methods for external use.
// decrypt returns the correct text if sha1 is ok, else it returns the raw result of the incorrect decryption.
// KEEP IN MIND that arcfour is its own inverse, so using two identical passphrases results in no encryption at all!
// A possible solution for that problem is to prefix every key with its own sequence number before passing them
// to encrypt/decrypt, thus forcing the keys to be different.
// encypher and decypher are more or less the same, but they use hrcfour instead of arcfour
// and the SHA1 is encoded in in base-64 instead of hex.
// ---------------------------------------------------------------------------------------------------------------------
	function encrypt(txt,key1,key2,etc)
	{	var sha = stringToHex(sha1(txt))
		var cyp = [sha,txt].join("/")
		for (var i = 1; i < arguments.length; )
		{	var a = arguments[i++], k = (a.constructor != Array ? [a] : a)
			for (var j = 0; j < k.length; cyp = arcfour(cyp,""+k[j++]) );
		}
		return cyp
	}
	function decrypt(cyp,key1,key2,etc)
	{	var cmb = cyp
		for (var i = arguments.length; i > 1; )
		{	var a = arguments[--i]
			var k = (a.constructor != Array ? [a] : a)
			for (var j = k.length; j > 0; cmb = arcfour(cmb,""+k[--j]) );
		}
		var i = cmb.indexOf("/")
		if (i < 0) return [false,cmb]
		var sha = cmb.slice(0,i++)
		var txt = cmb.slice(i)
		return (stringToHex(sha1(txt)) == sha ? [true,txt] : [false,cmb])
	}
	function encypher(txt,key1,key2,etc)
	{	var sha = stringToBase64(sha1(txt))	// always ends with "=" (which is used in decypher)
		var cyp = sha+txt
		for (var i = 1; i < arguments.length; )
		{	var a = arguments[i++], k = (a.constructor != Array ? [a] : a)
			for (var j = 0; j < k.length; cyp = hrcfour(cyp,""+k[j++]) );
		}
		return cyp
	}
	function decypher(cyp,key1,key2,etc)
	{	var cmb = cyp
		for (var i = arguments.length; i > 1; )
		{	var a = arguments[--i]
			var k = (a.constructor != Array ? [a] : a)
			for (var j = k.length; j > 0; cmb = hrcfour(cmb,""+k[--j]) );
		}
		var i = cmb.indexOf("=")	// the original b64-sha1 always ends with "="
		if (i < 0) return [false,cmb]	// not found? then it's definitely wrong
		var sha = cmb.slice(0,++i)	// split just after the "="
		var txt = cmb.slice(i)
		return (stringToBase64(sha1(txt)) == sha ? [true,txt] : [false,cmb])
	}
// ---------------------------------------------------------------------------------------------------------------------
// extra methods for input/output conversion (stringToHex and stringToBase64 are also used internally):
// ---------------------------------------------------------------------------------------------------------------------
	function stringToHex(txt,sep)
	{	for (var i=0,x="",s=(arguments.length>1?s:""); i<txt.length; x+=(i>0?s:"")+charToHex(txt.charAt(i++)) );
		return x
	}
	function stringFromHex(txt)
	{	var x = txt.replace(/^\s+/,"").replace(/\s+$/,"");	if (x == "") return x
		var X = x.replace(/[^0-9A-Fa-f,]+/g,",").split(",")
		for (var x = "", i = 0; i < X.length; )
		{	var Xi = X[i++]; if (Xi == "") Xi += "0";
			for (var j = 0; j < Xi.length; x += byteToChar(parseInt(Xi.substring(j++,++j),16)) );
		}
		return x
	}
	function stringToBase64(txt)
	{	var L = txt.length, M = L - L % 3, r = "", b0,b1,b2
		for (var k = 0, i = 0, t = 0; i < M; )
		{	b0 = charToByte(txt.charAt(i++))
			b1 = charToByte(txt.charAt(i++))
			b2 = charToByte(txt.charAt(i++))
			r += Base64chars.charAt(   (b0&0xff) >>>2                       )
			r += Base64chars.charAt( ( (b0&0x03) << 4) | ( (b1&0xf0) >>> 4) )
			r += Base64chars.charAt( ( (b1&0x0f) << 2) | ( (b2&0xc0) >>> 6) )
			r += Base64chars.charAt(    b2&0x3f                             )
		}
		if (M < L)
		{	b0 = charToByte(txt.charAt(M))
			b1 = (++M < L ? charToByte(txt.charAt(M)) : 0)
			r += Base64chars.charAt(        (b0&0xff) >>>2                       )
			r += Base64chars.charAt(      ( (b0&0x03) << 4) | ( (b1&0xf0) >>> 4) )
			r += (M<L ? Base64chars.charAt( (b1&0x0f) << 2) : "=")
			r += "="
		}
		return r
	}
	function stringFromBase64(txt)
	{	var Q = [], R = ""
		for (var k = 0, i = 0; i < txt.length; i++)
		{	var c = Base64chars.indexOf(txt.charAt(i))
			if (c >= 0) Q[k++] = c
			if (Q.length == 4)
			{	R += ByteTable.charAt( (  Q[0]       << 2) | (Q[1] >>> 4) )
				R += ByteTable.charAt( ( (Q[1]&0x0f) << 4) | (Q[2] >>> 2) )
				R += ByteTable.charAt( ( (Q[2]&0x03) << 6) |  Q[3]        )
				Q = [], k = 0
			}
		}
		if (Q.length > 1) R += ByteTable.charAt ( (  Q[0]       << 2) | (Q[1] >>> 4) )
		if (Q.length > 2) R += ByteTable.charAt ( ( (Q[1]&0x0f) << 4) | (Q[2] >>> 2) )
		return R
	}
	function wrapString(txt,wid)
	{	for (var x="",y=txt,w=(arguments.length>1?wid:76),s=""; y!=""; x+=s+y.slice(0,w), y=y.slice(w), s="\n");
		return x
	}
// ---------------------------------------------------------------------------------------------------------------------
// expose the methods:
// ---------------------------------------------------------------------------------------------------------------------
// internals:
	this.byteToChar			= byteToChar
	this.charToByte			= charToByte
	this.byteToHex			= byteToHex
	this.charToHex			= charToHex
// internals for SHA1:
	this.bigEndianWordToString	= bigEndianWordToString
	this.stringToBigEndianWords	= stringToBigEndianWords
// core:
	this.arcfour			= arcfour
	this.hrcfour			= hrcfour
	this.sha1			= sha1
// main:
	this.encrypt			= encrypt
	this.decrypt			= decrypt
	this.encypher			= encypher
	this.decypher			= decypher
// extra:
	this.stringToHex		= stringToHex
	this.stringFromHex		= stringFromHex
	this.stringToBase64		= stringToBase64
	this.stringFromBase64		= stringFromBase64
	this.wrapString			= wrapString

} // end of function hrcryptoObject()

// ---------------------------------------------------------------------------------------------------------------------
// create an object instance:
// ---------------------------------------------------------------------------------------------------------------------
	HRcrypto = new hrcryptoObject()

// =====================================================================================================================
// [EOF] hr$crypto.js - Copyright (c) 2005 Henk Reints, http://henk-reints.nl

