tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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);