Merge pull request #540 from 'BrandonOCasey-split-decrypter-files' into development
Split decrypter.js into smaller files.
Showing
4 changed files
with
207 additions
and
175 deletions
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 | } | ||
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 | } | 242 | } |
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 | }; | ... | ... |
src/decrypter/async-stream.js
0 → 100644
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 |
src/decrypter/decrypter.js
0 → 100644
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 | }; |
src/decrypter/index.js
0 → 100644
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 | }; |
-
Please register or sign in to post a comment