source: Dev/branches/rest-dojo-ui/client/dojox/sql/_crypto.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 16.7 KB
Line 
1dojo.provide("dojox.sql._crypto");
2dojo.mixin(dojox.sql._crypto, {
3        // summary: dojox.sql cryptography code
4        // description:
5        //      Taken from http://www.movable-type.co.uk/scripts/aes.html by
6        //      Chris Veness (CLA signed); adapted for Dojo and Google Gears Worker Pool
7        //      by Brad Neuberg, bkn3@columbia.edu
8        //
9        // _POOL_SIZE:
10        //      Size of worker pool to create to help with crypto
11        _POOL_SIZE: 100,
12
13        encrypt: function(plaintext, password, callback){
14                // summary:
15                //      Use Corrected Block TEA to encrypt plaintext using password
16                //      (note plaintext & password must be strings not string objects).
17                //      Results will be returned to the 'callback' asychronously.
18                this._initWorkerPool();
19
20                var msg ={plaintext: plaintext, password: password};
21                msg = dojo.toJson(msg);
22                msg = "encr:" + String(msg);
23
24                this._assignWork(msg, callback);
25        },
26
27        decrypt: function(ciphertext, password, callback){
28                // summary:
29                //      Use Corrected Block TEA to decrypt ciphertext using password
30                //      (note ciphertext & password must be strings not string objects).
31                //      Results will be returned to the 'callback' asychronously.
32                this._initWorkerPool();
33
34                var msg = {ciphertext: ciphertext, password: password};
35                msg = dojo.toJson(msg);
36                msg = "decr:" + String(msg);
37
38                this._assignWork(msg, callback);
39        },
40
41        _initWorkerPool: function(){
42                // bugs in Google Gears prevents us from dynamically creating
43                // and destroying workers as we need them -- the worker
44                // pool functionality stops working after a number of crypto
45                // cycles (probably related to a memory leak in Google Gears).
46                // this is too bad, since it results in much simpler code.
47
48                // instead, we have to create a pool of workers and reuse them. we
49                // keep a stack of 'unemployed' Worker IDs that are currently not working.
50                // if a work request comes in, we pop off the 'unemployed' stack
51                // and put them to work, storing them in an 'employed' hashtable,
52                // keyed by their Worker ID with the value being the callback function
53                // that wants the result. when an employed worker is done, we get
54                // a message in our 'manager' which adds this worker back to the
55                // unemployed stack and routes the result to the callback that
56                // wanted it. if all the workers were employed in the past but
57                // more work needed to be done (i.e. it's a tight labor pool ;)
58                // then the work messages are pushed onto
59                // a 'handleMessage' queue as an object tuple{msg: msg, callback: callback}
60
61                if(!this._manager){
62                        try{
63                                this._manager = google.gears.factory.create("beta.workerpool", "1.0");
64                                this._unemployed = [];
65                                this._employed ={};
66                                this._handleMessage = [];
67               
68                                var self = this;
69                                this._manager.onmessage = function(msg, sender){
70                                        // get the callback necessary to serve this result
71                                        var callback = self._employed["_" + sender];
72                       
73                                        // make this worker unemployed
74                                        self._employed["_" + sender] = undefined;
75                                        self._unemployed.push("_" + sender);
76                       
77                                        // see if we need to assign new work
78                                        // that was queued up needing to be done
79                                        if(self._handleMessage.length){
80                                                var handleMe = self._handleMessage.shift();
81                                                self._assignWork(handleMe.msg, handleMe.callback);
82                                        }
83                       
84                                        // return results
85                                        callback(msg);
86                                }
87                       
88                                var workerInit = "function _workerInit(){"
89                                                                        + "gearsWorkerPool.onmessage = "
90                                                                                + String(this._workerHandler)
91                                                                        + ";"
92                                                                + "}";
93               
94                                var code = workerInit + " _workerInit();";
95
96                                // create our worker pool
97                                for(var i = 0; i < this._POOL_SIZE; i++){
98                                        this._unemployed.push("_" + this._manager.createWorker(code));
99                                }
100                        }catch(exp){
101                                throw exp.message||exp;
102                        }
103                }
104        },
105
106        _assignWork: function(msg, callback){
107                // can we immediately assign this work?
108                if(!this._handleMessage.length && this._unemployed.length){
109                        // get an unemployed worker
110                        var workerID = this._unemployed.shift().substring(1); // remove _
111       
112                        // list this worker as employed
113                        this._employed["_" + workerID] = callback;
114       
115                        // do the worke
116                        this._manager.sendMessage(msg, parseInt(workerID,10));
117                }else{
118                        // we have to queue it up
119                        this._handleMessage ={msg: msg, callback: callback};
120                }
121        },
122
123        _workerHandler: function(msg, sender){
124       
125                /* Begin AES Implementation */
126       
127                /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
128       
129                // Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [§5.1.1]
130                var Sbox =      [0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
131                                         0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
132                                         0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
133                                         0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
134                                         0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
135                                         0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
136                                         0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
137                                         0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
138                                         0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
139                                         0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
140                                         0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
141                                         0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
142                                         0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
143                                         0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
144                                         0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
145                                         0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16];
146
147                // Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [§5.2]
148                var Rcon = [ [0x00, 0x00, 0x00, 0x00],
149                                         [0x01, 0x00, 0x00, 0x00],
150                                         [0x02, 0x00, 0x00, 0x00],
151                                         [0x04, 0x00, 0x00, 0x00],
152                                         [0x08, 0x00, 0x00, 0x00],
153                                         [0x10, 0x00, 0x00, 0x00],
154                                         [0x20, 0x00, 0x00, 0x00],
155                                         [0x40, 0x00, 0x00, 0x00],
156                                         [0x80, 0x00, 0x00, 0x00],
157                                         [0x1b, 0x00, 0x00, 0x00],
158                                         [0x36, 0x00, 0x00, 0x00] ];
159
160                /*
161                 * AES Cipher function: encrypt 'input' with Rijndael algorithm
162                 *
163                 *       takes   byte-array 'input' (16 bytes)
164                 *                       2D byte-array key schedule 'w' (Nr+1 x Nb bytes)
165                 *
166                 *       applies Nr rounds (10/12/14) using key schedule w for 'add round key' stage
167                 *
168                 *       returns byte-array encrypted value (16 bytes)
169                 */
170                function Cipher(input, w) {        // main Cipher function [§5.1]
171                  var Nb = 4;                           // block size (in words): no of columns in state (fixed at 4 for AES)
172                  var Nr = w.length/Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys
173
174                  var state = [[],[],[],[]];  // initialise 4xNb byte-array 'state' with input [§3.4]
175                  for (var i=0; i<4*Nb; i++) state[i%4][Math.floor(i/4)] = input[i];
176
177                  state = AddRoundKey(state, w, 0, Nb);
178
179                  for (var round=1; round<Nr; round++) {
180                        state = SubBytes(state, Nb);
181                        state = ShiftRows(state, Nb);
182                        state = MixColumns(state, Nb);
183                        state = AddRoundKey(state, w, round, Nb);
184                  }
185
186                  state = SubBytes(state, Nb);
187                  state = ShiftRows(state, Nb);
188                  state = AddRoundKey(state, w, Nr, Nb);
189
190                  var output = new Array(4*Nb);  // convert state to 1-d array before returning [§3.4]
191                  for (var i=0; i<4*Nb; i++) output[i] = state[i%4][Math.floor(i/4)];
192                  return output;
193                }
194
195
196                function SubBytes(s, Nb) {        // apply SBox to state S [§5.1.1]
197                  for (var r=0; r<4; r++) {
198                        for (var c=0; c<Nb; c++) s[r][c] = Sbox[s[r][c]];
199                  }
200                  return s;
201                }
202
203
204                function ShiftRows(s, Nb) {        // shift row r of state S left by r bytes [§5.1.2]
205                  var t = new Array(4);
206                  for (var r=1; r<4; r++) {
207                        for (var c=0; c<4; c++) t[c] = s[r][(c+r)%Nb];  // shift into temp copy
208                        for (var c=0; c<4; c++) s[r][c] = t[c];                 // and copy back
209                  }                      // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):
210                  return s;      // see fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf
211                }
212
213
214                function MixColumns(s, Nb) {   // combine bytes of each col of state S [§5.1.3]
215                  for (var c=0; c<4; c++) {
216                        var a = new Array(4);  // 'a' is a copy of the current column from 's'
217                        var b = new Array(4);  // 'b' is a•{02} in GF(2^8)
218                        for (var i=0; i<4; i++) {
219                          a[i] = s[i][c];
220                          b[i] = s[i][c]&0x80 ? s[i][c]<<1 ^ 0x011b : s[i][c]<<1;
221                        }
222                        // a[n] ^ b[n] is a•{03} in GF(2^8)
223                        s[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; // 2*a0 + 3*a1 + a2 + a3
224                        s[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; // a0 * 2*a1 + 3*a2 + a3
225                        s[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; // a0 + a1 + 2*a2 + 3*a3
226                        s[3][c] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; // 3*a0 + a1 + a2 + 2*a3
227                  }
228                  return s;
229                }
230
231
232                function AddRoundKey(state, w, rnd, Nb) {  // xor Round Key into state S [§5.1.4]
233                  for (var r=0; r<4; r++) {
234                        for (var c=0; c<Nb; c++) state[r][c] ^= w[rnd*4+c][r];
235                  }
236                  return state;
237                }
238
239
240                function KeyExpansion(key) {  // generate Key Schedule (byte-array Nr+1 x Nb) from Key [§5.2]
241                  var Nb = 4;                    // block size (in words): no of columns in state (fixed at 4 for AES)
242                  var Nk = key.length/4  // key length (in words): 4/6/8 for 128/192/256-bit keys
243                  var Nr = Nk + 6;               // no of rounds: 10/12/14 for 128/192/256-bit keys
244
245                  var w = new Array(Nb*(Nr+1));
246                  var temp = new Array(4);
247
248                  for (var i=0; i<Nk; i++) {
249                        var r = [key[4*i], key[4*i+1], key[4*i+2], key[4*i+3]];
250                        w[i] = r;
251                  }
252
253                  for (var i=Nk; i<(Nb*(Nr+1)); i++) {
254                        w[i] = new Array(4);
255                        for (var t=0; t<4; t++) temp[t] = w[i-1][t];
256                        if (i % Nk == 0) {
257                          temp = SubWord(RotWord(temp));
258                          for (var t=0; t<4; t++) temp[t] ^= Rcon[i/Nk][t];
259                        } else if (Nk > 6 && i%Nk == 4) {
260                          temp = SubWord(temp);
261                        }
262                        for (var t=0; t<4; t++) w[i][t] = w[i-Nk][t] ^ temp[t];
263                  }
264
265                  return w;
266                }
267
268                function SubWord(w) {    // apply SBox to 4-byte word w
269                  for (var i=0; i<4; i++) w[i] = Sbox[w[i]];
270                  return w;
271                }
272
273                function RotWord(w) {    // rotate 4-byte word w left by one byte
274                  w[4] = w[0];
275                  for (var i=0; i<4; i++) w[i] = w[i+1];
276                  return w;
277                }
278
279                /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
280
281                /*
282                 * Use AES to encrypt 'plaintext' with 'password' using 'nBits' key, in 'Counter' mode of operation
283                 *                                                       - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
284                 *       for each block
285                 *       - outputblock = cipher(counter, key)
286                 *       - cipherblock = plaintext xor outputblock
287                 */
288                function AESEncryptCtr(plaintext, password, nBits) {
289                  if (!(nBits==128 || nBits==192 || nBits==256)) return '';      // standard allows 128/192/256 bit keys
290
291                  // for this example script, generate the key by applying Cipher to 1st 16/24/32 chars of password;
292                  // for real-world applications, a more secure approach would be to hash the password e.g. with SHA-1
293                  var nBytes = nBits/8;  // no bytes in key
294                  var pwBytes = new Array(nBytes);
295                  for (var i=0; i<nBytes; i++) pwBytes[i] = password.charCodeAt(i) & 0xff;
296
297                  var key = Cipher(pwBytes, KeyExpansion(pwBytes));
298
299                  key = key.concat(key.slice(0, nBytes-16));  // key is now 16/24/32 bytes long
300
301                  // initialise counter block (NIST SP800-38A §B.2): millisecond time-stamp for nonce in 1st 8 bytes,
302                  // block counter in 2nd 8 bytes
303                  var blockSize = 16;  // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
304                  var counterBlock = new Array(blockSize);      // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
305                  var nonce = (new Date()).getTime();  // milliseconds since 1-Jan-1970
306
307                  // encode nonce in two stages to cater for JavaScript 32-bit limit on bitwise ops
308                  for (var i=0; i<4; i++) counterBlock[i] = (nonce >>> i*8) & 0xff;
309                  for (var i=0; i<4; i++) counterBlock[i+4] = (nonce/0x100000000 >>> i*8) & 0xff;
310
311                  // generate key schedule - an expansion of the key into distinct Key Rounds for each round
312                  var keySchedule = KeyExpansion(key);
313
314                  var blockCount = Math.ceil(plaintext.length/blockSize);
315                  var ciphertext = new Array(blockCount);  // ciphertext as array of strings
316 
317                  for (var b=0; b<blockCount; b++) {
318                        // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
319                        // again done in two stages for 32-bit ops
320                        for (var c=0; c<4; c++) counterBlock[15-c] = (b >>> c*8) & 0xff;
321                        for (var c=0; c<4; c++) counterBlock[15-c-4] = (b/0x100000000 >>> c*8)
322
323                        var cipherCntr = Cipher(counterBlock, keySchedule);      // -- encrypt counter block --
324
325                        // calculate length of final block:
326                        var blockLength = b<blockCount-1 ? blockSize : (plaintext.length-1)%blockSize+1;
327
328                        var ct = '';
329                        for (var i=0; i<blockLength; i++) {      // -- xor plaintext with ciphered counter byte-by-byte --
330                          var plaintextByte = plaintext.charCodeAt(b*blockSize+i);
331                          var cipherByte = plaintextByte ^ cipherCntr[i];
332                          ct += String.fromCharCode(cipherByte);
333                        }
334                        // ct is now ciphertext for this block
335
336                        ciphertext[b] = escCtrlChars(ct);  // escape troublesome characters in ciphertext
337                  }
338
339                  // convert the nonce to a string to go on the front of the ciphertext
340                  var ctrTxt = '';
341                  for (var i=0; i<8; i++) ctrTxt += String.fromCharCode(counterBlock[i]);
342                  ctrTxt = escCtrlChars(ctrTxt);
343
344                  // use '-' to separate blocks, use Array.join to concatenate arrays of strings for efficiency
345                  return ctrTxt + '-' + ciphertext.join('-');
346                }
347
348
349                /*
350                 * Use AES to decrypt 'ciphertext' with 'password' using 'nBits' key, in Counter mode of operation
351                 *
352                 *       for each block
353                 *       - outputblock = cipher(counter, key)
354                 *       - cipherblock = plaintext xor outputblock
355                 */
356                function AESDecryptCtr(ciphertext, password, nBits) {
357                  if (!(nBits==128 || nBits==192 || nBits==256)) return '';      // standard allows 128/192/256 bit keys
358
359                  var nBytes = nBits/8;  // no bytes in key
360                  var pwBytes = new Array(nBytes);
361                  for (var i=0; i<nBytes; i++) pwBytes[i] = password.charCodeAt(i) & 0xff;
362                  var pwKeySchedule = KeyExpansion(pwBytes);
363                  var key = Cipher(pwBytes, pwKeySchedule);
364                  key = key.concat(key.slice(0, nBytes-16));  // key is now 16/24/32 bytes long
365
366                  var keySchedule = KeyExpansion(key);
367
368                  ciphertext = ciphertext.split('-');  // split ciphertext into array of block-length strings
369
370                  // recover nonce from 1st element of ciphertext
371                  var blockSize = 16;  // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
372                  var counterBlock = new Array(blockSize);
373                  var ctrTxt = unescCtrlChars(ciphertext[0]);
374                  for (var i=0; i<8; i++) counterBlock[i] = ctrTxt.charCodeAt(i);
375
376                  var plaintext = new Array(ciphertext.length-1);
377
378                  for (var b=1; b<ciphertext.length; b++) {
379                        // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
380                        for (var c=0; c<4; c++) counterBlock[15-c] = ((b-1) >>> c*8) & 0xff;
381                        for (var c=0; c<4; c++) counterBlock[15-c-4] = ((b/0x100000000-1) >>> c*8) & 0xff;
382
383                        var cipherCntr = Cipher(counterBlock, keySchedule);      // encrypt counter block
384
385                        ciphertext[b] = unescCtrlChars(ciphertext[b]);
386
387                        var pt = '';
388                        for (var i=0; i<ciphertext[b].length; i++) {
389                          // -- xor plaintext with ciphered counter byte-by-byte --
390                          var ciphertextByte = ciphertext[b].charCodeAt(i);
391                          var plaintextByte = ciphertextByte ^ cipherCntr[i];
392                          pt += String.fromCharCode(plaintextByte);
393                        }
394                        // pt is now plaintext for this block
395
396                        plaintext[b-1] = pt;  // b-1 'cos no initial nonce block in plaintext
397                  }
398
399                  return plaintext.join('');
400                }
401
402                /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
403
404                function escCtrlChars(str) {  // escape control chars which might cause problems handling ciphertext
405                  return str.replace(/[\0\t\n\v\f\r\xa0!-]/g, function(c) { return '!' + c.charCodeAt(0) + '!'; });
406                }  // \xa0 to cater for bug in Firefox; include '-' to leave it free for use as a block marker
407
408                function unescCtrlChars(str) {  // unescape potentially problematic control characters
409                  return str.replace(/!\d\d?\d?!/g, function(c) { return String.fromCharCode(c.slice(1,-1)); });
410                }
411
412                /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
413       
414                function encrypt(plaintext, password){
415                        return AESEncryptCtr(plaintext, password, 256);
416                }
417
418                function decrypt(ciphertext, password){
419                        return AESDecryptCtr(ciphertext, password, 256);
420                }
421       
422                /* End AES Implementation */
423       
424                var cmd = msg.substr(0,4);
425                var arg = msg.substr(5);
426                if(cmd == "encr"){
427                        arg = eval("(" + arg + ")");
428                        var plaintext = arg.plaintext;
429                        var password = arg.password;
430                        var results = encrypt(plaintext, password);
431                        gearsWorkerPool.sendMessage(String(results), sender);
432                }else if(cmd == "decr"){
433                        arg = eval("(" + arg + ")");
434                        var ciphertext = arg.ciphertext;
435                        var password = arg.password;
436                        var results = decrypt(ciphertext, password);
437                        gearsWorkerPool.sendMessage(String(results), sender);
438                }
439        }
440});
Note: See TracBrowser for help on using the repository browser.