check-crypto-aes.js (17174B)
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 */ 13 function 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 39 function 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 47 function 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 57 function 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 75 function 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 83 function 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 111 function 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 116 function 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] 124 var 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] 142 var 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 */ 164 function 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("2000-01-01")).getTime(); // milliseconds since 1-Jan-1970; 180 // fixed for repeatability 181 182 // encode nonce in two stages to cater for JavaScript 32-bit limit on bitwise ops 183 for (var i=0; i<4; i++) counterBlock[i] = (nonce >>> i*8) & 0xff; 184 for (var i=0; i<4; i++) counterBlock[i+4] = (nonce/0x100000000 >>> i*8) & 0xff; 185 186 // generate key schedule - an expansion of the key into distinct Key Rounds for each round 187 var keySchedule = KeyExpansion(key); 188 189 var blockCount = Math.ceil(plaintext.length/blockSize); 190 var ciphertext = new Array(blockCount); // ciphertext as array of strings 191 192 for (var b=0; b<blockCount; b++) { 193 // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes) 194 // again done in two stages for 32-bit ops 195 for (var c=0; c<4; c++) counterBlock[15-c] = (b >>> c*8) & 0xff; 196 for (var c=0; c<4; c++) counterBlock[15-c-4] = (b/0x100000000 >>> c*8) 197 198 var cipherCntr = Cipher(counterBlock, keySchedule); // -- encrypt counter block -- 199 200 // calculate length of final block: 201 var blockLength = b<blockCount-1 ? blockSize : (plaintext.length-1)%blockSize+1; 202 203 var ct = ''; 204 for (var i=0; i<blockLength; i++) { // -- xor plaintext with ciphered counter byte-by-byte -- 205 var plaintextByte = plaintext.charCodeAt(b*blockSize+i); 206 var cipherByte = plaintextByte ^ cipherCntr[i]; 207 ct += String.fromCharCode(cipherByte); 208 } 209 // ct is now ciphertext for this block 210 211 ciphertext[b] = escCtrlChars(ct); // escape troublesome characters in ciphertext 212 } 213 214 // convert the nonce to a string to go on the front of the ciphertext 215 var ctrTxt = ''; 216 for (var i=0; i<8; i++) ctrTxt += String.fromCharCode(counterBlock[i]); 217 ctrTxt = escCtrlChars(ctrTxt); 218 219 // use '-' to separate blocks, use Array.join to concatenate arrays of strings for efficiency 220 return ctrTxt + '-' + ciphertext.join('-'); 221 } 222 223 224 /* 225 * Use AES to decrypt 'ciphertext' with 'password' using 'nBits' key, in Counter mode of operation 226 * 227 * for each block 228 * - outputblock = cipher(counter, key) 229 * - cipherblock = plaintext xor outputblock 230 */ 231 function AESDecryptCtr(ciphertext, password, nBits) { 232 if (!(nBits==128 || nBits==192 || nBits==256)) return ''; // standard allows 128/192/256 bit keys 233 234 var nBytes = nBits/8; // no bytes in key 235 var pwBytes = new Array(nBytes); 236 for (var i=0; i<nBytes; i++) pwBytes[i] = password.charCodeAt(i) & 0xff; 237 var pwKeySchedule = KeyExpansion(pwBytes); 238 var key = Cipher(pwBytes, pwKeySchedule); 239 key = key.concat(key.slice(0, nBytes-16)); // key is now 16/24/32 bytes long 240 241 var keySchedule = KeyExpansion(key); 242 243 ciphertext = ciphertext.split('-'); // split ciphertext into array of block-length strings 244 245 // recover nonce from 1st element of ciphertext 246 var blockSize = 16; // block size fixed at 16 bytes / 128 bits (Nb=4) for AES 247 var counterBlock = new Array(blockSize); 248 var ctrTxt = unescCtrlChars(ciphertext[0]); 249 for (var i=0; i<8; i++) counterBlock[i] = ctrTxt.charCodeAt(i); 250 251 var plaintext = new Array(ciphertext.length-1); 252 253 for (var b=1; b<ciphertext.length; b++) { 254 // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes) 255 for (var c=0; c<4; c++) counterBlock[15-c] = ((b-1) >>> c*8) & 0xff; 256 for (var c=0; c<4; c++) counterBlock[15-c-4] = ((b/0x100000000-1) >>> c*8) & 0xff; 257 258 var cipherCntr = Cipher(counterBlock, keySchedule); // encrypt counter block 259 260 ciphertext[b] = unescCtrlChars(ciphertext[b]); 261 262 var pt = ''; 263 for (var i=0; i<ciphertext[b].length; i++) { 264 // -- xor plaintext with ciphered counter byte-by-byte -- 265 var ciphertextByte = ciphertext[b].charCodeAt(i); 266 var plaintextByte = ciphertextByte ^ cipherCntr[i]; 267 pt += String.fromCharCode(plaintextByte); 268 } 269 // pt is now plaintext for this block 270 271 plaintext[b-1] = pt; // b-1 'cos no initial nonce block in plaintext 272 } 273 274 return plaintext.join(''); 275 } 276 277 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 278 279 function escCtrlChars(str) { // escape control chars which might cause problems handling ciphertext 280 return str.replace(/[\0\t\n\v\f\r\xa0'"!-]/g, function(c) { return '!' + c.charCodeAt(0) + '!'; }); 281 } // \xa0 to cater for bug in Firefox; include '-' to leave it free for use as a block marker 282 283 function unescCtrlChars(str) { // unescape potentially problematic control characters 284 return str.replace(/!\d\d?\d?!/g, function(c) { return String.fromCharCode(c.slice(1,-1)); }); 285 } 286 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 287 288 /* 289 * if escCtrlChars()/unescCtrlChars() still gives problems, use encodeBase64()/decodeBase64() instead 290 */ 291 var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 292 293 function encodeBase64(str) { // http://tools.ietf.org/html/rfc4648 294 var o1, o2, o3, h1, h2, h3, h4, bits, i=0, enc=''; 295 296 str = encodeUTF8(str); // encode multi-byte chars into UTF-8 for byte-array 297 298 do { // pack three octets into four hexets 299 o1 = str.charCodeAt(i++); 300 o2 = str.charCodeAt(i++); 301 o3 = str.charCodeAt(i++); 302 303 bits = o1<<16 | o2<<8 | o3; 304 305 h1 = bits>>18 & 0x3f; 306 h2 = bits>>12 & 0x3f; 307 h3 = bits>>6 & 0x3f; 308 h4 = bits & 0x3f; 309 310 // end of string? index to '=' in b64 311 if (isNaN(o3)) h4 = 64; 312 if (isNaN(o2)) h3 = 64; 313 314 // use hexets to index into b64, and append result to encoded string 315 enc += b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3) + b64.charAt(h4); 316 } while (i < str.length); 317 318 return enc; 319 } 320 321 function decodeBase64(str) { 322 var o1, o2, o3, h1, h2, h3, h4, bits, i=0, enc=''; 323 324 do { // unpack four hexets into three octets using index points in b64 325 h1 = b64.indexOf(str.charAt(i++)); 326 h2 = b64.indexOf(str.charAt(i++)); 327 h3 = b64.indexOf(str.charAt(i++)); 328 h4 = b64.indexOf(str.charAt(i++)); 329 330 bits = h1<<18 | h2<<12 | h3<<6 | h4; 331 332 o1 = bits>>16 & 0xff; 333 o2 = bits>>8 & 0xff; 334 o3 = bits & 0xff; 335 336 if (h3 == 64) enc += String.fromCharCode(o1); 337 else if (h4 == 64) enc += String.fromCharCode(o1, o2); 338 else enc += String.fromCharCode(o1, o2, o3); 339 } while (i < str.length); 340 341 return decodeUTF8(enc); // decode UTF-8 byte-array back to Unicode 342 } 343 344 function encodeUTF8(str) { // encode multi-byte string into utf-8 multiple single-byte characters 345 str = str.replace( 346 /[\u0080-\u07ff]/g, // U+0080 - U+07FF = 2-byte chars 347 function(c) { 348 var cc = c.charCodeAt(0); 349 return String.fromCharCode(0xc0 | cc>>6, 0x80 | cc&0x3f); } 350 ); 351 str = str.replace( 352 /[\u0800-\uffff]/g, // U+0800 - U+FFFF = 3-byte chars 353 function(c) { 354 var cc = c.charCodeAt(0); 355 return String.fromCharCode(0xe0 | cc>>12, 0x80 | cc>>6&0x3F, 0x80 | cc&0x3f); } 356 ); 357 return str; 358 } 359 360 function decodeUTF8(str) { // decode utf-8 encoded string back into multi-byte characters 361 str = str.replace( 362 /[\u00c0-\u00df][\u0080-\u00bf]/g, // 2-byte chars 363 function(c) { 364 var cc = (c.charCodeAt(0)&0x1f)<<6 | c.charCodeAt(1)&0x3f; 365 return String.fromCharCode(cc); } 366 ); 367 str = str.replace( 368 /[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g, // 3-byte chars 369 function(c) { 370 var cc = (c.charCodeAt(0)&0x0f)<<12 | (c.charCodeAt(1)&0x3f<<6) | c.charCodeAt(2)&0x3f; 371 return String.fromCharCode(cc); } 372 ); 373 return str; 374 } 375 376 377 function byteArrayToHexStr(b) { // convert byte array to hex string for displaying test vectors 378 var s = ''; 379 for (var i=0; i<b.length; i++) s += b[i].toString(16) + ' '; 380 return s; 381 } 382 383 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */ 384 385 386 var plainText = "ROMEO: But, soft! what light through yonder window breaks?\n\ 387 It is the east, and Juliet is the sun.\n\ 388 Arise, fair sun, and kill the envious moon,\n\ 389 Who is already sick and pale with grief,\n\ 390 That thou her maid art far more fair than she:\n\ 391 Be not her maid, since she is envious;\n\ 392 Her vestal livery is but sick and green\n\ 393 And none but fools do wear it; cast it off.\n\ 394 It is my lady, O, it is my love!\n\ 395 O, that she knew she were!\n\ 396 She speaks yet she says nothing: what of that?\n\ 397 Her eye discourses; I will answer it.\n\ 398 I am too bold, 'tis not to me she speaks:\n\ 399 Two of the fairest stars in all the heaven,\n\ 400 Having some business, do entreat her eyes\n\ 401 To twinkle in their spheres till they return.\n\ 402 What if her eyes were there, they in her head?\n\ 403 The brightness of her cheek would shame those stars,\n\ 404 As daylight doth a lamp; her eyes in heaven\n\ 405 Would through the airy region stream so bright\n\ 406 That birds would sing and think it were not night.\n\ 407 See, how she leans her cheek upon her hand!\n\ 408 O, that I were a glove upon that hand,\n\ 409 That I might touch that cheek!\n\ 410 JULIET: Ay me!\n\ 411 ROMEO: She speaks:\n\ 412 O, speak again, bright angel! for thou art\n\ 413 As glorious to this night, being o'er my head\n\ 414 As is a winged messenger of heaven\n\ 415 Unto the white-upturned wondering eyes\n\ 416 Of mortals that fall back to gaze on him\n\ 417 When he bestrides the lazy-pacing clouds\n\ 418 And sails upon the bosom of the air."; 419 420 var password = "O Romeo, Romeo! wherefore art thou Romeo?"; 421 422 var cipherText = AESEncryptCtr(plainText, password, 256); 423 var decryptedText = AESDecryptCtr(cipherText, password, 256); 424 425 assertEq(plainText, decryptedText);