JavaScript Implementation of AES Advanced Encryption Standard

JavaScript Implementation of AES Advanced Encryption Standard

varAes={};// Aes namespace/** * AES Cipher function: encrypt 'input' state with Rijndael algorithm * applies Nr rounds (10/12/14) using key schedule w for 'add round key' stage * * @param {Number[]} input 16-byte (128-bit) input state array * @param {Number[][]} w Key schedule as 2D byte-array (Nr+1 x Nb bytes) * @returns {Number[]} Encrypted output state array */Aes.cipher =function(input, w){// main Cipher function [§5.1]varNb=4;// block size (in words): no of columns in state (fixed at 4 for AES)varNr= w.length/Nb-1;// no of rounds: 10/12/14 for 128/192/256-bit keysvar state =[[],[],[],[]];// initialise 4xNb byte-array 'state' with input [§3.4]for(var i=0; i<4*Nb; i++) state[i%4][Math.floor(i/4)]= input[i]; state =Aes.addRoundKey(state, w,0,Nb);for(var round=1; round<Nr; round++){ state =Aes.subBytes(state,Nb); state =Aes.shiftRows(state,Nb); state =Aes.mixColumns(state,Nb); state =Aes.addRoundKey(state, w, round,Nb);} state =Aes.subBytes(state,Nb); state =Aes.shiftRows(state,Nb); state =Aes.addRoundKey(state, w,Nr,Nb);var output =newArray(4*Nb);// convert state to 1-d array before returning [§3.4]for(var i=0; i<4*Nb; i++) output[i]= state[i%4][Math.floor(i/4)];return output;}/** * Perform Key Expansion to generate a Key Schedule * * @param {Number[]} key Key as 16/24/32-byte array * @returns {Number[][]} Expanded key schedule as 2D byte-array (Nr+1 x Nb bytes) */Aes.keyExpansion =function(key){// generate Key Schedule (byte-array Nr+1 x Nb) from Key [§5.2]varNb=4;// block size (in words): no of columns in state (fixed at 4 for AES)varNk= key.length/4// key length (in words): 4/6/8 for 128/192/256-bit keysvarNr=Nk+6;// no of rounds: 10/12/14 for 128/192/256-bit keysvar w =newArray(Nb*(Nr+1));var temp =newArray(4);for(var i=0; i<Nk; i++){var r =[key[4*i], key[4*i+1], key[4*i+2], key[4*i+3]]; w[i]= r;}for(var i=Nk; i<(Nb*(Nr+1)); i++){ w[i]=newArray(4);for(var t=0; t<4; t++) temp[t]= w[i-1][t];if(i %Nk==0){ temp =Aes.subWord(Aes.rotWord(temp));for(var t=0; t<4; t++) temp[t]^=Aes.rCon[i/Nk][t];}elseif(Nk>6&& i%Nk==4){ temp =Aes.subWord(temp);}for(var t=0; t<4; t++) w[i][t]= w[i-Nk][t]^ temp[t];}return w;}/* * ---- remaining routines are private, not called externally ---- */Aes.subBytes =function(s,Nb){// apply SBox to state S [§5.1.1]for(var r=0; r<4; r++){for(var c=0; c<Nb; c++) s[r][c]=Aes.sBox[s[r][c]];}return s;}Aes.shiftRows =function(s,Nb){// shift row r of state S left by r bytes [§5.1.2]var t =newArray(4);for(var r=1; r<4; r++){for(var c=0; c<4; c++) t[c]= s[r][(c+r)%Nb];// shift into temp copyfor(var c=0; c<4; c++) s[r][c]= t[c];// and copy back}// note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):return s;// see asmaes.sourceforge.net/rijndael/rijndaelImplementation.pdf}Aes.mixColumns =function(s,Nb){// combine bytes of each col of state S [§5.1.3]for(var c=0; c<4; c++){var a =newArray(4);// 'a' is a copy of the current column from 's'var b =newArray(4);// 'b' is a•{02} in GF(2^8)for(var i=0; i<4; i++){ a[i]= s[i][c]; b[i]= s[i][c]&0x80? s[i][c]<<1^0x011b: s[i][c]<<1;}// a[n] ^ b[n] is a•{03} in GF(2^8) s[0][c]= b[0]^ a[1]^ b[1]^ a[2]^ a[3];// 2*a0 + 3*a1 + a2 + a3 s[1][c]= a[0]^ b[1]^ a[2]^ b[2]^ a[3];// a0 * 2*a1 + 3*a2 + a3 s[2][c]= a[0]^ a[1]^ b[2]^ a[3]^ b[3];// a0 + a1 + 2*a2 + 3*a3 s[3][c]= a[0]^ b[0]^ a[1]^ a[2]^ b[3];// 3*a0 + a1 + a2 + 2*a3}return s;}Aes.addRoundKey =function(state, w, rnd,Nb){// xor Round Key into state S [§5.1.4]for(var r=0; r<4; r++){for(var c=0; c<Nb; c++) state[r][c]^= w[rnd*4+c][r];}return state;}Aes.subWord =function(w){// apply SBox to 4-byte word wfor(var i=0; i<4; i++) w[i]=Aes.sBox[w[i]];return w;}Aes.rotWord =function(w){// rotate 4-byte word w left by one bytevar tmp = w[0];for(var i=0; i<3; i++) w[i]= w[i+1]; w[3]= tmp;return w;}// sBox is pre-computed multiplicative inverse in GF(2^8) used in subBytes and keyExpansion [§5.1.1]Aes.sBox =[0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16];// rCon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [§5.2]Aes.rCon =[[0x00,0x00,0x00,0x00],[0x01,0x00,0x00,0x00],[0x02,0x00,0x00,0x00],[0x04,0x00,0x00,0x00],[0x08,0x00,0x00,0x00],[0x10,0x00,0x00,0x00],[0x20,0x00,0x00,0x00],[0x40,0x00,0x00,0x00],[0x80,0x00,0x00,0x00],[0x1b,0x00,0x00,0x00],[0x36,0x00,0x00,0x00]];/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *//* AES Counter-mode implementation in JavaScript (c) Chris Veness 2005-2011 *//* - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf *//* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */Aes.Ctr={};// Aes.Ctr namespace: a subclass or extension of Aes/** * Encrypt a text using AES encryption in Counter mode of operation * * Unicode multi-byte character safe * * @param {String} plaintext Source text to be encrypted * @param {String} password The password to use to generate a key * @param {Number} nBits Number of bits to be used in the key (128, 192, or 256) * @returns {string} Encrypted text */Aes.Ctr.encrypt =function(plaintext, password, nBits){var blockSize =16;// block size fixed at 16 bytes / 128 bits (Nb=4) for AESif(!(nBits==128|| nBits==192|| nBits==256))return'';// standard allows 128/192/256 bit keys plaintext =Utf8.encode(plaintext); password =Utf8.encode(password);//var t = new Date(); // timer// use AES itself to encrypt password to get cipher key (using plain password as source for key // expansion) - gives us well encrypted key (though hashed key might be preferred for prod'n use)var nBytes = nBits/8;// no bytes in key (16/24/32)var pwBytes =newArray(nBytes);for(var i=0; i<nBytes; i++){// use 1st 16/24/32 chars of password for key pwBytes[i]= isNaN(password.charCodeAt(i))?0: password.charCodeAt(i);}var key =Aes.cipher(pwBytes,Aes.keyExpansion(pwBytes));// gives us 16-byte key key = key.concat(key.slice(0, nBytes-16));// expand key to 16/24/32 bytes long// initialise 1st 8 bytes of counter block with nonce (NIST SP800-38A §B.2): [0-1] = millisec, // [2-3] = random, [4-7] = seconds, together giving full sub-millisec uniqueness up to Feb 2106var counterBlock =newArray(blockSize);var nonce =(newDate()).getTime();// timestamp: milliseconds since 1-Jan-1970var nonceMs = nonce%1000;var nonceSec =Math.floor(nonce/1000);var nonceRnd =Math.floor(Math.random()*0xffff);for(var i=0; i<2; i++) counterBlock[i]=(nonceMs >>> i*8)&0xff;for(var i=0; i<2; i++) counterBlock[i+2]=(nonceRnd >>> i*8)&0xff;for(var i=0; i<4; i++) counterBlock[i+4]=(nonceSec >>> i*8)&0xff;// and convert it to a string to go on the front of the ciphertextvar ctrTxt ='';for(var i=0; i<8; i++) ctrTxt +=String.fromCharCode(counterBlock[i]);// generate key schedule - an expansion of the key into distinct Key Rounds for each roundvar keySchedule =Aes.keyExpansion(key);var blockCount =Math.ceil(plaintext.length/blockSize);var ciphertxt =newArray(blockCount);// ciphertext as array of stringsfor(var b=0; b<blockCount; b++){// set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)// done in two stages for 32-bit ops: using two words allows us to go past 2^32 blocks (68GB)for(var c=0; c<4; c++) counterBlock[15-c]=(b >>> c*8)&0xff;for(var c=0; c<4; c++) counterBlock[15-c-4]=(b/0x100000000>>> c*8)var cipherCntr =Aes.cipher(counterBlock, keySchedule);// -- encrypt counter block --// block size is reduced on final blockvar blockLength = b<blockCount-1? blockSize :(plaintext.length-1)%blockSize+1;var cipherChar =newArray(blockLength);for(var i=0; i<blockLength; i++){// -- xor plaintext with ciphered counter char-by-char -- cipherChar[i]= cipherCntr[i]^ plaintext.charCodeAt(b*blockSize+i); cipherChar[i]=String.fromCharCode(cipherChar[i]);} ciphertxt[b]= cipherChar.join('');}// Array.join is more efficient than repeated string concatenation in IEvar ciphertext = ctrTxt + ciphertxt.join(''); ciphertext =Base64.encode(ciphertext);// encode in base64//alert((new Date()) - t);return ciphertext;}/** * Decrypt a text encrypted by AES in counter mode of operation * * @param {String} ciphertext Source text to be encrypted * @param {String} password The password to use to generate a key * @param {Number} nBits Number of bits to be used in the key (128, 192, or 256) * @returns {String} Decrypted text */Aes.Ctr.decrypt =function(ciphertext, password, nBits){var blockSize =16;// block size fixed at 16 bytes / 128 bits (Nb=4) for AESif(!(nBits==128|| nBits==192|| nBits==256))return'';// standard allows 128/192/256 bit keys ciphertext =Base64.decode(ciphertext); password =Utf8.encode(password);//var t = new Date(); // timer// use AES to encrypt password (mirroring encrypt routine)var nBytes = nBits/8;// no bytes in keyvar pwBytes =newArray(nBytes);for(var i=0; i<nBytes; i++){ pwBytes[i]= isNaN(password.charCodeAt(i))?0: password.charCodeAt(i);}var key =Aes.cipher(pwBytes,Aes.keyExpansion(pwBytes)); key = key.concat(key.slice(0, nBytes-16));// expand key to 16/24/32 bytes long// recover nonce from 1st 8 bytes of ciphertextvar counterBlock =newArray(8); ctrTxt = ciphertext.slice(0,8);for(var i=0; i<8; i++) counterBlock[i]= ctrTxt.charCodeAt(i);// generate key schedulevar keySchedule =Aes.keyExpansion(key);// separate ciphertext into blocks (skipping past initial 8 bytes)var nBlocks =Math.ceil((ciphertext.length-8)/ blockSize);var ct =newArray(nBlocks);for(var b=0; b<nBlocks; b++) ct[b]= ciphertext.slice(8+b*blockSize,8+b*blockSize+blockSize); ciphertext = ct;// ciphertext is now array of block-length strings// plaintext will get generated block-by-block into array of block-length stringsvar plaintxt =newArray(ciphertext.length);for(var b=0; b<nBlocks; b++){// set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)for(var c=0; c<4; c++) counterBlock[15-c]=((b)>>> c*8)&0xff;for(var c=0; c<4; c++) counterBlock[15-c-4]=(((b+1)/0x100000000-1)>>> c*8)&0xff;var cipherCntr =Aes.cipher(counterBlock, keySchedule);// encrypt counter blockvar plaintxtByte =newArray(ciphertext[b].length);for(var i=0; i<ciphertext[b].length; i++){// -- xor plaintxt with ciphered counter byte-by-byte -- plaintxtByte[i]= cipherCntr[i]^ ciphertext[b].charCodeAt(i); plaintxtByte[i]=String.fromCharCode(plaintxtByte[i]);} plaintxt[b]= plaintxtByte.join('');}// join array of blocks into single plaintext stringvar plaintext = plaintxt.join(''); plaintext =Utf8.decode(plaintext);// decode from UTF8 back to Unicode multi-byte chars//alert((new Date()) - t);return plaintext;}/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *//* Base64 class: Base 64 encoding / decoding (c) Chris Veness 2002-2011 *//* note: depends on Utf8 class *//* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */varBase64={};// Base64 namespaceBase64.code ="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";/** * Encode string into Base64, as defined by RFC 4648 [http://tools.ietf.org/html/rfc4648] * (instance method extending String object). As per RFC 4648, no newlines are added. * * @param {String} str The string to be encoded as base-64 * @param {Boolean} [utf8encode=false] Flag to indicate whether str is Unicode string to be encoded * to UTF8 before conversion to base64; otherwise string is assumed to be 8-bit characters * @returns {String} Base64-encoded string */Base64.encode =function(str, utf8encode){// http://tools.ietf.org/html/rfc4648 utf8encode =(typeof utf8encode =='undefined')?false: utf8encode;var o1, o2, o3, bits, h1, h2, h3, h4, e=[], pad ='', c, plain, coded;var b64 =Base64.code; plain = utf8encode ? str.encodeUTF8(): str; c = plain.length %3;// pad string to length of multiple of 3if(c >0){while(c++<3){ pad +='='; plain +='';}}// note: doing padding here saves us doing special-case packing for trailing 1 or 2 charsfor(c=0; c<plain.length; c+=3){// pack three octets into four hexets o1 = plain.charCodeAt(c); o2 = plain.charCodeAt(c+1); o3 = plain.charCodeAt(c+2); bits = o1<<16| o2<<8| o3; h1 = bits>>18&0x3f; h2 = bits>>12&0x3f; h3 = bits>>6&0x3f; h4 = bits &0x3f;// use hextets to index into code string e[c/3]= b64.charAt(h1)+ b64.charAt(h2)+ b64.charAt(h3)+ b64.charAt(h4);} coded = e.join('');// join() is far faster than repeated string concatenation in IE// replace 'A's from padded nulls with '='s coded = coded.slice(0, coded.length-pad.length)+ pad;return coded;}/** * Decode string from Base64, as defined by RFC 4648 [http://tools.ietf.org/html/rfc4648] * (instance method extending String object). As per RFC 4648, newlines are not catered for. * * @param {String} str The string to be decoded from base-64 * @param {Boolean} [utf8decode=false] Flag to indicate whether str is Unicode string to be decoded * from UTF8 after conversion from base64 * @returns {String} decoded string */Base64.decode =function(str, utf8decode){ utf8decode =(typeof utf8decode =='undefined')?false: utf8decode;var o1, o2, o3, h1, h2, h3, h4, bits, d=[], plain, coded;var b64 =Base64.code; coded = utf8decode ? str.decodeUTF8(): str;for(var c=0; c<coded.length; c+=4){// unpack four hexets into three octets h1 = b64.indexOf(coded.charAt(c)); h2 = b64.indexOf(coded.charAt(c+1)); h3 = b64.indexOf(coded.charAt(c+2)); h4 = b64.indexOf(coded.charAt(c+3)); bits = h1<<18| h2<<12| h3<<6| h4; o1 = bits>>>16&0xff; o2 = bits>>>8&0xff; o3 = bits &0xff; d[c/4]=String.fromCharCode(o1, o2, o3);// check for paddingif(h4 ==0x40) d[c/4]=String.fromCharCode(o1, o2);if(h3 ==0x40) d[c/4]=String.fromCharCode(o1);} plain = d.join('');// join() is far faster than repeated string concatenation in IEreturn utf8decode ? plain.decodeUTF8(): plain;}/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - *//* Utf8 class: encode / decode between multi-byte Unicode characters and UTF-8 multiple *//* single-byte character encoding (c) Chris Veness 2002-2011 *//* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */varUtf8={};// Utf8 namespace/** * Encode multi-byte Unicode string into utf-8 multiple single-byte characters * (BMP / basic multilingual plane only) * * Chars in range U+0080 - U+07FF are encoded in 2 chars, U+0800 - U+FFFF in 3 chars * * @param {String} strUni Unicode string to be encoded as UTF-8 * @returns {String} encoded string */Utf8.encode =function(strUni){// use regular expressions & String.replace callback function for better efficiency // than procedural approachesvar strUtf = strUni.replace(/[\u0080-\u07ff]/g,// U+0080 - U+07FF => 2 bytes 110yyyyy, 10zzzzzzfunction(c){var cc = c.charCodeAt(0);returnString.fromCharCode(0xc0| cc>>6,0x80| cc&0x3f);}); strUtf = strUtf.replace(/[\u0800-\uffff]/g,// U+0800 - U+FFFF => 3 bytes 1110xxxx, 10yyyyyy, 10zzzzzzfunction(c){var cc = c.charCodeAt(0);returnString.fromCharCode(0xe0| cc>>12,0x80| cc>>6&0x3F,0x80| cc&0x3f);});return strUtf;}/** * Decode utf-8 encoded string back into multi-byte Unicode characters * * @param {String} strUtf UTF-8 string to be decoded back to Unicode * @returns {String} decoded string */Utf8.decode =function(strUtf){// note: decode 3-byte chars first as decoded 2-byte strings could appear to be 3-byte char!var strUni = strUtf.replace(/[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g,// 3-byte charsfunction(c){// (note parentheses for precence)var cc =((c.charCodeAt(0)&0x0f)<<12)|((c.charCodeAt(1)&0x3f)<<6)|( c.charCodeAt(2)&0x3f);returnString.fromCharCode(cc);}); strUni = strUni.replace(/[\u00c0-\u00df][\u0080-\u00bf]/g,// 2-byte charsfunction(c){// (note parentheses for precence)var cc =(c.charCodeAt(0)&0x1f)<<6| c.charCodeAt(1)&0x3f;returnString.fromCharCode(cc);});return strUni;}

 

 

This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s