Skip to content

Commit

Permalink
Use webcrypto for aes-cbc segment decryption when supported (#4)
Browse files Browse the repository at this point in the history
If webcrypto is supported, the spec requires Promises to be available as
well, so we use Promises in that branch. Otherwise it is the same
SJCL-based Decrypter implementation as before, just slightly
reorganized.
  • Loading branch information
dconnolly authored and brandonocasey committed Jul 12, 2016
1 parent d4e1e26 commit 22bde06
Show file tree
Hide file tree
Showing 2 changed files with 258 additions and 73 deletions.
119 changes: 110 additions & 9 deletions src/decrypter.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const ntoh = function(word) {
/**
* Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
*
* @param {Uint8Array} encrypted the encrypted bytes
* @param {Uint32Array} encrypted the encrypted bytes
* @param {Uint32Array} key the bytes of the decryption key
* @param {Uint32Array} initVector the initialization vector (IV) to
* use for the first round of CBC.
Expand All @@ -33,7 +33,7 @@ const ntoh = function(word) {
* @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
* @see https://tools.ietf.org/html/rfc2315
*/
export const decrypt = function(encrypted, key, initVector) {
const decryptNonNative = function(encrypted, key, initVector) {
// word-level access to the encrypted bytes
let encrypted32 = new Int32Array(encrypted.buffer,
encrypted.byteOffset,
Expand Down Expand Up @@ -107,13 +107,20 @@ export const decrypt = function(encrypted, key, initVector) {
* function
*
* @param {Uint8Array} encrypted the encrypted bytes
* @param {Uint32Array} key the bytes of the decryption key
* @param {Uint8Array} key the bytes of the decryption key
* @param {Uint32Array} initVector the initialization vector (IV) to
* @param {Function} done the function to run when done
* @class Decrypter
*/
export class Decrypter {
constructor(encrypted, key, initVector, done) {
let view = new DataView(key.buffer);
let littleEndianKey = new Uint32Array([
view.getUint32(0),
view.getUint32(4),
view.getUint32(8),
view.getUint32(12)
]);
let step = Decrypter.STEP;
let encrypted32 = new Int32Array(encrypted.buffer);
let decrypted = new Uint8Array(encrypted.byteLength);
Expand All @@ -123,7 +130,7 @@ export class Decrypter {

// split up the encryption job and do the individual chunks asynchronously
this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step),
key,
littleEndianKey,
initVector,
decrypted));
for (i = step; i < encrypted32.length; i += step) {
Expand All @@ -132,7 +139,7 @@ export class Decrypter {
ntoh(encrypted32[i - 2]),
ntoh(encrypted32[i - 1])]);
this.asyncStream_.push(this.decryptChunk_(encrypted32.subarray(i, i + step),
key,
littleEndianKey,
initVector,
decrypted));
}
Expand All @@ -158,14 +165,108 @@ export class Decrypter {
*/
decryptChunk_(encrypted, key, initVector, decrypted) {
return function() {
let bytes = decrypt(encrypted, key, initVector);
// decryptNonNative must be a separate function (not a method or
// static method on Decrypter) else IE10 will crash.
let bytes = decryptNonNative(encrypted, key, initVector);

decrypted.set(bytes, encrypted.byteOffset);
};
}
}

export default {
Decrypter,
decrypt
/**
* Get a consistent crypto.subtle across various browsers.
*
* @return {WebCrypto Object}
*/
const getWebCrypto = function() {
// IE11 uses this prefix, but with an out of date version of the
// spec that doesn't use Promises, and doesn't have native Promises
// either, thus we fall back to the non-native decryption. Edge is
// up to spec.
if (window.msCrypto) {
return null;
}

let _crypto = window ? window.crypto : crypto;

if (!_crypto) {
return null;
}

// We shouldn't need to worry about Safari (which does HLSe
// natively) but we use this for completeness.
if (_crypto.webkitSubtle) {
_crypto.subtle = _crypto.webkitSubtle;
}

return _crypto.subtle ? _crypto : null;
};

/**
* Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
*
* @param {Uint8Array} encrypted the encrypted bytes
* @param {Uint8Array} key the bytes of the decryption key
* @param {Uint32Array} iv the initialization vector (IV) to
* use for the first round of CBC.
* @param {Function} done callback that takes a Uint8Array
*
* @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
* @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
* @see https://tools.ietf.org/html/rfc2315
*/
const decryptWithWebCrypto = function(encrypted, key, iv, done) {
let crypto = getWebCrypto();
let algorithm = {name: 'AES-CBC', iv};
let extractable = true;
let usages = ['decrypt'];

let keyPromise = crypto.subtle.importKey('raw', key, algorithm, extractable, usages);

return keyPromise.then(function(importedKey) {
return crypto.subtle.decrypt(algorithm, importedKey, encrypted);
}).catch(function(rejection) {
return done(null, new Uint8Array());
}).then(function(plaintextArrayBuffer) {
return done(null, new Uint8Array(plaintextArrayBuffer));
});
};

/**
* Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
*
* @param {Uint8Array} encrypted the encrypted bytes
* @param {Uint8Array} key the bytes of the decryption key
* @param {Uint32Array} iv the initialization vector (IV) to
* use for the first round of CBC.
* @return {Promise} that resolves with a Uint8Array the decrypted bytes
*
* @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
* @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
* @see https://tools.ietf.org/html/rfc2315
*/
const decryptWithDecrypter = function(encrypted, key, iv, done) {
return new Decrypter(encrypted, key, iv, done);
};

/**
* Decrypt bytes using AES-128 with CBC and PKCS#7 padding.
* Chooses webcrypto or JS implementation where available.
*
* @param {Uint8Array} encrypted: the encrypted bytes
* @param {Uint8Array} key: the bytes of the decryption key
* @param {Uint32Array} iv: the initialization vector (IV) to
* use for the first round of CBC.
*
* @see http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
* @see http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_Block_Chaining_.28CBC.29
* @see https://tools.ietf.org/html/rfc2315
*/
export const decrypt = function(encrypted, key, iv, done) {
let decryptionMethod = getWebCrypto() ? decryptWithWebCrypto : decryptWithDecrypter;

return decryptionMethod(encrypted, key, iv, done);
};

export default decrypt;
Loading

0 comments on commit 22bde06

Please sign in to comment.