18ec2e0b by David LaPalomento

Merge pull request #540 from 'BrandonOCasey-split-decrypter-files' into development

Split decrypter.js into smaller files.
2 parents 97d703e4 d2d07241
1 /* 1 /*
2 * aes.js
2 * 3 *
3 * This file contains an adaptation of the AES decryption algorithm 4 * This file contains an adaptation of the AES decryption algorithm
4 * from the Standford Javascript Cryptography Library. That work is 5 * from the Standford Javascript Cryptography Library. That work is
...@@ -35,22 +36,6 @@ ...@@ -35,22 +36,6 @@
35 * are those of the authors and should not be interpreted as representing 36 * are those of the authors and should not be interpreted as representing
36 * official policies, either expressed or implied, of the authors. 37 * official policies, either expressed or implied, of the authors.
37 */ 38 */
38 import Stream from './stream';
39 import {unpad} from 'pkcs7';
40
41 /**
42 * Convert network-order (big-endian) bytes into their little-endian
43 * representation.
44 */
45 const ntoh = function(word) {
46 return (word << 24) |
47 ((word & 0xff00) << 8) |
48 ((word & 0xff0000) >> 8) |
49 (word >>> 24);
50 };
51
52
53 let aesTables;
54 39
55 /** 40 /**
56 * Schedule out an AES key for both encryption and decryption. This 41 * Schedule out an AES key for both encryption and decryption. This
...@@ -59,7 +44,7 @@ let aesTables; ...@@ -59,7 +44,7 @@ let aesTables;
59 * @constructor 44 * @constructor
60 * @param key {Array} The key as an array of 4, 6 or 8 words. 45 * @param key {Array} The key as an array of 4, 6 or 8 words.
61 */ 46 */
62 class AES { 47 export default class AES {
63 constructor(key) { 48 constructor(key) {
64 /** 49 /**
65 * The expanded S-box and inverse S-box tables. These will be computed 50 * The expanded S-box and inverse S-box tables. These will be computed
...@@ -127,7 +112,6 @@ class AES { ...@@ -127,7 +112,6 @@ class AES {
127 } 112 }
128 } 113 }
129 114
130
131 /** 115 /**
132 * Expand the S-box tables. 116 * Expand the S-box tables.
133 * 117 *
...@@ -255,161 +239,4 @@ class AES { ...@@ -255,161 +239,4 @@ class AES {
255 a2 = a; a = b; b = c; c = d; d = a2; 239 a2 = a; a = b; b = c; c = d; d = a2;
256 } 240 }
257 } 241 }
258
259 } 242 }
260
261 /* eslint-disable max-len */
262 /**
263 * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
264 * @param encrypted {Uint8Array} the encrypted bytes
265 * @param key {Uint32Array} the bytes of the decryption key
266 * @param initVector {Uint32Array} the initialization vector (IV) to
267 * use for the first round of CBC.
268 * @return {Uint8Array} the decrypted bytes
269 *
270 * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
271 * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
272 * @see https://tools.ietf.org/html/rfc2315
273 */
274 /* eslint-enable max-len */
275 export const decrypt = function(encrypted, key, initVector) {
276 // word-level access to the encrypted bytes
277 let encrypted32 = new Int32Array(encrypted.buffer,
278 encrypted.byteOffset,
279 encrypted.byteLength >> 2);
280
281 let decipher = new AES(Array.prototype.slice.call(key));
282
283 // byte and word-level access for the decrypted output
284 let decrypted = new Uint8Array(encrypted.byteLength);
285 let decrypted32 = new Int32Array(decrypted.buffer);
286
287 // temporary variables for working with the IV, encrypted, and
288 // decrypted data
289 let init0;
290 let init1;
291 let init2;
292 let init3;
293 let encrypted0;
294 let encrypted1;
295 let encrypted2;
296 let encrypted3;
297
298 // iteration variable
299 let wordIx;
300
301 // pull out the words of the IV to ensure we don't modify the
302 // passed-in reference and easier access
303 init0 = initVector[0];
304 init1 = initVector[1];
305 init2 = initVector[2];
306 init3 = initVector[3];
307
308 // decrypt four word sequences, applying cipher-block chaining (CBC)
309 // to each decrypted block
310 for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
311 // convert big-endian (network order) words into little-endian
312 // (javascript order)
313 encrypted0 = ntoh(encrypted32[wordIx]);
314 encrypted1 = ntoh(encrypted32[wordIx + 1]);
315 encrypted2 = ntoh(encrypted32[wordIx + 2]);
316 encrypted3 = ntoh(encrypted32[wordIx + 3]);
317
318 // decrypt the block
319 decipher.decrypt(encrypted0,
320 encrypted1,
321 encrypted2,
322 encrypted3,
323 decrypted32,
324 wordIx);
325
326 // XOR with the IV, and restore network byte-order to obtain the
327 // plaintext
328 decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
329 decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
330 decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
331 decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3);
332
333 // setup the IV for the next round
334 init0 = encrypted0;
335 init1 = encrypted1;
336 init2 = encrypted2;
337 init3 = encrypted3;
338 }
339
340 return decrypted;
341 };
342
343 export class AsyncStream extends Stream {
344 constructor() {
345 super(Stream);
346 this.jobs = [];
347 this.delay = 1;
348 this.timeout_ = null;
349 }
350 processJob_() {
351 this.jobs.shift()();
352 if (this.jobs.length) {
353 this.timeout_ = setTimeout(this.processJob_.bind(this),
354 this.delay);
355 } else {
356 this.timeout_ = null;
357 }
358 }
359 push(job) {
360 this.jobs.push(job);
361 if (!this.timeout_) {
362 this.timeout_ = setTimeout(this.processJob_.bind(this),
363 this.delay);
364 }
365 }
366 }
367
368 export class Decrypter {
369 constructor(encrypted, key, initVector, done) {
370 let step = Decrypter.STEP;
371 let encrypted32 = new Int32Array(encrypted.buffer);
372 let decrypted = new Uint8Array(encrypted.byteLength);
373 let i = 0;
374
375 this.asyncStream_ = new AsyncStream();
376
377 // split up the encryption job and do the individual chunks asynchronously
378 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step),
379 key,
380 initVector,
381 decrypted));
382 for (i = step; i < encrypted32.length; i += step) {
383 initVector = new Uint32Array([ntoh(encrypted32[i - 4]),
384 ntoh(encrypted32[i - 3]),
385 ntoh(encrypted32[i - 2]),
386 ntoh(encrypted32[i - 1])]);
387 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step),
388 key,
389 initVector,
390 decrypted));
391 }
392 // invoke the done() callback when everything is finished
393 this.asyncStream_.push(function() {
394 // remove pkcs#7 padding from the decrypted bytes
395 done(null, unpad(decrypted));
396 });
397 }
398 decryptChunk_(encrypted, key, initVector, decrypted) {
399 return function() {
400 let bytes = decrypt(encrypted, key, initVector);
401
402 decrypted.set(bytes, encrypted.byteOffset);
403 };
404 }
405 }
406
407 // the maximum number of bytes to process at one time
408 // 4 * 8000;
409 Decrypter.STEP = 32000;
410
411 export default {
412 decrypt,
413 Decrypter,
414 AsyncStream
415 };
......
1 import Stream from '../stream';
2
3 /**
4 * A wrapper around the Stream class to use setTiemout
5 * and run stream "jobs" Asynchronously
6 */
7 export default class AsyncStream extends Stream {
8 constructor() {
9 super(Stream);
10 this.jobs = [];
11 this.delay = 1;
12 this.timeout_ = null;
13 }
14 processJob_() {
15 this.jobs.shift()();
16 if (this.jobs.length) {
17 this.timeout_ = setTimeout(this.processJob_.bind(this),
18 this.delay);
19 } else {
20 this.timeout_ = null;
21 }
22 }
23 push(job) {
24 this.jobs.push(job);
25 if (!this.timeout_) {
26 this.timeout_ = setTimeout(this.processJob_.bind(this),
27 this.delay);
28 }
29 }
30 }
31
1 /*
2 * decrypter.js
3 *
4 * An asynchronous implementation of AES-128 CBC decryption with
5 * PKCS#7 padding.
6 */
7
8 import AES from './aes';
9 import AsyncStream from './async-stream';
10 import {unpad} from 'pkcs7';
11
12 /**
13 * Convert network-order (big-endian) bytes into their little-endian
14 * representation.
15 */
16 const ntoh = function(word) {
17 return (word << 24) |
18 ((word & 0xff00) << 8) |
19 ((word & 0xff0000) >> 8) |
20 (word >>> 24);
21 };
22
23 /* eslint-disable max-len */
24 /**
25 * Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
26 * @param encrypted {Uint8Array} the encrypted bytes
27 * @param key {Uint32Array} the bytes of the decryption key
28 * @param initVector {Uint32Array} the initialization vector (IV) to
29 * use for the first round of CBC.
30 * @return {Uint8Array} the decrypted bytes
31 *
32 * @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
33 * @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
34 * @see https://tools.ietf.org/html/rfc2315
35 */
36 /* eslint-enable max-len */
37 export const decrypt = function(encrypted, key, initVector) {
38 // word-level access to the encrypted bytes
39 let encrypted32 = new Int32Array(encrypted.buffer,
40 encrypted.byteOffset,
41 encrypted.byteLength >> 2);
42
43 let decipher = new AES(Array.prototype.slice.call(key));
44
45 // byte and word-level access for the decrypted output
46 let decrypted = new Uint8Array(encrypted.byteLength);
47 let decrypted32 = new Int32Array(decrypted.buffer);
48
49 // temporary variables for working with the IV, encrypted, and
50 // decrypted data
51 let init0;
52 let init1;
53 let init2;
54 let init3;
55 let encrypted0;
56 let encrypted1;
57 let encrypted2;
58 let encrypted3;
59
60 // iteration variable
61 let wordIx;
62
63 // pull out the words of the IV to ensure we don't modify the
64 // passed-in reference and easier access
65 init0 = initVector[0];
66 init1 = initVector[1];
67 init2 = initVector[2];
68 init3 = initVector[3];
69
70 // decrypt four word sequences, applying cipher-block chaining (CBC)
71 // to each decrypted block
72 for (wordIx = 0; wordIx < encrypted32.length; wordIx += 4) {
73 // convert big-endian (network order) words into little-endian
74 // (javascript order)
75 encrypted0 = ntoh(encrypted32[wordIx]);
76 encrypted1 = ntoh(encrypted32[wordIx + 1]);
77 encrypted2 = ntoh(encrypted32[wordIx + 2]);
78 encrypted3 = ntoh(encrypted32[wordIx + 3]);
79
80 // decrypt the block
81 decipher.decrypt(encrypted0,
82 encrypted1,
83 encrypted2,
84 encrypted3,
85 decrypted32,
86 wordIx);
87
88 // XOR with the IV, and restore network byte-order to obtain the
89 // plaintext
90 decrypted32[wordIx] = ntoh(decrypted32[wordIx] ^ init0);
91 decrypted32[wordIx + 1] = ntoh(decrypted32[wordIx + 1] ^ init1);
92 decrypted32[wordIx + 2] = ntoh(decrypted32[wordIx + 2] ^ init2);
93 decrypted32[wordIx + 3] = ntoh(decrypted32[wordIx + 3] ^ init3);
94
95 // setup the IV for the next round
96 init0 = encrypted0;
97 init1 = encrypted1;
98 init2 = encrypted2;
99 init3 = encrypted3;
100 }
101
102 return decrypted;
103 };
104
105 /**
106 * The `Decrypter` class that manages decryption of AES
107 * data through `AsyncStream` objects and the `decrypt`
108 * function
109 */
110 export class Decrypter {
111 constructor(encrypted, key, initVector, done) {
112 let step = Decrypter.STEP;
113 let encrypted32 = new Int32Array(encrypted.buffer);
114 let decrypted = new Uint8Array(encrypted.byteLength);
115 let i = 0;
116
117 this.asyncStream_ = new AsyncStream();
118
119 // split up the encryption job and do the individual chunks asynchronously
120 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step),
121 key,
122 initVector,
123 decrypted));
124 for (i = step; i < encrypted32.length; i += step) {
125 initVector = new Uint32Array([ntoh(encrypted32[i - 4]),
126 ntoh(encrypted32[i - 3]),
127 ntoh(encrypted32[i - 2]),
128 ntoh(encrypted32[i - 1])]);
129 this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step),
130 key,
131 initVector,
132 decrypted));
133 }
134 // invoke the done() callback when everything is finished
135 this.asyncStream_.push(function() {
136 // remove pkcs#7 padding from the decrypted bytes
137 done(null, unpad(decrypted));
138 });
139 }
140 decryptChunk_(encrypted, key, initVector, decrypted) {
141 return function() {
142 let bytes = decrypt(encrypted, key, initVector);
143
144 decrypted.set(bytes, encrypted.byteOffset);
145 };
146 }
147 }
148
149 // the maximum number of bytes to process at one time
150 // 4 * 8000;
151 Decrypter.STEP = 32000;
152
153 export default {
154 Decrypter,
155 decrypt
156 };
1 /*
2 * index.js
3 *
4 * Index module to easily import the primary components of AES-128
5 * decryption. Like this:
6 *
7 * ```js
8 * import {Decrypter, decrypt, AsyncStream} from './src/decrypter';
9 * ```
10 */
11 import {decrypt, Decrypter} from './decrypter';
12 import AsyncStream from './async-stream';
13
14 export default {
15 decrypt,
16 Decrypter,
17 AsyncStream
18 };