source: Dev/branches/rest-dojo-ui/client/dojox/encoding/crypto/SimpleAES.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: 13.5 KB
Line 
1define(["../base64", "./_base"],
2 function(base64, crypto){
3
4        /*=====
5                crypto = dojox.encoding.crypto;
6        =====*/
7
8        // Sbox is pre-computed multiplicative inverse in GF(2^8) used in SubBytes and KeyExpansion [®ª5.1.1]
9        var Sbox =      [0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76,
10                                 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0,
11                                 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15,
12                                 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75,
13                                 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84,
14                                 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf,
15                                 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8,
16                                 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2,
17                                 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73,
18                                 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb,
19                                 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79,
20                                 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08,
21                                 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a,
22                                 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e,
23                                 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf,
24                                 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16];
25
26        // Rcon is Round Constant used for the Key Expansion [1st col is 2^(r-1) in GF(2^8)] [®ª5.2]
27        var Rcon = [ [0x00, 0x00, 0x00, 0x00],
28                                 [0x01, 0x00, 0x00, 0x00],
29                                 [0x02, 0x00, 0x00, 0x00],
30                                 [0x04, 0x00, 0x00, 0x00],
31                                 [0x08, 0x00, 0x00, 0x00],
32                                 [0x10, 0x00, 0x00, 0x00],
33                                 [0x20, 0x00, 0x00, 0x00],
34                                 [0x40, 0x00, 0x00, 0x00],
35                                 [0x80, 0x00, 0x00, 0x00],
36                                 [0x1b, 0x00, 0x00, 0x00],
37                                 [0x36, 0x00, 0x00, 0x00] ];
38
39        /*
40         * AES Cipher function: encrypt 'input' with Rijndael algorithm
41         *
42         *       takes   byte-array 'input' (16 bytes)
43         *                       2D byte-array key schedule 'w' (Nr+1 x Nb bytes)
44         *
45         *       applies Nr rounds (10/12/14) using key schedule w for 'add round key' stage
46         *
47         *       returns byte-array encrypted value (16 bytes)
48         */
49        function Cipher(input, w) {        // main Cipher function [®ª5.1]
50          var Nb = 4;                           // block size (in words): no of columns in state (fixed at 4 for AES)
51          var Nr = w.length/Nb - 1; // no of rounds: 10/12/14 for 128/192/256-bit keys
52
53          var state = [[],[],[],[]];  // initialise 4xNb byte-array 'state' with input [®ª3.4]
54          for (var i=0; i<4*Nb; i++) state[i%4][Math.floor(i/4)] = input[i];
55
56          state = AddRoundKey(state, w, 0, Nb);
57
58          for (var round=1; round<Nr; round++) {
59                state = SubBytes(state, Nb);
60                state = ShiftRows(state, Nb);
61                state = MixColumns(state, Nb);
62                state = AddRoundKey(state, w, round, Nb);
63          }
64
65          state = SubBytes(state, Nb);
66          state = ShiftRows(state, Nb);
67          state = AddRoundKey(state, w, Nr, Nb);
68
69          var output = new Array(4*Nb);  // convert state to 1-d array before returning [®ª3.4]
70          for (var i=0; i<4*Nb; i++) output[i] = state[i%4][Math.floor(i/4)];
71          return output;
72        }
73
74
75        function SubBytes(s, Nb) {        // apply SBox to state S [®ª5.1.1]
76          for (var r=0; r<4; r++) {
77                for (var c=0; c<Nb; c++) s[r][c] = Sbox[s[r][c]];
78          }
79          return s;
80        }
81
82
83        function ShiftRows(s, Nb) {        // shift row r of state S left by r bytes [®ª5.1.2]
84          var t = new Array(4);
85          for (var r=1; r<4; r++) {
86                for (var c=0; c<4; c++) t[c] = s[r][(c+r)%Nb];  // shift into temp copy
87                for (var c=0; c<4; c++) s[r][c] = t[c];                 // and copy back
88          }                      // note that this will work for Nb=4,5,6, but not 7,8 (always 4 for AES):
89          return s;      // see fp.gladman.plus.com/cryptography_technology/rijndael/aes.spec.311.pdf
90        }
91
92
93        function MixColumns(s, Nb) {   // combine bytes of each col of state S [®ª5.1.3]
94          for (var c=0; c<4; c++) {
95                var a = new Array(4);  // 'a' is a copy of the current column from 's'
96                var b = new Array(4);  // 'b' is a‰Þ{02} in GF(2^8)
97                for (var i=0; i<4; i++) {
98                  a[i] = s[i][c];
99                  b[i] = s[i][c]&0x80 ? s[i][c]<<1 ^ 0x011b : s[i][c]<<1;
100                }
101                // a[n] ^ b[n] is a‰Þ{03} in GF(2^8)
102                s[0][c] = b[0] ^ a[1] ^ b[1] ^ a[2] ^ a[3]; // 2*a0 + 3*a1 + a2 + a3
103                s[1][c] = a[0] ^ b[1] ^ a[2] ^ b[2] ^ a[3]; // a0 * 2*a1 + 3*a2 + a3
104                s[2][c] = a[0] ^ a[1] ^ b[2] ^ a[3] ^ b[3]; // a0 + a1 + 2*a2 + 3*a3
105                s[3][c] = a[0] ^ b[0] ^ a[1] ^ a[2] ^ b[3]; // 3*a0 + a1 + a2 + 2*a3
106          }
107          return s;
108        }
109
110
111        function AddRoundKey(state, w, rnd, Nb) {  // xor Round Key into state S [®ª5.1.4]
112          for (var r=0; r<4; r++) {
113                for (var c=0; c<Nb; c++) state[r][c] ^= w[rnd*4+c][r];
114          }
115          return state;
116        }
117
118
119        function KeyExpansion(key) {  // generate Key Schedule (byte-array Nr+1 x Nb) from Key [®ª5.2]
120          var Nb = 4;                    // block size (in words): no of columns in state (fixed at 4 for AES)
121          var Nk = key.length/4  // key length (in words): 4/6/8 for 128/192/256-bit keys
122          var Nr = Nk + 6;               // no of rounds: 10/12/14 for 128/192/256-bit keys
123
124          var w = new Array(Nb*(Nr+1));
125          var temp = new Array(4);
126
127          for (var i=0; i<Nk; i++) {
128                var r = [key[4*i], key[4*i+1], key[4*i+2], key[4*i+3]];
129                w[i] = r;
130          }
131
132          for (var i=Nk; i<(Nb*(Nr+1)); i++) {
133                w[i] = new Array(4);
134                for (var t=0; t<4; t++) temp[t] = w[i-1][t];
135                if (i % Nk == 0) {
136                  temp = SubWord(RotWord(temp));
137                  for (var t=0; t<4; t++) temp[t] ^= Rcon[i/Nk][t];
138                } else if (Nk > 6 && i%Nk == 4) {
139                  temp = SubWord(temp);
140                }
141                for (var t=0; t<4; t++) w[i][t] = w[i-Nk][t] ^ temp[t];
142          }
143
144          return w;
145        }
146
147        function SubWord(w) {    // apply SBox to 4-byte word w
148          for (var i=0; i<4; i++) w[i] = Sbox[w[i]];
149          return w;
150        }
151
152        function RotWord(w) {    // rotate 4-byte word w left by one byte
153          w[4] = w[0];
154          for (var i=0; i<4; i++) w[i] = w[i+1];
155          return w;
156        }
157
158        /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
159
160        /*
161         * Use AES to encrypt 'plaintext' with 'password' using 'nBits' key, in 'Counter' mode of operation
162         *                                                       - see http://csrc.nist.gov/publications/nistpubs/800-38a/sp800-38a.pdf
163         *       for each block
164         *       - outputblock = cipher(counter, key)
165         *       - cipherblock = plaintext xor outputblock
166         */
167        function AESEncryptCtr(plaintext, password, nBits) {
168          if (!(nBits==128 || nBits==192 || nBits==256)) return '';      // standard allows 128/192/256 bit keys
169
170          // for this example script, generate the key by applying Cipher to 1st 16/24/32 chars of password;
171          // for real-world applications, a more secure approach would be to hash the password e.g. with SHA-1
172          var nBytes = nBits/8;  // no bytes in key
173          var pwBytes = new Array(nBytes);
174          for (var i=0; i<nBytes; i++) pwBytes[i] = password.charCodeAt(i) & 0xff;
175
176          var key = Cipher(pwBytes, KeyExpansion(pwBytes));
177
178          key = key.concat(key.slice(0, nBytes-16));  // key is now 16/24/32 bytes long
179
180          // initialise counter block (NIST SP800-38A ®ªB.2): millisecond time-stamp for nonce in 1st 8 bytes,
181          // block counter in 2nd 8 bytes
182          var blockSize = 16;  // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
183          var counterBlock = new Array(blockSize);      // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
184          var nonce = (new Date()).getTime();  // milliseconds since 1-Jan-1970
185
186          // encode nonce in two stages to cater for JavaScript 32-bit limit on bitwise ops
187          for (var i=0; i<4; i++) counterBlock[i] = (nonce >>> i*8) & 0xff;
188          for (var i=0; i<4; i++) counterBlock[i+4] = (nonce/0x100000000 >>> i*8) & 0xff;
189
190          // generate key schedule - an expansion of the key into distinct Key Rounds for each round
191          var keySchedule = KeyExpansion(key);
192
193          var blockCount = Math.ceil(plaintext.length/blockSize);
194          var ciphertext = new Array(blockCount);  // ciphertext as array of strings
195
196          for (var b=0; b<blockCount; b++) {
197                // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
198                // again done in two stages for 32-bit ops
199                for (var c=0; c<4; c++) counterBlock[15-c] = (b >>> c*8) & 0xff;
200                for (var c=0; c<4; c++) counterBlock[15-c-4] = (b/0x100000000 >>> c*8)
201
202                var cipherCntr = Cipher(counterBlock, keySchedule);      // -- encrypt counter block --
203
204                // calculate length of final block:
205                var blockLength = b<blockCount-1 ? blockSize : (plaintext.length-1)%blockSize+1;
206
207                var ct = '';
208                for (var i=0; i<blockLength; i++) {      // -- xor plaintext with ciphered counter byte-by-byte --
209                  var plaintextByte = plaintext.charCodeAt(b*blockSize+i);
210                  var cipherByte = plaintextByte ^ cipherCntr[i];
211                  //ct += String.fromCharCode(cipherByte);
212                  ct += ((cipherByte < 16) ? "0" : "") + cipherByte.toString(16);
213                }
214                // ct is now ciphertext for this block
215
216                ciphertext[b] = ct; // escCtrlChars(ct);  // escape troublesome characters in ciphertext
217          }
218
219          // convert the nonce to a string to go on the front of the ciphertext
220          var ctrTxt = '';
221          for (var i=0; i<8; i++) ctrTxt += ((counterBlock[i] < 16) ? "0" : "") + counterBlock[i].toString(16); //String.fromCharCode(counterBlock[i]);
222          //ctrTxt = escCtrlChars(ctrTxt);
223
224          // use '-' to separate blocks, use Array.join to concatenate arrays of strings for efficiency
225          return ctrTxt + ' ' + ciphertext.join(' ');
226        }
227
228        function stringToHex(s){
229                var ret = [];
230                s.replace(/(..)/g, function(str){
231                        ret.push(parseInt(str, 16));
232                });
233                return ret;
234        }
235
236        /*
237         * Use AES to decrypt 'ciphertext' with 'password' using 'nBits' key, in Counter mode of operation
238         *
239         *       for each block
240         *       - outputblock = cipher(counter, key)
241         *       - cipherblock = plaintext xor outputblock
242         */
243        function AESDecryptCtr(ciphertext, password, nBits) {
244          if (!(nBits==128 || nBits==192 || nBits==256)) return '';      // standard allows 128/192/256 bit keys
245
246          var nBytes = nBits/8;  // no bytes in key
247          var pwBytes = new Array(nBytes);
248          for (var i=0; i<nBytes; i++) pwBytes[i] = password.charCodeAt(i) & 0xff;
249          var pwKeySchedule = KeyExpansion(pwBytes);
250          var key = Cipher(pwBytes, pwKeySchedule);
251          key = key.concat(key.slice(0, nBytes-16));  // key is now 16/24/32 bytes long
252
253          var keySchedule = KeyExpansion(key);
254
255          ciphertext = ciphertext.split(' ');  // split ciphertext into array of block-length strings
256
257          // recover nonce from 1st element of ciphertext
258          var blockSize = 16;  // block size fixed at 16 bytes / 128 bits (Nb=4) for AES
259          var counterBlock = new Array(blockSize);
260          var ctrTxt = ciphertext[0]; //unescCtrlChars(ciphertext[0]);
261          counterBlock = stringToHex(ctrTxt);
262
263          var plaintext = new Array(ciphertext.length-1);
264
265          for (var b=1; b<ciphertext.length; b++) {
266                // set counter (block #) in last 8 bytes of counter block (leaving nonce in 1st 8 bytes)
267                for (var c=0; c<4; c++) counterBlock[15-c] = ((b-1) >>> c*8) & 0xff;
268                for (var c=0; c<4; c++) counterBlock[15-c-4] = ((b/0x100000000-1) >>> c*8) & 0xff;
269
270                var cipherCntr = Cipher(counterBlock, keySchedule);      // encrypt counter block
271
272                //ciphertext[b] = ciphertext[b]; //unescCtrlChars(ciphertext[b]);
273
274                var pt = '';
275                var tmp = stringToHex(ciphertext[b]);
276                for (var i=0; i<tmp.length; i++) {
277                  // -- xor plaintext with ciphered counter byte-by-byte --
278                  var ciphertextByte = ciphertext[b].charCodeAt(i);
279                  var plaintextByte = tmp[i] ^ cipherCntr[i];
280                  pt += String.fromCharCode(plaintextByte);
281                }
282                // pt is now plaintext for this block
283
284                plaintext[b-1] = pt;  // b-1 'cos no initial nonce block in plaintext
285          }
286
287          return plaintext.join('');
288        }
289
290        /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
291
292        function escCtrlChars(str) {  // escape control chars which might cause problems handling ciphertext
293          return str.replace(/[\0\t\n\v\f\r\xa0!-]/g, function(c) { return '!' + c.charCodeAt(0) + '!'; });
294        }  // \xa0 to cater for bug in Firefox; include '-' to leave it free for use as a block marker
295
296        function unescCtrlChars(str) {  // unescape potentially problematic control characters
297          return str.replace(/!\d\d?\d?!/g, function(c) { return String.fromCharCode(c.slice(1,-1)); });
298        }
299
300        /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
301
302        crypto.SimpleAES = new (function(){
303                // summary:
304                //              SimpleAES, ported from dojox.sql, and done without the need for
305                //              a Google Gears worker pool.
306                // description:
307                //              Taken from http://www.movable-type.co.uk/scripts/aes.html by
308                //              Chris Veness (CLA signed); adapted for Dojo by Brad Neuberg
309                //              (bkn3 AT columbia.edu) and moved to DojoX crypto by Tom Trenka
310                //              (ttrenka AT gmail.com).
311                //
312                //              A few notes:
313                //              1) This algorithm uses a customized version of CBC mode by creating
314                //              a nonce, using it as an initialization vector, and storing the
315                //              IV as the first portion of the encrypted text.  Because of this, it
316                //              is HIGHLY PROBABLE that it will NOT be usable by other AES implementations.
317                //              2) All encoding is done in hex format; other encoding formats (such
318                //              as base 64) are not supported.
319                //              3) The bit depth of the key is hardcoded at 256, despite the ability
320                //              of the code to handle all three recommended bit depths.
321                //              4) The passed key will be padded (as opposed to enforcing a strict
322                //              length) with null bytes.
323                this.encrypt = function(/* String */plaintext, /* String */key){
324                        //      summary:
325                        //              Encrypt the passed plaintext using the key, with a
326                        //              hardcoded bit depth of 256.
327                        return AESEncryptCtr(plaintext, key, 256);      //      String
328                };
329                this.decrypt = function(/* String */ciphertext, /* String */key){
330                        //      summary:
331                        //              Decrypt the passed ciphertext using the key at a fixed
332                        //              bit depth of 256.
333                        return AESDecryptCtr(ciphertext, key, 256);     //      String
334                };
335        })();
336
337        return crypto.SimpleAES;
338});
Note: See TracBrowser for help on using the repository browser.