1/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 2 3/* 4 * AES Cipher function: encrypt 'input' with Rijndael algorithm 5 * 6 * takes byte-array 'input' (16 bytes) 7 * 2D byte-array key schedule 'w' (Nr+1 x Nb bytes) 8 * 9 * applies Nr rounds (10/12/14) using key schedule w for 'add round key' stage 10 * 11 * returns byte-array encrypted value (16 bytes) 12 */ 13function Cipher(input, w) { // main Cipher function [§5.1] 14 var Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES) 15 var Nr = w.length/Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys 16 17 var state = [[],[],[],[]]; // initialise 4xNb byte-array 'state' with input [§3.4] 18 for (var i=0; i<4*Nb; i++) state[i%4][Math.floor(i/4)] = input[i]; 19 20 state = AddRoundKey(state, w, 0, Nb); 21 22 for (var round=1; round<Nr; round++) { 23 state = SubBytes(state, Nb); 24 state = ShiftRows(state, Nb); 25 state = MixColumns(state, Nb); 26 state = AddRoundKey(state, w, round, Nb); 27 } 28 29 state = SubBytes(state, Nb); 30 state = ShiftRows(state, Nb); 31 state = AddRoundKey(state, w, Nr, Nb); 32 33 var output = new Array(4*Nb); // convert state to 1-d array before returning [§3.4] 34 for (var i=0; i<4*Nb; i++) output[i] = state[i%4][Math.floor(i/4)]; 35 return output; 36} 37 38 39function SubBytes(s, Nb) { // apply SBox to state S [§5.1.1] 40 for (var r=0; r<4; r++) { 41 for (var c=0; c<Nb; c++) s[r][c] = Sbox[s[r][c]]; 42 } 43 return s; 44} 45 46 47function ShiftRows(s, Nb) { // shift row r of state S left by r bytes [§5.1.2] 48 var t = new Array(4); 49 for (var r=1; r<4; r++) { 50 for (var c=0; c<4; c++) t[c] = s[r][(c+r)%Nb]; // shift into temp copy 51 for (var c=0; c<4; c++) s[r][c] = t[c]; // and copy back 52 } // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES): 53 return s; // see fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf 54} 55 56 57function MixColumns(s, Nb) { // combine bytes of each col of state S [§5.1.3] 58 for (var c=0; c<4; c++) { 59 var a = new Array(4); // 'a' is a copy of the current column from 's' 60 var b = new Array(4); // 'b' is a•{02} in GF(2^8) 61 for (var i=0; i<4; i++) { 62 a[i] = s[i][c]; 63 b[i] = s[i][c]&0x80 ? s[i][c]<<1 ^ 0x011b : s[i][c]<<1; 64 } 65 // a[n] ^ b[n] is a•{03} in GF(2^8) 66 s[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; // 2*a0 + 3*a1 + a2 + a3 67 s[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; // a0 * 2*a1 + 3*a2 + a3 68 s[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; // a0 + a1 + 2*a2 + 3*a3 69 s[3][c] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; // 3*a0 + a1 + a2 + 2*a3 70 } 71 return s; 72} 73 74 75function AddRoundKey(state, w, rnd, Nb) { // xor Round Key into state S [§5.1.4] 76 for (var r=0; r<4; r++) { 77 for (var c=0; c<Nb; c++) state[r][c] ^= w[rnd*4+c][r]; 78 } 79 return state; 80} 81 82 83function KeyExpansion(key) { // generate Key Schedule (byte-array Nr+1 x Nb) from Key [§5.2] 84 var Nb = 4; // block size (in words): no of columns in state (fixed at 4 for AES) 85 var Nk = key.length/4 // key length (in words): 4/6/8 for 128/192/256-bit keys 86 var Nr = Nk + 6; // no of rounds: 10/12/14 for 128/192/256-bit keys 87 88 var w = new Array(Nb*(Nr+1)); 89 var temp = new Array(4); 90 91 for (var i=0; i<Nk; i++) { 92 var r = [key[4*i], key[4*i+1], key[4*i+2], key[4*i+3]]; 93 w[i] = r; 94 } 95 96 for (var i=Nk; i<(Nb*(Nr+1)); i++) { 97 w[i] = new Array(4); 98 for (var t=0; t<4; t++) temp[t] = w[i-1][t]; 99 if (i % Nk == 0) { 100 temp = SubWord(RotWord(temp)); 101 for (var t=0; t<4; t++) temp[t] ^= Rcon[i/Nk][t]; 102 } else if (Nk > 6 && i%Nk == 4) { 103 temp = SubWord(temp); 104 } 105 for (var t=0; t<4; t++) w[i][t] = w[i-Nk][t] ^ temp[t]; 106 } 107 108 return w; 109} 110 111function SubWord(w) { // apply SBox to 4-byte word w 112 for (var i=0; i<4; i++) w[i] = Sbox[w[i]]; 113 return w; 114} 115 116function RotWord(w) { // rotate 4-byte word w left by one byte 117 w[4] = w[0]; 118 for (var i=0; i<4; i++) w[i] = w[i+1]; 119 return w; 120} 121 122 123// Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [§5.1.1] 124var Sbox = [0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76, 125 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0, 126 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15, 127 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75, 128 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84, 129 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf, 130 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8, 131 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2, 132 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73, 133 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb, 134 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79, 135 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08, 136 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a, 137 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e, 138 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf, 139 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16]; 140 141// Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [§5.2] 142var Rcon = [ [0x00, 0x00, 0x00, 0x00], 143 [0x01, 0x00, 0x00, 0x00], 144 [0x02, 0x00, 0x00, 0x00], 145 [0x04, 0x00, 0x00, 0x00], 146 [0x08, 0x00, 0x00, 0x00], 147 [0x10, 0x00, 0x00, 0x00], 148 [0x20, 0x00, 0x00, 0x00], 149 [0x40, 0x00, 0x00, 0x00], 150 [0x80, 0x00, 0x00, 0x00], 151 [0x1b, 0x00, 0x00, 0x00], 152 [0x36, 0x00, 0x00, 0x00] ]; 153 154 155/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 156 157/* 158 * Use AES to encrypt 'plaintext' with 'password' using 'nBits' key, in 'Counter' mode of operation 159 * - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf 160 * for each block 161 * - outputblock = cipher(counter, key) 162 * - cipherblock = plaintext xor outputblock 163 */ 164function AESEncryptCtr(plaintext, password, nBits) { 165 if (!(nBits==128 || nBits==192 || nBits==256)) return ''; // standard allows 128/192/256 bit keys 166 167 // for this example script, generate the key by applying Cipher to 1st 16/24/32 chars of password; 168 // for real-world applications, a more secure approach would be to hash the password e.g. with SHA-1 169 var nBytes = nBits/8; // no bytes in key 170 var pwBytes = new Array(nBytes); 171 for (var i=0; i<nBytes; i++) pwBytes[i] = password.charCodeAt(i) & 0xff; 172 var key = Cipher(pwBytes, KeyExpansion(pwBytes)); 173 key = key.concat(key.slice(0, nBytes-16)); // key is now 16/24/32 bytes long 174 175 // initialise counter block (NIST SP800-38A §B.2): millisecond time-stamp for nonce in 1st 8 bytes, 176 // block counter in 2nd 8 bytes 177 var blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES 178 var counterBlock = new Array(blockSize); // block size fixed at 16 bytes / 128 bits (Nb=4) for AES 179 var nonce = (new Date()).getTime(); // milliseconds since 1-Jan-1970 180 181 // encode nonce in two stages to cater for JavaScript 32-bit limit on bitwise ops 182 for (var i=0; i<4; i++) counterBlock[i] = (nonce >>> i*8) & 0xff; 183 for (var i=0; i<4; i++) counterBlock[i+4] = (nonce/0x100000000 >>> i*8) & 0xff; 184 185 // generate key schedule - an expansion of the key into distinct Key Rounds for each round 186 var keySchedule = KeyExpansion(key); 187 188 var blockCount = Math.ceil(plaintext.length/blockSize); 189 var ciphertext = new Array(blockCount); // ciphertext as array of strings 190 191 for (var b=0; b<blockCount; b++) { 192 // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes) 193 // again done in two stages for 32-bit ops 194 for (var c=0; c<4; c++) counterBlock[15-c] = (b >>> c*8) & 0xff; 195 for (var c=0; c<4; c++) counterBlock[15-c-4] = (b/0x100000000 >>> c*8) 196 197 var cipherCntr = Cipher(counterBlock, keySchedule); // -- encrypt counter block -- 198 199 // calculate length of final block: 200 var blockLength = b<blockCount-1 ? blockSize : (plaintext.length-1)%blockSize+1; 201 202 var ct = ''; 203 for (var i=0; i<blockLength; i++) { // -- xor plaintext with ciphered counter byte-by-byte -- 204 var plaintextByte = plaintext.charCodeAt(b*blockSize+i); 205 var cipherByte = plaintextByte ^ cipherCntr[i]; 206 ct += String.fromCharCode(cipherByte); 207 } 208 // ct is now ciphertext for this block 209 210 ciphertext[b] = escCtrlChars(ct); // escape troublesome characters in ciphertext 211 } 212 213 // convert the nonce to a string to go on the front of the ciphertext 214 var ctrTxt = ''; 215 for (var i=0; i<8; i++) ctrTxt += String.fromCharCode(counterBlock[i]); 216 ctrTxt = escCtrlChars(ctrTxt); 217 218 // use '-' to separate blocks, use Array.join to concatenate arrays of strings for efficiency 219 return ctrTxt + '-' + ciphertext.join('-'); 220} 221 222 223/* 224 * Use AES to decrypt 'ciphertext' with 'password' using 'nBits' key, in Counter mode of operation 225 * 226 * for each block 227 * - outputblock = cipher(counter, key) 228 * - cipherblock = plaintext xor outputblock 229 */ 230function AESDecryptCtr(ciphertext, password, nBits) { 231 if (!(nBits==128 || nBits==192 || nBits==256)) return ''; // standard allows 128/192/256 bit keys 232 233 var nBytes = nBits/8; // no bytes in key 234 var pwBytes = new Array(nBytes); 235 for (var i=0; i<nBytes; i++) pwBytes[i] = password.charCodeAt(i) & 0xff; 236 var pwKeySchedule = KeyExpansion(pwBytes); 237 var key = Cipher(pwBytes, pwKeySchedule); 238 key = key.concat(key.slice(0, nBytes-16)); // key is now 16/24/32 bytes long 239 240 var keySchedule = KeyExpansion(key); 241 242 ciphertext = ciphertext.split('-'); // split ciphertext into array of block-length strings 243 244 // recover nonce from 1st element of ciphertext 245 var blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES 246 var counterBlock = new Array(blockSize); 247 var ctrTxt = unescCtrlChars(ciphertext[0]); 248 for (var i=0; i<8; i++) counterBlock[i] = ctrTxt.charCodeAt(i); 249 250 var plaintext = new Array(ciphertext.length-1); 251 252 for (var b=1; b<ciphertext.length; b++) { 253 // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes) 254 for (var c=0; c<4; c++) counterBlock[15-c] = ((b-1) >>> c*8) & 0xff; 255 for (var c=0; c<4; c++) counterBlock[15-c-4] = ((b/0x100000000-1) >>> c*8) & 0xff; 256 257 var cipherCntr = Cipher(counterBlock, keySchedule); // encrypt counter block 258 259 ciphertext[b] = unescCtrlChars(ciphertext[b]); 260 261 var pt = ''; 262 for (var i=0; i<ciphertext[b].length; i++) { 263 // -- xor plaintext with ciphered counter byte-by-byte -- 264 var ciphertextByte = ciphertext[b].charCodeAt(i); 265 var plaintextByte = ciphertextByte ^ cipherCntr[i]; 266 pt += String.fromCharCode(plaintextByte); 267 } 268 // pt is now plaintext for this block 269 270 plaintext[b-1] = pt; // b-1 'cos no initial nonce block in plaintext 271 } 272 273 return plaintext.join(''); 274} 275 276/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 277 278function escCtrlChars(str) { // escape control chars which might cause problems handling ciphertext 279 return str.replace(/[\0\t\n\v\f\r\xa0'"!-]/g, function(c) { return '!' + c.charCodeAt(0) + '!'; }); 280} // \xa0 to cater for bug in Firefox; include '-' to leave it free for use as a block marker 281 282function unescCtrlChars(str) { // unescape potentially problematic control characters 283 return str.replace(/!\d\d?\d?!/g, function(c) { return String.fromCharCode(c.slice(1,-1)); }); 284} 285/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 286 287/* 288 * if escCtrlChars()/unescCtrlChars() still gives problems, use encodeBase64()/decodeBase64() instead 289 */ 290var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 291 292function encodeBase64(str) { // http://tools.ietf.org/html/rfc4648 293 var o1, o2, o3, h1, h2, h3, h4, bits, i=0, enc=''; 294 295 str = encodeUTF8(str); // encode multi-byte chars into UTF-8 for byte-array 296 297 do { // pack three octets into four hexets 298 o1 = str.charCodeAt(i++); 299 o2 = str.charCodeAt(i++); 300 o3 = str.charCodeAt(i++); 301 302 bits = o1<<16 | o2<<8 | o3; 303 304 h1 = bits>>18 & 0x3f; 305 h2 = bits>>12 & 0x3f; 306 h3 = bits>>6 & 0x3f; 307 h4 = bits & 0x3f; 308 309 // end of string? index to '=' in b64 310 if (isNaN(o3)) h4 = 64; 311 if (isNaN(o2)) h3 = 64; 312 313 // use hexets to index into b64, and append result to encoded string 314 enc += b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4); 315 } while (i < str.length); 316 317 return enc; 318} 319 320function decodeBase64(str) { 321 var o1, o2, o3, h1, h2, h3, h4, bits, i=0, enc=''; 322 323 do { // unpack four hexets into three octets using index points in b64 324 h1 = b64.indexOf(str.charAt(i++)); 325 h2 = b64.indexOf(str.charAt(i++)); 326 h3 = b64.indexOf(str.charAt(i++)); 327 h4 = b64.indexOf(str.charAt(i++)); 328 329 bits = h1<<18 | h2<<12 | h3<<6 | h4; 330 331 o1 = bits>>16 & 0xff; 332 o2 = bits>>8 & 0xff; 333 o3 = bits & 0xff; 334 335 if (h3 == 64) enc += String.fromCharCode(o1); 336 else if (h4 == 64) enc += String.fromCharCode(o1, o2); 337 else enc += String.fromCharCode(o1, o2, o3); 338 } while (i < str.length); 339 340 return decodeUTF8(enc); // decode UTF-8 byte-array back to Unicode 341} 342 343function encodeUTF8(str) { // encode multi-byte string into utf-8 multiple single-byte characters 344 str = str.replace( 345 /[\u0080-\u07ff]/g, // U+0080 - U+07FF = 2-byte chars 346 function(c) { 347 var cc = c.charCodeAt(0); 348 return String.fromCharCode(0xc0 | cc>>6, 0x80 | cc&0x3f); } 349 ); 350 str = str.replace( 351 /[\u0800-\uffff]/g, // U+0800 - U+FFFF = 3-byte chars 352 function(c) { 353 var cc = c.charCodeAt(0); 354 return String.fromCharCode(0xe0 | cc>>12, 0x80 | cc>>6&0x3F, 0x80 | cc&0x3f); } 355 ); 356 return str; 357} 358 359function decodeUTF8(str) { // decode utf-8 encoded string back into multi-byte characters 360 str = str.replace( 361 /[\u00c0-\u00df][\u0080-\u00bf]/g, // 2-byte chars 362 function(c) { 363 var cc = (c.charCodeAt(0)&0x1f)<<6 | c.charCodeAt(1)&0x3f; 364 return String.fromCharCode(cc); } 365 ); 366 str = str.replace( 367 /[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g, // 3-byte chars 368 function(c) { 369 var cc = (c.charCodeAt(0)&0x0f)<<12 | (c.charCodeAt(1)&0x3f<<6) | c.charCodeAt(2)&0x3f; 370 return String.fromCharCode(cc); } 371 ); 372 return str; 373} 374 375 376function byteArrayToHexStr(b) { // convert byte array to hex string for displaying test vectors 377 var s = ''; 378 for (var i=0; i<b.length; i++) s += b[i].toString(16) + ' '; 379 return s; 380} 381 382/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 383 384 385var plainText = "ROMEO: But, soft! what light through yonder window breaks?\n\ 386It is the east, and Juliet is the sun.\n\ 387Arise, fair sun, and kill the envious moon,\n\ 388Who is already sick and pale with grief,\n\ 389That thou her maid art far more fair than she:\n\ 390Be not her maid, since she is envious;\n\ 391Her vestal livery is but sick and green\n\ 392And none but fools do wear it; cast it off.\n\ 393It is my lady, O, it is my love!\n\ 394O, that she knew she were!\n\ 395She speaks yet she says nothing: what of that?\n\ 396Her eye discourses; I will answer it.\n\ 397I am too bold, 'tis not to me she speaks:\n\ 398Two of the fairest stars in all the heaven,\n\ 399Having some business, do entreat her eyes\n\ 400To twinkle in their spheres till they return.\n\ 401What if her eyes were there, they in her head?\n\ 402The brightness of her cheek would shame those stars,\n\ 403As daylight doth a lamp; her eyes in heaven\n\ 404Would through the airy region stream so bright\n\ 405That birds would sing and think it were not night.\n\ 406See, how she leans her cheek upon her hand!\n\ 407O, that I were a glove upon that hand,\n\ 408That I might touch that cheek!\n\ 409JULIET: Ay me!\n\ 410ROMEO: She speaks:\n\ 411O, speak again, bright angel! for thou art\n\ 412As glorious to this night, being o'er my head\n\ 413As is a winged messenger of heaven\n\ 414Unto the white-upturned wondering eyes\n\ 415Of mortals that fall back to gaze on him\n\ 416When he bestrides the lazy-pacing clouds\n\ 417And sails upon the bosom of the air."; 418 419var password = "O Romeo, Romeo! wherefore art thou Romeo?"; 420 421var cipherText = AESEncryptCtr(plainText, password, 256); 422var decryptedText = AESDecryptCtr(cipherText, password, 256); 423