1 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
4 * AES Cipher function: encrypt 'input' with Rijndael algorithm
6 * takes byte-array 'input' (16 bytes)
7 * 2D byte-array key schedule 'w' (Nr+1 x Nb bytes)
9 * applies Nr rounds (10/12/14) using key schedule w for 'add round key' stage
11 * returns byte-array encrypted value (16 bytes)
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
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
];
20 state
= AddRoundKey(state
, w
, 0, Nb
);
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
);
29 state
= SubBytes(state
, Nb
);
30 state
= ShiftRows(state
, Nb
);
31 state
= AddRoundKey(state
, w
, Nr
, Nb
);
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)];
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
]];
47 function ShiftRows(s
, Nb
) { // shift row r of state S left by r bytes [§5.1.2]
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
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
++) {
63 b
[i
] = s
[i
][c
]&0x80 ? s
[i
][c
]<<1 ^ 0x011b : s
[i
][c
]<<1;
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
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
];
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
88 var w
= new Array(Nb
*(Nr
+1));
89 var temp
= new Array(4);
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]];
96 for (var i
=Nk
; i
<(Nb
*(Nr
+1)); i
++) {
98 for (var t
=0; t
<4; t
++) temp
[t
] = w
[i
-1][t
];
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
);
105 for (var t
=0; t
<4; t
++) w
[i
][t
] = w
[i
-Nk
][t
] ^ temp
[t
];
111 function SubWord(w
) { // apply SBox to 4-byte word w
112 for (var i
=0; i
<4; i
++) w
[i
] = Sbox
[w
[i
]];
116 function RotWord(w
) { // rotate 4-byte word w left by one byte
118 for (var i
=0; i
<4; i
++) w
[i
] = w
[i
+1];
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];
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] ];
155 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
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
161 * - outputblock = cipher(counter, key)
162 * - cipherblock = plaintext xor outputblock
164 function AESEncryptCtr(plaintext
, password
, nBits
) {
165 if (!(nBits
==128 || nBits
==192 || nBits
==256)) return ''; // standard allows 128/192/256 bit keys
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
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
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;
185 // generate key schedule - an expansion of the key into distinct Key Rounds for each round
186 var keySchedule
= KeyExpansion(key
);
188 var blockCount
= Math
.ceil(plaintext
.length
/blockSize
);
189 var ciphertext
= new Array(blockCount
); // ciphertext as array of strings
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)
197 var cipherCntr
= Cipher(counterBlock
, keySchedule
); // -- encrypt counter block --
199 // calculate length of final block:
200 var blockLength
= b
<blockCount
-1 ? blockSize
: (plaintext
.length
-1)%blockSize
+1;
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
);
208 // ct is now ciphertext for this block
210 ciphertext
[b
] = escCtrlChars(ct
); // escape troublesome characters in ciphertext
213 // convert the nonce to a string to go on the front of the ciphertext
215 for (var i
=0; i
<8; i
++) ctrTxt
+= String
.fromCharCode(counterBlock
[i
]);
216 ctrTxt
= escCtrlChars(ctrTxt
);
218 // use '-' to separate blocks, use Array.join to concatenate arrays of strings for efficiency
219 return ctrTxt
+ '-' + ciphertext
.join('-');
224 * Use AES to decrypt 'ciphertext' with 'password' using 'nBits' key, in Counter mode of operation
227 * - outputblock = cipher(counter, key)
228 * - cipherblock = plaintext xor outputblock
230 function AESDecryptCtr(ciphertext
, password
, nBits
) {
231 if (!(nBits
==128 || nBits
==192 || nBits
==256)) return ''; // standard allows 128/192/256 bit keys
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
240 var keySchedule
= KeyExpansion(key
);
242 ciphertext
= ciphertext
.split('-'); // split ciphertext into array of block-length strings
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
);
250 var plaintext
= new Array(ciphertext
.length
-1);
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;
257 var cipherCntr
= Cipher(counterBlock
, keySchedule
); // encrypt counter block
259 ciphertext
[b
] = unescCtrlChars(ciphertext
[b
]);
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
);
268 // pt is now plaintext for this block
270 plaintext
[b
-1] = pt
; // b-1 'cos no initial nonce block in plaintext
273 return plaintext
.join('');
276 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
278 function 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
282 function unescCtrlChars(str
) { // unescape potentially problematic control characters
283 return str
.replace(/!\d\d?\d?!/g, function(c
) { return String
.fromCharCode(c
.slice(1,-1)); });
285 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
288 * if escCtrlChars()/unescCtrlChars() still gives problems, use encodeBase64()/decodeBase64() instead
290 var b64
= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
292 function encodeBase64(str
) { // http://tools.ietf.org/html/rfc4648
293 var o1
, o2
, o3
, h1
, h2
, h3
, h4
, bits
, i
=0, enc
='';
295 str
= encodeUTF8(str
); // encode multi-byte chars into UTF-8 for byte-array
297 do { // pack three octets into four hexets
298 o1
= str
.charCodeAt(i
++);
299 o2
= str
.charCodeAt(i
++);
300 o3
= str
.charCodeAt(i
++);
302 bits
= o1
<<16 | o2
<<8 | o3
;
304 h1
= bits
>>18 & 0x3f;
305 h2
= bits
>>12 & 0x3f;
309 // end of string? index to '=' in b64
310 if (isNaN(o3
)) h4
= 64;
311 if (isNaN(o2
)) h3
= 64;
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
);
320 function decodeBase64(str
) {
321 var o1
, o2
, o3
, h1
, h2
, h3
, h4
, bits
, i
=0, enc
='';
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
++));
329 bits
= h1
<<18 | h2
<<12 | h3
<<6 | h4
;
331 o1
= bits
>>16 & 0xff;
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
);
340 return decodeUTF8(enc
); // decode UTF-8 byte-array back to Unicode
343 function encodeUTF8(str
) { // encode multi-byte string into utf-8 multiple single-byte characters
345 /[\u0080-\u07ff]/g, // U+0080 - U+07FF = 2-byte chars
347 var cc
= c
.charCodeAt(0);
348 return String
.fromCharCode(0xc0 | cc
>>6, 0x80 | cc
&0x3f); }
351 /[\u0800-\uffff]/g, // U+0800 - U+FFFF = 3-byte chars
353 var cc
= c
.charCodeAt(0);
354 return String
.fromCharCode(0xe0 | cc
>>12, 0x80 | cc
>>6&0x3F, 0x80 | cc
&0x3f); }
359 function decodeUTF8(str
) { // decode utf-8 encoded string back into multi-byte characters
361 /[\u00c0-\u00df][\u0080-\u00bf]/g, // 2-byte chars
363 var cc
= (c
.charCodeAt(0)&0x1f)<<6 | c
.charCodeAt(1)&0x3f;
364 return String
.fromCharCode(cc
); }
367 /[\u00e0-\u00ef][\u0080-\u00bf][\u0080-\u00bf]/g, // 3-byte chars
369 var cc
= (c
.charCodeAt(0)&0x0f)<<12 | (c
.charCodeAt(1)&0x3f<<6) | c
.charCodeAt(2)&0x3f;
370 return String
.fromCharCode(cc
); }
376 function byteArrayToHexStr(b
) { // convert byte array to hex string for displaying test vectors
378 for (var i
=0; i
<b
.length
; i
++) s
+= b
[i
].toString(16) + ' ';
382 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
385 var plainText
= "ROMEO: But, soft! what light through yonder window breaks?\n\
386 It is the east, and Juliet is the sun.\n\
387 Arise, fair sun, and kill the envious moon,\n\
388 Who is already sick and pale with grief,\n\
389 That thou her maid art far more fair than she:\n\
390 Be not her maid, since she is envious;\n\
391 Her vestal livery is but sick and green\n\
392 And none but fools do wear it; cast it off.\n\
393 It is my lady, O, it is my love!\n\
394 O, that she knew she were!\n\
395 She speaks yet she says nothing: what of that?\n\
396 Her eye discourses; I will answer it.\n\
397 I am too bold, 'tis not to me she speaks:\n\
398 Two of the fairest stars in all the heaven,\n\
399 Having some business, do entreat her eyes\n\
400 To twinkle in their spheres till they return.\n\
401 What if her eyes were there, they in her head?\n\
402 The brightness of her cheek would shame those stars,\n\
403 As daylight doth a lamp; her eyes in heaven\n\
404 Would through the airy region stream so bright\n\
405 That birds would sing and think it were not night.\n\
406 See, how she leans her cheek upon her hand!\n\
407 O, that I were a glove upon that hand,\n\
408 That I might touch that cheek!\n\
410 ROMEO: She speaks:\n\
411 O, speak again, bright angel! for thou art\n\
412 As glorious to this night, being o'er my head\n\
413 As is a winged messenger of heaven\n\
414 Unto the white-upturned wondering eyes\n\
415 Of mortals that fall back to gaze on him\n\
416 When he bestrides the lazy-pacing clouds\n\
417 And sails upon the bosom of the air.";
419 var password
= "O Romeo, Romeo! wherefore art thou Romeo?";
421 var cipherText
= AESEncryptCtr(plainText
, password
, 256);
422 var decryptedText
= AESDecryptCtr(cipherText
, password
, 256);