fa251c38 by David LaPalomento

AES-128 decryption

Implement the block level AES decipher and include the routine to do PKCS#7 unpadding.
1 parent 1d78b907
...@@ -11,3 +11,39 @@ distributed under the License is distributed on an "AS IS" BASIS, ...@@ -11,3 +11,39 @@ distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and 12 See the License for the specific language governing permissions and
13 limitations under the License. 13 limitations under the License.
14
15 The AES decryption implementation in this project is derived from the
16 Stanford Javascript Cryptography Library
17 (http://bitwiseshiftleft.github.io/sjcl/). That work is covered by the
18 following copyright and permission notice:
19
20 Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
21 All rights reserved.
22
23 Redistribution and use in source and binary forms, with or without
24 modification, are permitted provided that the following conditions are
25 met:
26
27 1. Redistributions of source code must retain the above copyright
28 notice, this list of conditions and the following disclaimer.
29
30 2. Redistributions in binary form must reproduce the above
31 copyright notice, this list of conditions and the following
32 disclaimer in the documentation and/or other materials provided
33 with the distribution.
34
35 THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
36 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
37 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
38 DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
39 LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
40 CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
41 SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
42 BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
43 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
44 OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
45 IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
46
47 The views and conclusions contained in the software and documentation
48 are those of the authors and should not be interpreted as representing
49 official policies, either expressed or implied, of the authors.
...\ No newline at end of file ...\ No newline at end of file
......
...@@ -3,15 +3,18 @@ The [HLS spec](http://tools.ietf.org/html/draft-pantos-http-live-streaming-13#se ...@@ -3,15 +3,18 @@ The [HLS spec](http://tools.ietf.org/html/draft-pantos-http-live-streaming-13#se
3 3
4 ```sh 4 ```sh
5 # encrypt the text "hello" into a file 5 # encrypt the text "hello" into a file
6 echo -n "hello" | pkcs7 | openssl enc -aes-128-cbc > hello.encrypted 6 # since this is for testing, skip the key salting so the output is stable
7 # using -nosalt outside of testing is a terrible idea!
8 echo -n "hello" | pkcs7 | \
9 openssl enc -aes-128-cbc -nopad -nosalt -k $KEY -iv $IV > hello.encrypted
7 10
8 # encrypt some text and get the bytes in a format that can be easily used for 11 # xxd is a handy way of translating binary into a format easily consumed by
9 # testing in javascript 12 # javascript
10 echo -n "hello" | pkcs7 | openssl enc -aes-128-cbc | xxd -i 13 xxd -i hello.encrypted
11 ``` 14 ```
12 15
13 Later, you can decrypt it: 16 Later, you can decrypt it:
14 17
15 ```sh 18 ```sh
16 cat hello.encrypted | openssl enc -d -aes-128-cbc 19 openssl enc -d -nopad -aes-128-cbc -k $KEY -iv $IV
17 ``` 20 ```
......
...@@ -37,6 +37,7 @@ ...@@ -37,6 +37,7 @@
37 "video.js": "^4.7.2" 37 "video.js": "^4.7.2"
38 }, 38 },
39 "dependencies": { 39 "dependencies": {
40 "pkcs7": "^0.2.2",
40 "videojs-contrib-media-sources": "^0.3.0" 41 "videojs-contrib-media-sources": "^0.3.0"
41 } 42 }
42 } 43 }
......
1 /*
2 * videojs-hls
3 *
4 * Copyright (c) 2014 Brightcove
5 * All rights reserved.
6 *
7 * This file contains an adaptation of the AES decryption algorithm
8 * from the Standford Javascript Cryptography Library. That work is
9 * covered by the following copyright and permissions notice:
10 *
11 * Copyright 2009-2010 Emily Stark, Mike Hamburg, Dan Boneh.
12 * All rights reserved.
13 *
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions are
16 * met:
17 *
18 * 1. Redistributions of source code must retain the above copyright
19 * notice, this list of conditions and the following disclaimer.
20 *
21 * 2. Redistributions in binary form must reproduce the above
22 * copyright notice, this list of conditions and the following
23 * disclaimer in the documentation and/or other materials provided
24 * with the distribution.
25 *
26 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
27 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
28 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
29 * DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> OR CONTRIBUTORS BE
30 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
31 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
32 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
33 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
34 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
35 * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
36 * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 *
38 * The views and conclusions contained in the software and documentation
39 * are those of the authors and should not be interpreted as representing
40 * official policies, either expressed or implied, of the authors.
41 */
42 (function(window, videojs, unpad) {
43 'use strict';
44
45 var AES, decrypt;
46
47 /**
48 * Schedule out an AES key for both encryption and decryption. This
49 * is a low-level class. Use a cipher mode to do bulk encryption.
50 *
51 * @constructor
52 * @param key {Array} The key as an array of 4, 6 or 8 words.
53 */
54 AES = function (key) {
55 if (!this._tables[0][0][0]) {
56 this._precompute();
57 }
58
59 var i, j, tmp,
60 encKey, decKey,
61 sbox = this._tables[0][4], decTable = this._tables[1],
62 keyLen = key.length, rcon = 1;
63
64 if (keyLen !== 4 && keyLen !== 6 && keyLen !== 8) {
65 throw new Error("Invalid aes key size");
66 }
67
68 encKey = key.slice(0);
69 decKey = [];
70 this._key = [encKey, decKey];
71
72 // schedule encryption keys
73 for (i = keyLen; i < 4 * keyLen + 28; i++) {
74 tmp = encKey[i-1];
75
76 // apply sbox
77 if (i%keyLen === 0 || (keyLen === 8 && i%keyLen === 4)) {
78 tmp = sbox[tmp>>>24]<<24 ^ sbox[tmp>>16&255]<<16 ^ sbox[tmp>>8&255]<<8 ^ sbox[tmp&255];
79
80 // shift rows and add rcon
81 if (i%keyLen === 0) {
82 tmp = tmp<<8 ^ tmp>>>24 ^ rcon<<24;
83 rcon = rcon<<1 ^ (rcon>>7)*283;
84 }
85 }
86
87 encKey[i] = encKey[i-keyLen] ^ tmp;
88 }
89
90 // schedule decryption keys
91 for (j = 0; i; j++, i--) {
92 tmp = encKey[j&3 ? i : i - 4];
93 if (i<=4 || j<4) {
94 decKey[j] = tmp;
95 } else {
96 decKey[j] = decTable[0][sbox[tmp>>>24 ]] ^
97 decTable[1][sbox[tmp>>16 & 255]] ^
98 decTable[2][sbox[tmp>>8 & 255]] ^
99 decTable[3][sbox[tmp & 255]];
100 }
101 }
102 };
103
104 AES.prototype = {
105 /**
106 * The expanded S-box and inverse S-box tables. These will be computed
107 * on the client so that we don't have to send them down the wire.
108 *
109 * There are two tables, _tables[0] is for encryption and
110 * _tables[1] is for decryption.
111 *
112 * The first 4 sub-tables are the expanded S-box with MixColumns. The
113 * last (_tables[01][4]) is the S-box itself.
114 *
115 * @private
116 */
117 _tables: [[[],[],[],[],[]],[[],[],[],[],[]]],
118
119 /**
120 * Expand the S-box tables.
121 *
122 * @private
123 */
124 _precompute: function () {
125 var encTable = this._tables[0], decTable = this._tables[1],
126 sbox = encTable[4], sboxInv = decTable[4],
127 i, x, xInv, d=[], th=[], x2, x4, x8, s, tEnc, tDec;
128
129 // Compute double and third tables
130 for (i = 0; i < 256; i++) {
131 th[( d[i] = i<<1 ^ (i>>7)*283 )^i]=i;
132 }
133
134 for (x = xInv = 0; !sbox[x]; x ^= x2 || 1, xInv = th[xInv] || 1) {
135 // Compute sbox
136 s = xInv ^ xInv<<1 ^ xInv<<2 ^ xInv<<3 ^ xInv<<4;
137 s = s>>8 ^ s&255 ^ 99;
138 sbox[x] = s;
139 sboxInv[s] = x;
140
141 // Compute MixColumns
142 x8 = d[x4 = d[x2 = d[x]]];
143 tDec = x8*0x1010101 ^ x4*0x10001 ^ x2*0x101 ^ x*0x1010100;
144 tEnc = d[s]*0x101 ^ s*0x1010100;
145
146 for (i = 0; i < 4; i++) {
147 encTable[i][x] = tEnc = tEnc<<24 ^ tEnc>>>8;
148 decTable[i][s] = tDec = tDec<<24 ^ tDec>>>8;
149 }
150 }
151
152 // Compactify. Considerable speedup on Firefox.
153 for (i = 0; i < 5; i++) {
154 encTable[i] = encTable[i].slice(0);
155 decTable[i] = decTable[i].slice(0);
156 }
157 },
158
159 /**
160 * Decrypt an array of 4 big-endian words.
161 * @param {Array} data The ciphertext.
162 * @return {Array} The plaintext.
163 */
164 decrypt:function (input) {
165 if (input.length !== 4) {
166 throw new Error("Invalid aes block size");
167 }
168
169 var key = this._key[1],
170 // state variables a,b,c,d are loaded with pre-whitened data
171 a = input[0] ^ key[0],
172 b = input[3] ^ key[1],
173 c = input[2] ^ key[2],
174 d = input[1] ^ key[3],
175 a2, b2, c2,
176
177 nInnerRounds = key.length/4 - 2,
178 i,
179 kIndex = 4,
180 out = [0,0,0,0],
181 table = this._tables[1],
182
183 // load up the tables
184 t0 = table[0],
185 t1 = table[1],
186 t2 = table[2],
187 t3 = table[3],
188 sbox = table[4];
189
190 // Inner rounds. Cribbed from OpenSSL.
191 for (i = 0; i < nInnerRounds; i++) {
192 a2 = t0[a>>>24] ^ t1[b>>16 & 255] ^ t2[c>>8 & 255] ^ t3[d & 255] ^ key[kIndex];
193 b2 = t0[b>>>24] ^ t1[c>>16 & 255] ^ t2[d>>8 & 255] ^ t3[a & 255] ^ key[kIndex + 1];
194 c2 = t0[c>>>24] ^ t1[d>>16 & 255] ^ t2[a>>8 & 255] ^ t3[b & 255] ^ key[kIndex + 2];
195 d = t0[d>>>24] ^ t1[a>>16 & 255] ^ t2[b>>8 & 255] ^ t3[c & 255] ^ key[kIndex + 3];
196 kIndex += 4;
197 a=a2; b=b2; c=c2;
198 }
199
200 // Last round.
201 for (i = 0; i < 4; i++) {
202 out[3 & -i] =
203 sbox[a>>>24 ]<<24 ^
204 sbox[b>>16 & 255]<<16 ^
205 sbox[c>>8 & 255]<<8 ^
206 sbox[d & 255] ^
207 key[kIndex++];
208 a2=a; a=b; b=c; c=d; d=a2;
209 }
210
211 return out;
212 }
213 };
214
215 decrypt = function(encrypted, key) {
216 var
217 view = new DataView(encrypted.buffer),
218 decrypted = new AES(key)
219 // convert big-endian input to platform byte order for decryption
220 .decrypt(new Uint32Array([
221 view.getUint32(0),
222 view.getUint32(4),
223 view.getUint32(8),
224 view.getUint32(12)
225 ])),
226 bytes = new Uint8Array(encrypted.byteLength);
227
228 // convert platform byte order back to big-endian for unpadding
229 view = new DataView(bytes.buffer);
230 view.setUint32(0, decrypted[0]);
231 view.setUint32(4, decrypted[1]);
232 view.setUint32(8, decrypted[2]);
233 view.setUint32(12, decrypted[3]);
234
235 return unpad(bytes);
236 };
237
238 // exports
239 videojs.hls = videojs.util.mergeOptions(videojs.hls, {
240 decrypt: decrypt
241 });
242 })(window, window.videojs, window.pkcs7.unpad);
1 (function(window, videojs, undefined) {
2 'use strict';
3 /*
4 ======== A Handy Little QUnit Reference ========
5 http://api.qunitjs.com/
6
7 Test methods:
8 module(name, {[setup][ ,teardown]})
9 test(name, callback)
10 expect(numberOfAssertions)
11 stop(increment)
12 start(decrement)
13 Test assertions:
14 ok(value, [message])
15 equal(actual, expected, [message])
16 notEqual(actual, expected, [message])
17 deepEqual(actual, expected, [message])
18 notDeepEqual(actual, expected, [message])
19 strictEqual(actual, expected, [message])
20 notStrictEqual(actual, expected, [message])
21 throws(block, [expected], [message])
22 */
23
24 // see docs/hlse.md for instructions on how test data was generated
25
26 module('Decryption');
27
28 test('decrypts using AES-128 CBC with PKCS7', function() {
29 // the string "howdy folks" with key and initialization
30 // vector
31 var
32 key = [0, 0, 0, 0],
33 initVector = key,
34 encrypted = new Uint8Array([
35 0xce, 0x90, 0x97, 0xd0,
36 0x08, 0x46, 0x4d, 0x18,
37 0x4f, 0xae, 0x01, 0x1c,
38 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
48 // decrypt works on the sjcl example site
49 // correct output: [1752135524, 2032166511, 1818981125, 84215045]
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');
57 });
58
59 })(window, window.videojs);
...@@ -77,6 +77,7 @@ module.exports = function(config) { ...@@ -77,6 +77,7 @@ module.exports = function(config) {
77 '../node_modules/sinon/lib/sinon/util/fake_timers.js', 77 '../node_modules/sinon/lib/sinon/util/fake_timers.js',
78 '../node_modules/video.js/dist/video-js/video.js', 78 '../node_modules/video.js/dist/video-js/video.js',
79 '../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js', 79 '../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js',
80 '../node_modules/pkcs7/dist/pkcs7.unpad.js',
80 '../test/karma-qunit-shim.js', 81 '../test/karma-qunit-shim.js',
81 '../src/videojs-hls.js', 82 '../src/videojs-hls.js',
82 '../src/xhr.js', 83 '../src/xhr.js',
...@@ -88,6 +89,7 @@ module.exports = function(config) { ...@@ -88,6 +89,7 @@ module.exports = function(config) {
88 '../src/stream.js', 89 '../src/stream.js',
89 '../src/m3u8/m3u8-parser.js', 90 '../src/m3u8/m3u8-parser.js',
90 '../src/playlist-loader.js', 91 '../src/playlist-loader.js',
92 '../src/decrypter.js',
91 '../tmp/manifests.js', 93 '../tmp/manifests.js',
92 '../tmp/expected.js', 94 '../tmp/expected.js',
93 'tsSegment-bc.js', 95 'tsSegment-bc.js',
......
...@@ -41,6 +41,7 @@ module.exports = function(config) { ...@@ -41,6 +41,7 @@ module.exports = function(config) {
41 '../node_modules/sinon/lib/sinon/util/fake_timers.js', 41 '../node_modules/sinon/lib/sinon/util/fake_timers.js',
42 '../node_modules/video.js/dist/video-js/video.js', 42 '../node_modules/video.js/dist/video-js/video.js',
43 '../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js', 43 '../node_modules/videojs-contrib-media-sources/src/videojs-media-sources.js',
44 '../node_modules/pkcs7/dist/pkcs7.unpad.js',
44 '../test/karma-qunit-shim.js', 45 '../test/karma-qunit-shim.js',
45 '../src/videojs-hls.js', 46 '../src/videojs-hls.js',
46 '../src/xhr.js', 47 '../src/xhr.js',
...@@ -52,6 +53,7 @@ module.exports = function(config) { ...@@ -52,6 +53,7 @@ module.exports = function(config) {
52 '../src/stream.js', 53 '../src/stream.js',
53 '../src/m3u8/m3u8-parser.js', 54 '../src/m3u8/m3u8-parser.js',
54 '../src/playlist-loader.js', 55 '../src/playlist-loader.js',
56 '../src/decrypter.js',
55 '../tmp/manifests.js', 57 '../tmp/manifests.js',
56 '../tmp/expected.js', 58 '../tmp/expected.js',
57 'tsSegment-bc.js', 59 'tsSegment-bc.js',
......
...@@ -31,6 +31,8 @@ ...@@ -31,6 +31,8 @@
31 <script src="../src/stream.js"></script> 31 <script src="../src/stream.js"></script>
32 <script src="../src/m3u8/m3u8-parser.js"></script> 32 <script src="../src/m3u8/m3u8-parser.js"></script>
33 <script src="../src/playlist-loader.js"></script> 33 <script src="../src/playlist-loader.js"></script>
34 <script src="../node_modules/pkcs7/dist/pkcs7.unpad.js"></script>
35 <script src="../src/decrypter.js"></script>
34 <!-- M3U8 TEST DATA --> 36 <!-- M3U8 TEST DATA -->
35 <script src="../tmp/manifests.js"></script> 37 <script src="../tmp/manifests.js"></script>
36 <script src="../tmp/expected.js"></script> 38 <script src="../tmp/expected.js"></script>
...@@ -55,6 +57,7 @@ ...@@ -55,6 +57,7 @@
55 <script src="flv-tag_test.js"></script> 57 <script src="flv-tag_test.js"></script>
56 <script src="m3u8_test.js"></script> 58 <script src="m3u8_test.js"></script>
57 <script src="playlist-loader_test.js"></script> 59 <script src="playlist-loader_test.js"></script>
60 <script src="decrypter_test.js"></script>
58 </head> 61 </head>
59 <body> 62 <body>
60 <div id="qunit"></div> 63 <div id="qunit"></div>
......