var debug = false;

var hexDigits = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"];

var IP, IPinv, SBoxes, P, PC1, PC2, numShifts, E, keys;

init();

// initialize tables
function init() {
	IP = [
	58, 50, 42, 34, 26, 18, 10,  2,
	60, 52, 44, 36, 28, 20, 12,  4,
	62, 54, 46, 38, 30, 22, 14,  6,
	64, 56, 48, 40, 32, 24, 16,  8,
	57, 49, 41, 33, 25, 17,  9,  1,
	59, 51, 43, 35, 27, 19, 11,  3,
	61, 53, 45, 37, 29, 21, 13,  5,
	63, 55, 47, 39, 31, 23, 15,  7];

	IPinv = [
	40,  8, 48, 16, 56, 24, 64, 32,
	39,  7, 47, 15, 55, 23, 63, 31,
	38,  6, 46, 14, 54, 22, 62, 30,
	37,  5, 45, 13, 53, 21, 61, 29,
	36,  4, 44, 12, 52, 20, 60, 28,
	35,  3, 43, 11, 51, 19, 59, 27,
	34,  2, 42, 10, 50, 18, 58, 26,
	33,  1, 41,  9, 49, 17, 57, 25];
	
	
	SBoxes = [
	/* S1 */
	[	14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7,
		0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8,
		4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0,
		15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13
	],
	/* S2 */
	[	15, 1, 8, 14, 6, 11, 3, 4, 9, 7, 2, 13, 12, 0, 5, 10,
		3, 13, 4, 7, 15, 2, 8, 14, 12, 0, 1, 10, 6, 9, 11, 5,
		0, 14, 7, 11, 10, 4, 13, 1, 5, 8, 12, 6, 9, 3, 2, 15,
		13, 8, 10, 1, 3, 15, 4, 2, 11, 6, 7, 12, 0, 5, 14, 9
	],
	/* S3 */
	[	10, 0, 9, 14, 6, 3, 15, 5, 1, 13, 12, 7, 11, 4, 2, 8,
		13, 7, 0, 9, 3, 4, 6, 10, 2, 8, 5, 14, 12, 11, 15, 1,
		13, 6, 4, 9, 8, 15, 3, 0, 11, 1, 2, 12, 5, 10, 14, 7,
		1, 10, 13, 0, 6, 9, 8, 7, 4, 15, 14, 3, 11, 5, 2, 12
	],
	/* S4 */
	[	7, 13, 14, 3, 0, 6, 9, 10, 1, 2, 8, 5, 11, 12, 4, 15,
		13, 8, 11, 5, 6, 15, 0, 3, 4, 7, 2, 12, 1, 10, 14, 9,
		10, 6, 9, 0, 12, 11, 7, 13, 15, 1, 3, 14, 5, 2, 8, 4,
		3, 15, 0, 6, 10, 1, 13, 8, 9, 4, 5, 11, 12, 7, 2, 14
	],
	/* S5 */
	[	2, 12, 4, 1, 7, 10, 11, 6, 8, 5, 3, 15, 13, 0, 14, 9,
		14, 11, 2, 12, 4, 7, 13, 1, 5, 0, 15, 10, 3, 9, 8, 6,
		4, 2, 1, 11, 10, 13, 7, 8, 15, 9, 12, 5, 6, 3, 0, 14,
		11, 8, 12, 7, 1, 14, 2, 13, 6, 15, 0, 9, 10, 4, 5, 3
	],
	/* S6 */
	[	12, 1, 10, 15, 9, 2, 6, 8, 0, 13, 3, 4, 14, 7, 5, 11,
		10, 15, 4, 2, 7, 12, 9, 5, 6, 1, 13, 14, 0, 11, 3, 8,
		9, 14, 15, 5, 2, 8, 12, 3, 7, 0, 4, 10, 1, 13, 11, 6,
		4, 3, 2, 12, 9, 5, 15, 10, 11, 14, 1, 7, 6, 0, 8, 13
	],
	/* S7 */
	[	4, 11, 2, 14, 15, 0, 8, 13, 3, 12, 9, 7, 5, 10, 6, 1,
		13, 0, 11, 7, 4, 9, 1, 10, 14, 3, 5, 12, 2, 15, 8, 6,
		1, 4, 11, 13, 12, 3, 7, 14, 10, 15, 6, 8, 0, 5, 9, 2,
		6, 11, 13, 8, 1, 4, 10, 7, 9, 5, 0, 15, 14, 2, 3, 12
	],
	/* S8 */
	[	13, 2, 8, 4, 6, 15, 11, 1, 10, 9, 3, 14, 5, 0, 12, 7,
		1, 15, 13, 8, 10, 3, 7, 4, 12, 5, 6, 11, 0, 14, 9, 2,
		7, 11, 4, 1, 9, 12, 14, 2, 0, 6, 10, 13, 15, 3, 5, 8,
		2, 1, 14, 7, 4, 10, 8, 13, 15, 12, 9, 0, 3, 5, 6, 11
	]
	];

	P = [
	16,  7, 20, 21,
	29, 12, 28, 17,
	 1, 15, 23, 26,
	 5, 18, 31, 10,
	 2,  8, 24, 14,
	32, 27,  3,  9,
	19, 13, 30,  6,
	22, 11,  4, 25];

	PC1 = [
	57, 49, 41, 33, 25, 17,  9,  1,
	58, 50, 42, 34, 26, 18, 10,  2,
	59, 51, 43, 35, 27,	19, 11,  3,
	60, 52, 44, 36,
	63, 55, 47, 39, 31, 23, 15,  7,
	62, 54, 46, 38, 30, 22, 14,  6,
	61, 53, 45, 37, 29, 21, 13,  5,
	28, 20, 12,  4];

	numShifts = [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1];

	PC2 = [
	14, 17, 11, 24,  1,  5,
	 3, 28, 15,  6, 21, 10,
	23, 19, 12,  4, 26,  8,
	16,  7, 27, 20, 13,  2,
	41, 52, 31, 37, 47, 55,
	30, 40, 51, 45, 33, 48,
	44, 49, 39, 56, 34, 53,
	46, 42, 50, 36, 29, 32];

	E = [
	32,  1,  2,  3,  4,  5,
	 4,  5,  6,  7,  8,  9,
	 8,  9, 10, 11, 12, 13,
	12, 13, 14, 15, 16, 17,
	16, 17, 18, 19, 20, 21,
	20, 21, 22, 23, 24, 25,
	24, 25, 26, 27, 28, 29,
	28, 29, 30, 31, 32,  1];
	
	keys = new Array(16);
}


// APPLICATORS FOR TABLES

// 64-bit -> 64-bit
// use at beginning, before 16 encryption steps
function useIP(dataBlock) {
	var retVal = new Array(64);
	for (var i = 0; i < 64; i++) {
		retVal[i] = dataBlock[IP[i] - 1];
	}
	return retVal;
}

// 64-bit -> 64-bit
// use after all 16 encryption steps, before output
function useIPinv(dataBlock) {
	var retVal = new Array(64);
	for (var i = 0; i < 64; i++) {
		retVal[i] = dataBlock[IPinv[i] - 1];
	}
	return retVal;
}

// 6 bit -> 4 bit
// use in f(), after xor-ing data and key
function useSBox(dataBlock, boxNo) {
	var row = (dataBlock[0] ? 2 : 0) + (dataBlock[5] ? 1 : 0);
	var column = (dataBlock[1] ? 8 : 0) + (dataBlock[2] ? 4 : 0) + (dataBlock[3] ? 2 : 0) + (dataBlock[4] ? 1 : 0);
	var boxValue = SBoxes[boxNo][row * 16 + column];
	var retVal = new Array(4);
	for (var i = 0; i < 4; i++) {
		retVal[i] = ((boxValue & (8 >> i)) > 0)
	}
	return retVal;
}

// 32 bit -> 32 bit
// use in f(), for permuting output after use of SBoxes
function useP(dataBlock) {
	var retVal = new Array(32);
	for (var i = 0; i < 32; i++) {
		retVal[i] = dataBlock[P[i] - 1];
	}
	return retVal;
}

// 64 bit KEY -> 56 bit
// use in key schedule generation
function usePC1(dataBlock) {
	var retVal = new Array(56);
	for (var i = 0; i < 56; i++) {
		retVal[i] = dataBlock[PC1[i] - 1];
	}
	return retVal;
}

// 56 bit KEY -> 48 bit
// use in key schedule generation
function usePC2(dataBlock) {
	var retVal = new Array(48);
	for (var i = 0; i < 48; i++) {
		retVal[i] = dataBlock[PC2[i] - 1];
	}
	return retVal;
}

// 32 bit -> 48 bit
// use in f(), applied immediately to R
function useE(dataBlock) {
	var retVal = new Array(48);
	for (var i = 0; i < 48; i++) {
		retVal[i] = dataBlock[E[i] - 1];
	}
	return retVal;
}

function getNumShifts(iteration) {
	return numShifts[iteration];
}

function getScheduleKey(iteration) {
	return keys[iteration];
}


// convert key String to boolean array
function convertKeyToArray(key) {
	// key-length:64 bit
	var retVal = new Array(64);
	if (key.length < 8) key = "--------";
	for (var i = 0; i < 8; i++) {
		var c = key.charCodeAt(i);
		for (var j = 0; j < 8; j++) {
			var val = ((c & (128 >> j)) != 0);
			retVal[i * 8 + j] = val;
		}
	}
	return retVal;
}

// convert plaintext String to boolean array (length = x * 64 bit)
function convertPlaintextToArray(plaintext) {
	// append spaces until block width is reached
	while (plaintext.length % 8 != 0) plaintext += " ";
	
	var alength = plaintext.length * 8;
	var retVal = new Array(alength);
	
	for (var i = 0; i < plaintext.length; i++) {
		var c = plaintext.charCodeAt(i);
		for (var j = 0; j < 8; j++) {
			var val = ((c & (128 >> j)) != 0);
			retVal[i * 8 + j] = val;
		}
	}
	return retVal;
}

// convert ciphertext String (hexadecimal) to boolean array (length = x * 64 bit)
function convertCiphertextToArray(ciphertext) {
	// append spaces until block width is reached (THIS SHOULD NEVER HAPPEN!)
	while (ciphertext.length % 8 != 0) ciphertext += " ";
	
	var alength = ciphertext.length * 4;
	var retVal = new Array(alength);
	
	for (var i = 0; i < ciphertext.length; i++) {
		var c = parseInt(ciphertext.charAt(i), 16);
		for (var j = 0; j < 4; j++) {
			var val = ((c & (8 >> j)) != 0);
			retVal[i * 4 + j] = val;
		}
	}
	return retVal;	
}

// convert boolean array to ciphertext output (hex)
function convertArrayToCiphertext(array) {
	var retVal = "";
	for (var i = 0; i < array.length; i += 4) {
		var val = (array[i] ? 8 : 0) + (array[i + 1] ? 4 : 0) + (array[i+2] ? 2 : 0) + (array[i+3] ? 1 : 0);
		retVal += hexDigits[val];
	}
	return retVal;
}

// convert boolean array to plaintext output
function convertArrayToPlaintext(array) {
	var retVal = "";
	for (var i = 0; i < array.length; i += 8) {
		var val = (array[i] ? 128 : 0) + (array[i+1] ? 64 : 0) + (array[i+2] ? 32 : 0) + (array[i+3] ? 16 : 0) +
				(array[i+4] ? 8 : 0) + (array[i+5] ? 4 : 0) + (array[i+6] ? 2 : 0) + (array[i+7] ? 1 : 0);
		retVal += String.fromCharCode(val);
	}
	return retVal;
}

// computes arrayA XOR arrayB. Assumes arrayA.length == arrayB.length
function xor(arrayA, arrayB) {
	var retVal = new Array(arrayA.length);
	for (var i = 0; i < arrayA.length; i++) {
		retVal[i] = ((arrayA[i] && !arrayB[i]) || (!arrayA[i] && arrayB[i]));
	}
	return retVal;
}

// shifts all entries n positions to the left, appending entries that fall out left to the right
function shiftLeft(data, n) {
	var retVal = data.slice(n, data.length).concat(data.slice(0, n));
	return retVal;
}

// initializes schedule keys
function computeKeySchedule(key, encrypt) {
	if (encrypt) {
		var pckey = usePC1(key);
		var C = pckey.slice(0, 28);
		var D = pckey.slice(28, 56);
		for (var i = 0; i < 16; i++) {
			// left shift
			C = shiftLeft(C, getNumShifts(i));
			D = shiftLeft(D, getNumShifts(i));
			keys[i] = usePC2(C.concat(D));
		}
	} else {
		var pckey = usePC1(key);
		var C = pckey.slice(0, 28);
		var D = pckey.slice(28, 56);
		for (var i = 0; i < 16; i++) {
			// left shift
			C = shiftLeft(C, getNumShifts(i));
			D = shiftLeft(D, getNumShifts(i));
			keys[15-i] = usePC2(C.concat(D));
		}
	}
}


function arrayCopy(array) {
	var retVal = new Array(array.length);
	for (var i = 0; i < array.length; i++) {
		retVal[i] = array[i];
	}
	return retVal;
}

function DESBlock(dataBlock) {
	var data = useIP(dataBlock);
	var L = data.slice(0, 32);
	var R = data.slice(32, 64);
	for (var i = 0; i < 16; i++) {
		var Lnew = arrayCopy(R);
		var afterF = f(R, getScheduleKey(i));
		R = xor(L, afterF);
		L = Lnew;
	}
	data = R.concat(L);
	return useIPinv(data);
}


function f(R, sKey) {
	var afterE = useE(R);
	var preS = xor(afterE, sKey);
	var postS = new Array();
	for (var i = 0; i < 8; i++) {
		var current = preS.slice(i * 6, (i+1) * 6);
		var sResult = useSBox(current, i);
		postS = postS.concat(sResult);
	}
	return useP(postS);
}

// assume data.length = x*64, key.length = 64. encrypt = true for encryption, false for decryprion
function DES(data, key, encrypt) {
	computeKeySchedule(key, encrypt);
	var retVal = new Array();
	for (var i = 0; i < data.length; i += 64) {
		var block = data.slice(i, i+64);
		var result = DESBlock(block);
		retVal = retVal.concat(result);
	}
	return retVal;
}


function writeArray(array, sep, msg) {
	if (debug == false) return;
	if (sep == "undefined") sep = 8;
	if (msg == "undefined") msg = "debug:";
	var div = document.createElement("div");
	var pnode = document.createElement("div");
	var attr = document.createAttribute("style");
	attr.nodeValue = "background-color:#bbbbbb";
	pnode.setAttributeNode(attr);
	pnode.appendChild(document.createTextNode(msg));
	div.appendChild(pnode);
	var code = document.createElement("code");
	attr = document.createAttribute("style");
	attr.nodeValue = "font-size:14pt";
	code.setAttributeNode(attr);
	var txt = "";
	for (var i = 0; i < array.length; i++) {
		txt += (array[i] ? "1" : "0");
		if (i % sep == sep-1) txt += " ";
	}
	code.appendChild(document.createTextNode(txt));
	div.appendChild(code);
	document.getElementsByTagName("body")[0].appendChild(div);
}

function write(data, msg) {
	if (debug == false) return;
	if (msg == "undefined") msg = "debug:";
	var div = document.createElement("div");
	var pnode = document.createElement("div");
	var attr = document.createAttribute("style");
	attr.nodeValue = "background-color:#bbbbbb";
	pnode.setAttributeNode(attr);
	pnode.appendChild(document.createTextNode(msg));
	div.appendChild(pnode);
	var code = document.createElement("code");
	attr = document.createAttribute("style");
	attr.nodeValue = "font-size:14pt";
	code.setAttributeNode(attr);
	code.appendChild(document.createTextNode(data));
	div.appendChild(code);
	document.getElementsByTagName("body")[0].appendChild(div);
}

// replace all ascii control chars and reserved html chars
function websafe(text) {
	return text.replace(/[\0\x01-\x19]/gim, "_");
}
	
function encode(dataString, keyString) {
	var data = convertPlaintextToArray(dataString);
	var key = convertKeyToArray(keyString);
	var crypt = DES(data, key, true);
	var cryptString = convertArrayToCiphertext(crypt);
	return cryptString;
}

function decode(dataString, keyString) {
	var data = convertCiphertextToArray(dataString);
	var key = convertKeyToArray(keyString);
	var plain = DES(data, key, false);
	var plainString = convertArrayToPlaintext(plain);
	return plainString;
}
	

