/*
 *  jsmcrypt version 0.1  -  Copyright 2012 F. Doering
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License as
 *  published by the Free Software Foundation; either version 2 of the
 *  License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
 *  02111-1307 USA
 */
 
 
 //this creates a static class mcrypt that is already initialized
 var mcrypt=mcrypt?mcrypt:new function(){
 
 //this allows the user to create instances of this class that keep
 //track of their own key, cipher, and mode
 //calling syntax becomes var myMcrypt=new mcrypt();
 //var mcrypt=function(){
  /**********
 * Private *
 **********/
 
 /************************
 * START OF CIPHER DEFFS *
 ************************/
 
 /* Cipher Data
 * This is an object, keyed with the cipher name whose value is an
 * array containing the number of octets (bytes) in the block size,
 * and the number of octets in the key.
 */
 var ciphers={		//	block size,	key size
  "rijndael-128"	:[	16,			32],
  "rijndael-192"	:[	24,			32],
  "rijndael-256"	:[	32,			32],
  "serpent"			:[	16,			32],
 }
 
 /* blockCipherCalls
 * This object is keyed by the cipher names and the vaules are
 * functions that calls external block ciphers to encypt or
 * decrypt a single block. These functions must have the arguments:
 * function(cipher_name,block,key,encrypt)
 * where: chipher_name is the text of the cipher name,
 * block is an array of inegers representing octets
 * key is a string
 * and encrypt indicates whether it should encrypt or decrypt
 * the block.
 * the function should modify the block as its output
 */
 var blockCipherCalls={};
 blockCipherCalls['rijndael-128']=function(cipher,block,key,encrypt){
	if(key.length<32)
		key+=Array(33-key.length).join(String.fromCharCode(0));
	if(encrypt)
		Rijndael.Encrypt(block,key);
	else
		Rijndael.Decrypt(block,key);
	return block;
 };
 blockCipherCalls['rijndael-192']=blockCipherCalls['rijndael-128'];
 blockCipherCalls['rijndael-256']=blockCipherCalls['rijndael-128'];
 blockCipherCalls.serpent=function(cipher,block,key,encrypt){
	if(encrypt)
		Serpent.Encrypt(block);
	else
		Serpent.Decrypt(block);
	return block;
 };
 blockCipherCalls.serpent.init=function(cipher,key,encrypt){
	var keyA=[];
	for(var i=0;i<key.length;i++)
		keyA[i]=key.charCodeAt(i);
	Serpent.Init(keyA);
 };
 blockCipherCalls.serpent.deinit=function(cipher,key,encrypt){
	Serpent.Close();
 };
 
 /**********************
 * END OF CIPHER DEFFS *
 **********************/
 
 /*********
 * Public *
 *********/ 
 var pub={};
 
 /* Encrypt
 * This function encypts a plaintext message with an IV, key,  ciphertype, and mode
 * The message, key, and IV should be extended ascii strings
 * the ciphertype should be a string that is a supported cipher (see above)
 * the mode should be a string that is a supported mode of operation
 * the key, cipher type, and mode will default to the last used
 * these can be set without encypting by "encrypting" a null message
 */
 pub.Encrypt=function(message,IV,key, cipher, mode){
	return pub.Crypt(true,message,IV,key, cipher, mode);
};
/* Decrypt
 * See Encrypt for usage
 */ 
 
 pub.Decrypt=function(ctext,IV,key, cipher, mode){
	return pub.Crypt(false,ctext,IV,key, cipher, mode);
 };
/* Crypt
 * This function can encrypt or decrypt text
 */
 
pub.Crypt=function(encrypt,text,IV,key, cipher, mode){
	if(key) cKey=key; else key=cKey;
	if(cipher) cCipher=cipher; else cipher=cCipher;
	if(mode) cMode=mode; else mode=cMode;
	if(!text)
		return true;
	if(blockCipherCalls[cipher].init)
		blockCipherCalls[cipher].init(cipher,key,encrypt);
	var blockS=ciphers[cipher][0];
	var chunkS=blockS;
	var iv=new Array(blockS);
	switch(mode){
		case 'cfb':
			chunkS=1;//8-bit
		case 'cbc':
		case 'ncfb':
		case 'nofb':
		case 'ctr':
			if(!IV)
				throw "mcrypt.Crypt: IV Required for mode "+mode;
			if(IV.length!=blockS)
				throw "mcrypt.Crypt: IV must be "+blockS+" characters long for "+cipher;
			for(var i = blockS-1; i>=0; i--)
				iv[i] = IV.charCodeAt(i);
			break;
		case 'ecb':
			break;
		default:
			throw "mcrypt.Crypt: Unsupported mode of opperation"+cMode;
	}
	var chunks=Math.ceil(text.length/chunkS);
	var orig=text.length;

	text += Array(chunks * chunkS - orig + 1).join(String.fromCharCode((chunks * chunkS - orig)));

	var out='';
	switch(mode){
		case 'ecb':
			for(var i = 0; i < chunks; i++){
				for(var j = 0; j < chunkS; j++)
					iv[j]=text.charCodeAt((i*chunkS)+j);
				blockCipherCalls[cipher](cipher,iv, cKey,encrypt);
				for(var j = 0; j < chunkS; j++)
					out+=String.fromCharCode(iv[j]);
			}
			break;
		case 'cbc':
			if(encrypt){
				for(var i = 0; i < chunks; i++){
					for(var j = 0; j < chunkS; j++)
						iv[j]=text.charCodeAt((i*chunkS)+j)^iv[j];
					blockCipherCalls[cipher](cipher,iv, cKey,true);
					for(var j = 0; j < chunkS; j++)
						out+=String.fromCharCode(iv[j]);
				}
			}
			else{
				for(var i = 0; i < chunks; i++){
					var temp=iv;
						iv=new Array(chunkS);
					for(var j = 0; j < chunkS; j++)
						iv[j]=text.charCodeAt((i*chunkS)+j);
					var decr=iv.slice(0);
					blockCipherCalls[cipher](cipher,decr, cKey,false);
					for(var j = 0; j < chunkS; j++)
						out+=String.fromCharCode(temp[j]^decr[j]);
				}
			}
			break;
		case 'cfb':
			for(var i = 0; i < chunks; i++){
				var temp=iv.slice(0);
				blockCipherCalls[cipher](cipher,temp, cKey,true);
				temp=temp[0]^text.charCodeAt(i);
				iv.push(encrypt?temp:text.charCodeAt(i));
				iv.shift();
				out+=String.fromCharCode(temp);
			}
			out=out.substr(0,orig);
			break;
		case 'ncfb':
			for(var i = 0; i < chunks; i++){
				blockCipherCalls[cipher](cipher,iv, cKey,true);
				for(var j = 0; j < chunkS; j++){
					var temp=text.charCodeAt((i*chunkS)+j);
					iv[j]=temp^iv[j];
					out+=String.fromCharCode(iv[j]);
					if(!encrypt)
						iv[j]=temp;
				}
			}
			out=out.substr(0,orig);
			break;
		case 'nofb':
			for(var i = 0; i < chunks; i++){
				blockCipherCalls[cipher](cipher,iv, cKey,true);
				for(var j = 0; j < chunkS; j++)
					out+=String.fromCharCode(text.charCodeAt((i*chunkS)+j)^iv[j]);
			}
			out=out.substr(0,orig);
			break;
		case 'ctr':
			for(var i = 0; i < chunks; i++){
				temp=iv.slice(0);
				blockCipherCalls[cipher](cipher,temp, cKey,true);
				for(var j = 0; j < chunkS; j++)
					out+=String.fromCharCode(text.charCodeAt((i*chunkS)+j)^temp[j]);
				var carry=1;
				var index=chunkS;
				do{
					index--;
					iv[index]+=1;
					carry=iv[index]>>8;
					iv[index]&=255;
				}while(carry)
			}
			out=out.substr(0,orig);
			break;
	}
	if(blockCipherCalls[cipher].deinit)
		blockCipherCalls[cipher].deinit(cipher,key,encrypt);
	return out;
};

//Gets the block size of the specified cipher
pub.get_block_size=function(cipher,mode){
	if(!cipher) cipher=cCipher;
	if(!ciphers[cipher])
		return false;
	return ciphers[cipher][0];
}

//Gets the name of the specified cipher
pub.get_cipher_name=function(cipher){
	if(!cipher) cipher=cCipher;
	if(!ciphers[cipher])
		return false;
	return cipher;
}

//Returns the size of the IV belonging to a specific cipher/mode combination
pub.get_iv_size=function(cipher,mode){
	if(!cipher) cipher=cCipher;
	if(!ciphers[cipher])
		return false;
	return ciphers[cipher][0];
}

//Gets the key size of the specified cipher
pub.get_key_size=function(cipher,mode){
	if(!cipher) cipher=cCipher;
	if(!ciphers[cipher])
		return false;
	return ciphers[cipher][1];
}

//Gets an array of all supported ciphers
pub.list_algorithms=function(){
	var ret=[];
	for(var i in ciphers)
		ret.push(i);
	return ret;
}

pub.list_modes=function(){
	return ['ecb','cbc','cfb','ncfb','nofb','ctr'];
}


 
 /**********
 * Private *
 **********/
  
 var cMode='cbc';
 var cCipher='rijndael-128';
 var cKey='12345678911234567892123456789312';


return pub; 
};