/***

The top code is Copyright (C) 2005

Michiel van Everdingen
Meerstraat 36
1411 BH Naarden
The Netherlands
MAvanEverdingen@zonnet.nl

and is subject to the GPL

http://www.gnu.org/copyleft/gpl.html

Modifications are marked with KMD

Modified in July 2007 by Kent M. Davidson
kent -at- marketruler -dot- com
for use with http://pw.ex.to/

Original code from Michiel van Everdingen can be found and downloaded here:

http://pw.ex.to/code.php

Changes are mostly those of exclusion, removal of extraneous code.

****/

var sData;
var bData;
var key;
var i;
var j;
var tot;
var lenInd = 1;

// Math

var wMax = 0xFFFFFFFF;
function rotb(b,n){ return ( b<<n | b>>>( 8-n) ) & 0xFF; }
function rotw(w,n){ return ( w<<n | w>>>(32-n) ) & 0xFFFFFFFF; }
function getW(a,i){ return a[i]|a[i+1]<<8|a[i+2]<<16|a[i+3]<<24; }
function setW(a,i,w){
	a.splice(i,4,w&0xFF,(w>>>8)&0xFF,(w>>>16)&0xFF,(w>>>24)&0xFF);
}
function setWInv(a,i,w){ a.splice(i,4,(w>>>24)&0xFF,(w>>>16)&0xFF,(w>>>8)&0xFF,w&0xFF); }
function getB(x,n){ return (x>>>(n*8))&0xFF; }

function getNrBits(i){ var n=0; while (i>0){ n++; i>>>=1; } return n; }
function getMask(n){ return (1<<n)-1; }

var bMax=0xFFFF;
var bMaxBits=getNrBits(bMax);

function bGetNrBits(a){ return (a.length-1)*bMaxBits+getNrBits(a[a.length-1]); }

function bPlusIs( a, b, shift ){
  var i;
  if (!shift) shift=0;
  var n = Math.max(a.length, b.length+shift);
  var c = 0;
  for ( i=0; i<n; i++ ){
    c += (a[i]?a[i]:0) + (b[i-shift]?b[i-shift]:0);
    a[i] = c&bMax;
    c >>>= bMaxBits;
  }
  if (c) a[i]=c;
  return a;
}

function bMinIs(a,b){
  var n = Math.max(a.length, b.length);
  var c = 0;
  var i;
  for ( i=0; i<n; i++ ){
    c += (a[i]?a[i]:0) - (b[i]?b[i]:0);
    a[i] = c&bMax;
    c >>= bMaxBits;
  }
  if (c) a[i]=c;
  while (a[a.length-1]==0&&a.length>1) a.pop();
  return a;
}

function bShlIs(a,n){
  if (a==0) return;
  var c = 0;
  var i;
  while (n>=bMaxBits){ a.splice(0,0,0); n-=bMaxBits; }
  for ( i=0; i<a.length; i++ ){
    var t = a[i];
    a[i] = ( t<<n | c ) & bMax;
    c = t>>>(bMaxBits-n);
  }
  if (c) a[i]=c;
  return a;
}

function bShrIs(a,n){
  var c = 0;
  while (n>=bMaxBits){ a.splice(0,1); n-=bMaxBits; }
  for ( var i=a.length-1; i>=0; i-- ){
    var t = a[i];
    a[i] = t>>>n | c;
    c = (t<<(bMaxBits-n)) & bMax;
  }
  while (a[a.length-1]==0&&a.length>1) a.pop();
  return a;
}

function bCmp(a,b){
  var al=a.length;
  var bl=b.length;
  if ( al>bl ) return 1;
  if ( al<bl ) return -1;
  for ( var i=al-1; i>=0; i-- )
    if (a[i]>b[i]) return 1;
    else if (a[i]<b[i]) return -1;
  return 0;
}

function sbMul(i,a){
  var r=[0];
  var c = 0;
  var n = 0;
  while ( n<a.length ){
    c+=i*a[n];
    r[n++]=c&bMax;
    c >>>= bMaxBits;
  }
  if (c) r[n]=c;
  return r;
}

function bMul(a,b){
  var r=[0];
  for ( var n=0; n<b.length; n++ ) bPlusIs( r, sbMul( b[n], a ), n );
  return r;
}

function bDiv(a,b){
  var q=[0];
  var r=a.slice(0);
  var n=1+bGetNrBits(a)-bGetNrBits(b);
  if ( n<1 ) return [[0],a];
  bShlIs(b,n);
  for (var i=0;i<n;i++){
    bShrIs(b,1);
    bShlIs(q,1);
    if (bCmp(r,b)>=0){
      q[0]|=1;
      bMinIs(r,b);
    }
  }
  return [q,r];
}


var bSeeds = [(new Date()).getTime()&bMax, 1];
var bSeedsRIdx=0;
var bSeedsWIdx=1;

function bSetSeed(){
  bSeeds[bSeedsWIdx] = (bSeeds[bSeedsWIdx]<<8) | ((new Date()).getTime()&0xFF);
  if (bSeeds[bSeedsWIdx]>bMax){
    bSeeds[bSeedsWIdx] &= bMax;
    bSeeds[++bSeedsWIdx]=1;
  }
}

function bRnd(a, bits, ones){
  var n = Math.ceil(bits/bMaxBits);
  var hMask = bMax>>>(n*bMaxBits-bits);
  var i;

  for (i=0; i<n; i++){
    a[i]=(Math.floor((bMax+1)*Math.random())+bSeeds[bSeedsRIdx])&bMax;
    bSeedsRIdx = (bSeedsRIdx+1)%bSeeds.length;
  }
  a.length=i; a[--i] &= hMask;
  hMask = (hMask+1)>>>1;
  while (ones && ones-->0){ a[i]|=hMask; hMask>>>=1; if (hMask==0){ hMask=(bMax+1)>>>1; i--; } }
  while (a[a.length-1]==0&&a.length>1) a.pop();
}

function bFromBytes(a){
  var k=a.length;
  var r=[0];
  while (k>0){ bShlIs(r,8); r[0]|=a[--k]; }
  return r;
}

function bToBytes(a){
  var b=a.slice(0);
  var r=[];
  while (b.length>1||b[0]!=0){ r[r.length]=b[0]&0xFF; bShrIs(b,8); }
  return r;
}

function bFromJBI(jbi){
  var s = jbi.toString(8);
  var r = [0];
  for (var i=0; i<s.length(); i++){ bShlIs(r,3); r[0]+=s.charAt(i)-48; }
  return r;
}

function bToJBI(b){
  var s = '';
  do{
    s = (b[0]&7)+s;
    bShrIs( b, 3 );
  }while (b.length>1||b[0]>0);
  return new java.math.BigInteger(s,8);
}


// UTF-8

var utf8sets = [0x800,0x10000,0x110000];

function utf8Encrypt(){
  if (i==0) { prgr='UTF-8'; bData=[]; tot=sData.length; j=0; }
  // KMD: Removed limit on encoding here
  // var z = Math.min(i+100,tot);
  var z = tot;
  while (i<z) {
    var c = sData.charCodeAt(i++);
    if (c<0x80){ bData[j++]=c; continue; }
    var k=0; while(k<utf8sets.length && c>=utf8sets[k]) k++;
    if (k>=utf8sets.length) throw( "UTF-8: "+unExpChar(c) );
    for (var n=j+k+1;n>j;n--){ bData[n]=0x80|(c&0x3F); c>>>=6; }
    bData[j]=c+((0xFF<<(6-k))&0xFF);
    j += k+2;
  }
}

function utf8Decrypt()
{
	if (i==0){ prgr='UTF-8'; sData=""; tot=bData.length; }
	// KMD: Removed limit on encoding here
	// var z = Math.min(i+100,tot);
	// KMD: Optimized this
	var c,k,n,d,z = tot;
	while (i<z)
	{
		c = bData[i++];
		    var e = '0x'+c.toString(16);
		k=0; while(c&0x80){ c=(c<<1)&0xFF; k++; }
		c >>= k;
		if (k==1||k>4) throw('UTF-8: invalid first byte '+e+'.');
		for (n=1;n<k;n++){
			d = bData[i++];
			     e+=',0x'+d.toString(16);
			if (d<0x80||d>0xBF) break;
			c=(c<<6)+(d&0x3F);
		}
//		if ( (k==2&&c<0x80) || (k>2&&c<utf8sets[k-3]) ) throw("UTF-8: invalid sequence "+e+'.');
		sData+=String.fromCharCode(c);
	}
}

// Base64

function b64Encrypt(){
  if (i==0) { prgr='Base64'; sData=""; tot=bData.length; }
  // KMD: Removed limit on encoding here
  // var z=Math.min(i+100,tot);
  var z = tot;
  while (i<z){
    var x = [ bData[i]>>2, (bData[i]&3)<<4, 64, 64 ];
    if (++i<bData.length){x[1]+=(bData[i]&240)>>4;x[2]=(bData[i]&15)<<2;}
    if (++i<bData.length){x[2]+=(bData[i]&192)>>6;x[3]=bData[i]&63;}
    for (var j = 0; j < 4; j++) {
      var y=x[j];
      sData += String.fromCharCode(y<26?65+y:y<52?71+y:y<62?y-4:y<63?43:y<64?47:61);
    }
    i++;
  }
}

function unExpChar(c){
  return "unexpected character '"+String.fromCharCode(c)+"' (code 0x"+c.toString(16)+").";
}

function b64Decrypt(){
  var x = new Array(4);
  // KMD: Removed limit on encoding here
  // var z = i+100;
  var z = sData.length;
  if (i==0){ prgr='Base64'; j=0; tot=sData.length; bData=[]; }
  while(i<z){
    for (var k=0;k<4;k++){
      var c=0; while (c<33&&i<tot){ c=sData.charCodeAt(i++); }
      if (c<33){
        if (k!=0) throw( "Base64: unexpected #chars." );
        return;
      }
      x[k] = c==43?62:c==47?63:c==61?64:c>47&&c<58?c+4:c>64&&c<91?c-65:c>96&&c<123?c-71:-1;
      if (x[k]<0||(x[k]==64&&k<2)) throw( "Base64: "+unExpChar(c)
        +"\nAllowed characters:\n['A'-'Z'], ['a'-'z'], ['0'-'9'], '+', '-' and '='."  );
    }
    bData[j++] = (x[0]<<2)+(x[1]>>4);
    if (x[2]<64) bData[j++] = ((x[1]&15)<<4)+(x[2]>>2);
    if (x[3]<64) bData[j++] = ((x[2]&3)<<6)+x[3];
  }
}

function setKey(p, b64){
  var tmp = bData;
  if (b64){
    sData=p.replace(/\s/g,'');
    if (sData.length%4==1) sData+='A';
    while (sData.length%4) sData+='=';
  }
  else sData=p;
  i=tot=0;
  do{ if (b64) b64Decrypt(); else utf8Encrypt(); } while (i<tot);
  key = bData;
  bData = tmp;
}

function bytesToB64(b)
{
	bData=b;
	i=tot=0;
	do{ b64Encrypt(); } while (i<tot);
	return sData.replace(/(.{40})/g,'$1\n');
}

// AES
var aesNk;
var aesNr;

var aesPows;
var aesLogs;
var aesSBox;
var aesSBoxInv;
var aesRco;
var aesFtable;
var aesRtable;
var aesFi;
var aesRi;
var aesFkey;
var aesRkey;

function aesMult(x, y){ return (x&&y) ? aesPows[(aesLogs[x]+aesLogs[y])%255]:0; }

function aesPackBlock() {
  return [ getW(bData,i), getW(bData,i+4), getW(bData,i+8), getW(bData,i+12) ];
}

function aesUnpackBlock(packed){
  if (typeof packed == "undefined") { tot = i; return; }
  for ( var j=0; j<4; j++,i+=4) setW( bData, i, packed[j] );
}

function aesXTime(p){
  p <<= 1;
  return p&0x100 ? p^0x11B : p;
}

function aesSubByte(w){
  return aesSBox[getB(w,0)] | aesSBox[getB(w,1)]<<8 | aesSBox[getB(w,2)]<<16 | aesSBox[getB(w,3)]<<24;
}

function aesProduct(w1,w2){
  return aesMult(getB(w1,0),getB(w2,0)) ^ aesMult(getB(w1,1),getB(w2,1))
       ^ aesMult(getB(w1,2),getB(w2,2)) ^ aesMult(getB(w1,3),getB(w2,3));
}

function aesInvMixCol(x){
  return aesProduct(0x090d0b0e,x)     | aesProduct(0x0d0b0e09,x)<<8 |
         aesProduct(0x0b0e090d,x)<<16 | aesProduct(0x0e090d0b,x)<<24;
}

function aesByteSub(x){
  var y=aesPows[255-aesLogs[x]];
  x=y;  x=rotb(x,1);
  y^=x; x=rotb(x,1);
  y^=x; x=rotb(x,1);
  y^=x; x=rotb(x,1);
  return x^y^0x63;
}

var aesStateCache = null;

function aesState()
{
	this.aesPows 	= aesPows;
	this.aesLogs 	= aesLogs;
	this.aesSBox 	= aesSBox;
	this.aesSBoxInv	= aesSBoxInv;
	this.aesFtable	= aesFtable;
	this.aesRtable	= aesRtable;
	this.aesRco		= aesRco;
}
aesState.prototype = {
	setup: function() {
		aesPows 		= this.aesPows;
		aesLogs 		= this.aesLogs;
		aesSBox 		= this.aesSBox;
		aesSBoxInv 		= this.aesSBoxInv;
		aesFtable 		= this.aesFtable;
		aesRtable 		= this.aesRtable;
		aesRco 			= this.aesRco;
	}
};
function aesGenTables()
{
	if (aesStateCache != null) {
		aesStateCache.setup();
		return;
	}
	var i,y;
	aesPows = [ 1,3 ];
	aesLogs = [ 0,0,null,1 ];
	aesSBox = new Array(256);
	aesSBoxInv = new Array(256);
	aesFtable = new Array(256);
	aesRtable = new Array(256);
	aesRco = new Array(30);

	for ( i=2; i<256; i++){
	aesPows[i]=aesPows[i-1]^aesXTime( aesPows[i-1] );
	aesLogs[aesPows[i]]=i;
	}

	aesSBox[0]=0x63;
	aesSBoxInv[0x63]=0;
	for ( i=1; i<256; i++){
	y=aesByteSub(i);
	aesSBox[i]=y; aesSBoxInv[y]=i;
	}

	for (i=0,y=1; i<30; i++){ aesRco[i]=y; y=aesXTime(y); }

	for ( i=0; i<256; i++){
	y = aesSBox[i];
	aesFtable[i] = aesXTime(y) | y<<8 | y<<16 | (y^aesXTime(y))<<24;
	y = aesSBoxInv[i];
	aesRtable[i]= aesMult(14,y) | aesMult(9,y)<<8 |
	              aesMult(13,y)<<16 | aesMult(11,y)<<24;
	}
	aesStateCache = new aesState();
}

var bDataSave;

function aesInit(){
  key=key.slice(0,32);
  var i,k,m;
  var j = 0;
  var l = key.length;
  while ( l!=16 && l!=24 && l!=32 ) key[l++]=key[j++];

  aesGenTables();

  aesNk = key.length >>> 2;
  aesNr = 6 + aesNk;

  var N=4*(aesNr+1);

  aesFi = new Array(12);
  aesRi = new Array(12);
  aesFkey = new Array(N);
  aesRkey = new Array(N);

  for (m=j=0;j<4;j++,m+=3){
    aesFi[m]=(j+1)%4;
    aesFi[m+1]=(j+2)%4;
    aesFi[m+2]=(j+3)%4;
    aesRi[m]=(4+j-1)%4;
    aesRi[m+1]=(4+j-2)%4;
    aesRi[m+2]=(4+j-3)%4;
  }

  for (i=j=0;i<aesNk;i++,j+=4) aesFkey[i]=getW(key,j);

  for (k=0,j=aesNk;j<N;j+=aesNk,k++){
    aesFkey[j]=aesFkey[j-aesNk]^aesSubByte(rotw(aesFkey[j-1], 24))^aesRco[k];
    if (aesNk<=6)
      for (i=1;i<aesNk && (i+j)<N;i++) aesFkey[i+j]=aesFkey[i+j-aesNk]^aesFkey[i+j-1];
    else{
      for (i=1;i<4 &&(i+j)<N;i++) aesFkey[i+j]=aesFkey[i+j-aesNk]^aesFkey[i+j-1];
      if ((j+4)<N) aesFkey[j+4]=aesFkey[j+4-aesNk]^aesSubByte(aesFkey[j+3]);
      for (i=5;i<aesNk && (i+j)<N;i++) aesFkey[i+j]=aesFkey[i+j-aesNk]^aesFkey[i+j-1];
    }
  }

  for (j=0;j<4;j++) aesRkey[j+N-4]=aesFkey[j];
  for (i=4;i<N-4;i+=4){
    k=N-4-i;
    for (j=0;j<4;j++) aesRkey[k+j]=aesInvMixCol(aesFkey[i+j]);
  }
  for (j=N-4;j<N;j++) aesRkey[j-N+4]=aesFkey[j];
 }

function aesClose(){
  aesPows=aesLogs=aesSBox=aesSBoxInv=aesRco=null;
  aesFtable=aesRtable=aesFi=aesRi=aesFkey=aesRkey=null;
}

function aesRounds( block, key, table, inc, box ){
	var tmp = new Array( 4 );
	var i,r;

	block[0]^=key[0];block[1]^=key[1];block[2]^=key[2];block[3]^=key[3];r=4;
	for (i = 1; i < aesNr; i++)
	{
		tmp[0] = key[r++]^table[block[0]&0xFF]^rotw(table[(block[inc[ 0]]>>>8)&0xFF], 8)^rotw(table[(block[inc[ 1]]>>>16)&0xFF], 16)^rotw(table[(block[inc[ 2]]>>>24)&0xFF], 24);
		tmp[1] = key[r++]^table[block[1]&0xFF]^rotw(table[(block[inc[ 3]]>>>8)&0xFF], 8)^rotw(table[(block[inc[ 4]]>>>16)&0xFF], 16)^rotw(table[(block[inc[ 5]]>>>24)&0xFF], 24);
		tmp[2] = key[r++]^table[block[2]&0xFF]^rotw(table[(block[inc[ 6]]>>>8)&0xFF], 8)^rotw(table[(block[inc[ 7]]>>>16)&0xFF], 16)^rotw(table[(block[inc[ 8]]>>>24)&0xFF], 24);
		tmp[3] = key[r++]^table[block[3]&0xFF]^rotw(table[(block[inc[ 9]]>>>8)&0xFF], 8)^rotw(table[(block[inc[10]]>>>16)&0xFF], 16)^rotw(table[(block[inc[11]]>>>24)&0xFF], 24);
		var t=block; block=tmp; tmp=t;
	}

	tmp[0]=key[r++]^box[block[0]&0xFF]^rotw(box[(block[inc[ 0]]>>> 8)&0xFF], 8)^rotw(box[(block[inc[ 1]]>>>16)&0xFF],16)^rotw(box[(block[inc[ 2]]>>>24)&0xFF],24);
	tmp[1]=key[r++]^box[block[1]&0xFF]^rotw(box[(block[inc[ 3]]>>> 8)&0xFF], 8)^rotw(box[(block[inc[ 4]]>>>16)&0xFF],16)^rotw(box[(block[inc[ 5]]>>>24)&0xFF],24);
	tmp[2]=key[r++]^box[block[2]&0xFF]^rotw(box[(block[inc[ 6]]>>> 8)&0xFF], 8)^rotw(box[(block[inc[ 7]]>>>16)&0xFF],16)^rotw(box[(block[inc[ 8]]>>>24)&0xFF],24);
	tmp[3]=key[r++]^box[block[3]&0xFF]^rotw(box[(block[inc[ 9]]>>> 8)&0xFF], 8)^rotw(box[(block[inc[10]]>>>16)&0xFF],16)^rotw(box[(block[inc[11]]>>>24)&0xFF],24);

	return tmp;
}

function aesEncrypt(){
  aesUnpackBlock( aesRounds(aesPackBlock(), aesFkey, aesFtable, aesFi, aesSBox ) );
}

function aesDecrypt(){
	var b = aesRounds(aesPackBlock(), aesRkey, aesRtable, aesRi, aesSBoxInv );
  aesUnpackBlock( b );
}

function insLen(len, bits){
  var n=(bits+7)>>>3;
  while (bits<n*8){ len=((len&0xFF)<<bits)|len; bits*=2; }
  while (n-->0) bData.unshift( (len>>>(n*8))&0xFF );
}

function getLen(bits){
  var n = (bits+7)>>>3;
  var r=0;
  for (var i=0; i<n; i++) r += bData.shift()<<(i*8);
  return r&getMask(bits);
}

function blcEncrypt(name, cbc, init, enc, close){
  if (tot==0){
    prgr = name;
    if (key.length<1) return;
    if (lenInd) insLen( bData.length%16, 4 );
    if (cbc) for (i=0; i<16; i++) bData.unshift( Math.floor(Math.random()*256) );
    while( bData.length%16!=0 ) bData.push(0);
    tot = bData.length;
    init();
  }else{
    if (cbc) for (j=i; j<i+16; j++) bData[j] ^= bData[j-16];
    enc();
  }
  if (i>=tot) close();
}

function blcDecrypt(name, cbc, init, dec, close){
  if (tot==0){
    prgr = name;
    if (key.length<1) return;
    if (cbc){ i=16; }
    tot = bData.length;
    if ( (tot%16) || tot<i ) throw name+': Incorrect length. tot=' + tot + ',i=' + i;
    init();
  }else{
    if (cbc) i=tot-i;
    dec();
    if (cbc){
      for (j=i-16; j<i; j++) bData[j] ^= bData[j-16];
      i = tot+32-i;
    }
  }
  if (i>=tot){
    close();
    if (cbc) bData.splice(0,16);
    if (lenInd){
      var ol = bData.length;
      var k = getLen(getNrBits(15));
      while((k+ol-bData.length)%16!=0) bData.pop();
    }
    else{
      while(bData[bData.length-1]==0) bData.pop();
    }
  }
}

function aescEncrypt(){ blcEncrypt('AESc',1,aesInit,aesEncrypt,aesClose); }
function aescDecrypt(){ blcDecrypt('AESc',1,aesInit,aesDecrypt,aesClose); }

function aeseEncrypt(){ blcDecrypt('AESe',0,aesInit,aesEncrypt,aesClose); }
function aeseDecrypt(){ blcDecrypt('AESe',0,aesInit,aesDecrypt,aesClose); }

/*
 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
 * Digest Algorithm, as defined in RFC 1321.
 * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for more info.
 */

/*
 * Configurable variables. You may need to tweak these to be compatible with
 * the server-side, but the defaults work in most cases.
 */
var hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */
var b64pad  = ""; /* base-64 pad character. "=" for strict RFC compliance   */
var chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */

/*
 * These are the functions you'll usually want to call
 * They take string arguments and return either hex or base-64 encoded strings
 */
function hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}
function b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}
function str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}
function hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }
function b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }
function str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }

/*
 * Perform a simple self-test to see if the VM is working
 */
function md5_vm_test()
{
  return hex_md5("abc") == "900150983cd24fb0d6963f7d28e17f72";
}

/*
 * Calculate the MD5 of an array of little-endian words, and a bit length
 */
function core_md5(x, len)
{
  /* append padding */
  x[len >> 5] |= 0x80 << ((len) % 32);
  x[(((len + 64) >>> 9) << 4) + 14] = len;

  var a =  1732584193;
  var b = -271733879;
  var c = -1732584194;
  var d =  271733878;

  for(var i = 0; i < x.length; i += 16)
  {
    var olda = a;
    var oldb = b;
    var oldc = c;
    var oldd = d;

    a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
    d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
    c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);
    b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
    a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
    d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);
    c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
    b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
    a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);
    d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
    c = md5_ff(c, d, a, b, x[i+10], 17, -42063);
    b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
    a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);
    d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);
    c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
    b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);

    a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
    d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
    c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);
    b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
    a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
    d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);
    c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);
    b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
    a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);
    d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
    c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
    b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);
    a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
    d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
    c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);
    b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);

    a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
    d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
    c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);
    b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);
    a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
    d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);
    c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
    b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
    a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);
    d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
    c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
    b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);
    a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
    d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);
    c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);
    b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);

    a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
    d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);
    c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
    b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
    a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);
    d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
    c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);
    b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
    a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);
    d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);
    c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
    b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);
    a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
    d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
    c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);
    b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);

    a = safe_add(a, olda);
    b = safe_add(b, oldb);
    c = safe_add(c, oldc);
    d = safe_add(d, oldd);
  }
  return Array(a, b, c, d);

}

/*
 * These functions implement the four basic operations the algorithm uses.
 */
function md5_cmn(q, a, b, x, s, t)
{
  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);
}
function md5_ff(a, b, c, d, x, s, t)
{
  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5_gg(a, b, c, d, x, s, t)
{
  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5_hh(a, b, c, d, x, s, t)
{
  return md5_cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5_ii(a, b, c, d, x, s, t)
{
  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
}

/*
 * Calculate the HMAC-MD5, of a key and some data
 */
function core_hmac_md5(key, data)
{
  var bkey = str2binl(key);
  if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);

  var ipad = Array(16), opad = Array(16);
  for(var i = 0; i < 16; i++)
  {
    ipad[i] = bkey[i] ^ 0x36363636;
    opad[i] = bkey[i] ^ 0x5C5C5C5C;
  }

  var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);
  return core_md5(opad.concat(hash), 512 + 128);
}

/*
 * Add integers, wrapping at 2^32. This uses 16-bit operations internally
 * to work around bugs in some JS interpreters.
 */
function safe_add(x, y)
{
  var lsw = (x & 0xFFFF) + (y & 0xFFFF);
  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
  return (msw << 16) | (lsw & 0xFFFF);
}

/*
 * Bitwise rotate a 32-bit number to the left.
 */
function bit_rol(num, cnt)
{
  return (num << cnt) | (num >>> (32 - cnt));
}

/*
 * Convert a string to an array of little-endian words
 * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
 */
function str2binl(str)
{
  var bin = Array();
  var mask = (1 << chrsz) - 1;
  for(var i = 0; i < str.length * chrsz; i += chrsz)
    bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);
  return bin;
}

/*
 * Convert an array of little-endian words to a string
 */
function binl2str(bin)
{
  var str = "";
  var mask = (1 << chrsz) - 1;
  for(var i = 0; i < bin.length * 32; i += chrsz)
    str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);
  return str;
}

/*
 * Convert an array of little-endian words to a hex string.
 */
function binl2hex(binarray)
{
  var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef";
  var str = "";
  for(var i = 0; i < binarray.length * 4; i++)
  {
    str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
           hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);
  }
  return str;
}

/*
 * Convert an array of little-endian words to a base-64 string
 */
function binl2b64(binarray)
{
  var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  var str = "";
  for(var i = 0; i < binarray.length * 4; i += 3)
  {
    var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)
                | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
                |  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
    for(var j = 0; j < 4; j++)
    {
      if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;
      else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);
    }
  }
  return str;
}
