68cfe65a by David LaPalomento

Decrypt multiple blocks with cipher block chaining

Generalize the decryption function to use the initialization vector and decrypt multiple four-word sequences using cipher block chaining. Add a utility function to convert ASCII bytes to strings to clean up the test cases a bit.
1 parent fa251c38
...@@ -52,9 +52,7 @@ var AES, decrypt; ...@@ -52,9 +52,7 @@ var AES, decrypt;
52 * @param key {Array} The key as an array of 4, 6 or 8 words. 52 * @param key {Array} The key as an array of 4, 6 or 8 words.
53 */ 53 */
54 AES = function (key) { 54 AES = function (key) {
55 if (!this._tables[0][0][0]) {
56 this._precompute(); 55 this._precompute();
57 }
58 56
59 var i, j, tmp, 57 var i, j, tmp,
60 encKey, decKey, 58 encKey, decKey,
...@@ -212,27 +210,40 @@ AES.prototype = { ...@@ -212,27 +210,40 @@ AES.prototype = {
212 } 210 }
213 }; 211 };
214 212
215 decrypt = function(encrypted, key) { 213 decrypt = function(encrypted, key, initVector) {
216 var 214 var
217 view = new DataView(encrypted.buffer), 215 encryptedView = new DataView(encrypted.buffer),
218 decrypted = new AES(key) 216 platformEndian = new Uint32Array(encrypted.byteLength / 4),
217 decipher = new AES(key),
218 decrypted = new Uint8Array(encrypted.byteLength),
219 decryptedView = new DataView(decrypted.buffer),
220 decryptedBlock,
221 word, byte;
222
219 // convert big-endian input to platform byte order for decryption 223 // convert big-endian input to platform byte order for decryption
220 .decrypt(new Uint32Array([ 224 for (byte = 0; byte < encrypted.byteLength; byte += 4) {
221 view.getUint32(0), 225 platformEndian[byte >>> 2] = encryptedView.getUint32(byte);
222 view.getUint32(4), 226 }
223 view.getUint32(8), 227 // decrypt four word sequences, applying cipher-block chaining (CBC)
224 view.getUint32(12) 228 // to each decrypted block
225 ])), 229 for (word = 0; word < platformEndian.length; word += 4) {
226 bytes = new Uint8Array(encrypted.byteLength); 230 // decrypt the block
227 231 decryptedBlock = decipher.decrypt(platformEndian.subarray(word, word + 4));
228 // convert platform byte order back to big-endian for unpadding 232
229 view = new DataView(bytes.buffer); 233 // XOR with the IV, and restore network byte-order to obtain the
230 view.setUint32(0, decrypted[0]); 234 // plaintext
231 view.setUint32(4, decrypted[1]); 235 byte = word << 2;
232 view.setUint32(8, decrypted[2]); 236 decryptedView.setUint32(byte, decryptedBlock[0] ^ initVector[0]);
233 view.setUint32(12, decrypted[3]); 237 decryptedView.setUint32(byte + 4, decryptedBlock[1] ^ initVector[1]);
234 238 decryptedView.setUint32(byte + 8, decryptedBlock[2] ^ initVector[2]);
235 return unpad(bytes); 239 decryptedView.setUint32(byte + 12, decryptedBlock[3] ^ initVector[3]);
240
241 // setup the IV for the next round
242 initVector = platformEndian.subarray(word, word + 4);
243 }
244
245 // remove any padding
246 return unpad(decrypted);
236 }; 247 };
237 248
238 // exports 249 // exports
......
...@@ -23,37 +23,53 @@ ...@@ -23,37 +23,53 @@
23 23
24 // see docs/hlse.md for instructions on how test data was generated 24 // see docs/hlse.md for instructions on how test data was generated
25 25
26 var stringFromBytes = function(bytes) {
27 var result = '', i;
28
29 for (i = 0; i < bytes.length; i++) {
30 result += String.fromCharCode(bytes[i]);
31 }
32 return result;
33 };
34
26 module('Decryption'); 35 module('Decryption');
27 36
28 test('decrypts using AES-128 CBC with PKCS7', function() { 37 test('decrypts a single AES-128 with PKCS7 block', function() {
29 // the string "howdy folks" with key and initialization
30 // vector
31 var 38 var
32 key = [0, 0, 0, 0], 39 key = [0, 0, 0, 0],
33 initVector = key, 40 initVector = key,
41 // the string "howdy folks" encrypted
34 encrypted = new Uint8Array([ 42 encrypted = new Uint8Array([
35 0xce, 0x90, 0x97, 0xd0, 43 0xce, 0x90, 0x97, 0xd0,
36 0x08, 0x46, 0x4d, 0x18, 44 0x08, 0x46, 0x4d, 0x18,
37 0x4f, 0xae, 0x01, 0x1c, 45 0x4f, 0xae, 0x01, 0x1c,
38 0x82, 0xa8, 0xf0, 0x67]), 46 0x82, 0xa8, 0xf0, 0x67]);
39 length = 'howdy folks'.length,
40 plaintext = new Uint8Array(length),
41 i;
42
43 i = length;
44 while (i--) {
45 plaintext[i] = 'howdy folks'.charCodeAt(i);
46 }
47 47
48 // decrypt works on the sjcl example site 48 deepEqual('howdy folks',
49 // correct output: [1752135524, 2032166511, 1818981125, 84215045] 49 stringFromBytes(videojs.hls.decrypt(encrypted, key, initVector)),
50
51 deepEqual(plaintext,
52 new Uint8Array(videojs.hls.decrypt(encrypted, key, initVector)),
53 'decrypted with a numeric key');
54 deepEqual(plaintext,
55 new Uint8Array(videojs.hls.decrypt(encrypted, key, initVector)),
56 'decrypted with a byte array key'); 50 'decrypted with a byte array key');
57 }); 51 });
58 52
53 test('decrypts multiple AES-128 blocks with CBC', function() {
54 var
55 key = [0, 0, 0, 0],
56 initVector = key,
57 // the string "0123456789abcdef01234" encrypted
58 encrypted = new Uint8Array([
59 0x14, 0xf5, 0xfe, 0x74,
60 0x69, 0x66, 0xf2, 0x92,
61 0x65, 0x1c, 0x22, 0x88,
62 0xbb, 0xff, 0x46, 0x09,
63
64 0x0b, 0xde, 0x5e, 0x71,
65 0x77, 0x87, 0xeb, 0x84,
66 0xa9, 0x54, 0xc2, 0x45,
67 0xe9, 0x4e, 0x29, 0xb3
68 ]);
69
70 deepEqual('0123456789abcdef01234',
71 stringFromBytes(videojs.hls.decrypt(encrypted, key, initVector)),
72 'decrypted multiple blocks');
73 });
74
59 })(window, window.videojs); 75 })(window, window.videojs);
......