使用 AES 和自定义密钥使用 WebCrypto API 加密和解密视频
Encrypt and decrypt a video with WebCrypto API using AES and a custom key
我想知道如何使用 AES 和自定义密钥使用 WebCrypto API 加密和解密视频。我只找到这段代码,只说明如何加密视频而不说明如何解密,也使用随机密钥。提前谢谢你。
function processFile(evt) {
var file = evt.target.files[0],
reader = new FileReader();
reader.onload = function(e) {
var data = e.target.result,
iv = crypto.getRandomValues(new Uint8Array(16));
crypto.subtle.generateKey({ 'name': 'AES-CBC', 'length': 256 }, false, ['encrypt', 'decrypt'])
.then(key => crypto.subtle.encrypt({ 'name': 'AES-CBC', iv }, key, data) )
.then(encrypted => {
console.log(encrypted);
alert('The encrypted data is ' + encrypted.byteLength + ' bytes long'); // encrypted is an ArrayBuffer
})
.catch(console.error);
}
reader.readAsArrayBuffer(file);
}
您将在此处找到有关如何使用 AES-GCM 生成密钥、导入密钥、加密和解密的完整示例:
https://github.com/diafygi/webcrypto-examples/blob/master/README.md#aes-gcm
您应该使用 GCM,因为它是一种经过身份验证的加密模式。 WebCrypto 没有流接口,因此您必须分块处理,否则非常直接。
您可能想使用 ECDH 来交换 AES 密钥。同一页面也有相关示例。
这里有一个使用 aes-256-cbc 解密和播放 HLS 视频的演示:
https://kaizhu256.github.io/node-demo-hls-encrypted/index.html
它是通过在 hls.js (https://github.com/video-dev/hls.js/blob/v0.8.9/dist/hls.js) 中破解 ajax-call 来解密 xhr.response 然后再传递给视频播放来实现的:
--- assets.hls.v0.8.9.js 2018-08-04 03:59:42.000000000 +0700
+++ assets.hls.v0.8.9.crypto.js 2018-08-04 03:59:42.000000000 +0700
@@ -1,3 +1,97 @@
+var local;
+(function () {
+ (function () {
+ local = local || {};
+ local.base64ToBuffer = function (b64, mode) {
+ /*
+ * this function will convert b64 to Uint8Array
+ * https://gist.github.com/wang-bin/7332335
+ */
+ /*globals Uint8Array*/
+ var bff, byte, chr, ii, jj, map64, mod4;
+ b64 = b64 || '';
+ bff = new Uint8Array(b64.length); // 3/4
+ byte = 0;
+ jj = 0;
+ map64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+ mod4 = 0;
+ for (ii = 0; ii < b64.length; ii += 1) {
+ chr = map64.indexOf(b64[ii]);
+ if (chr >= 0) {
+ mod4 %= 4;
+ if (mod4 === 0) {
+ byte = chr;
+ } else {
+ byte = byte * 64 + chr;
+ bff[jj] = 255 & (byte >> ((-2 * (mod4 + 1)) & 6));
+ jj += 1;
+ }
+ mod4 += 1;
+ }
+ }
+ // optimization - create resized-view of bff
+ bff = bff.subarray(0, jj);
+ // mode !== 'string'
+ if (mode !== 'string') {
+ return bff;
+ }
+ // mode === 'string' - browser js-env
+ if (typeof window === 'object' && window && typeof window.TextDecoder === 'function') {
+ return new window.TextDecoder().decode(bff);
+ }
+ // mode === 'string' - node js-env
+ Object.setPrototypeOf(bff, Buffer.prototype);
+ return String(bff);
+ };
+ local.cryptoAes256CbcByteDecrypt = function (key, data, onError, mode) {
+ /*
+ * this function will aes-256-cbc decrypt with the hex-key, Uint8Array data
+ * example usage:
+ key = '0000000000000000000000000000000000000000000000000000000000000000';
+ local.cryptoAes256CbcByteEncrypt(key, new Uint8Array([1,2,3]), function (error, data) {
+ console.assert(!error, error);
+ local.cryptoAes256CbcByteDecrypt(key, data, console.log);
+ });
+ */
+ /*globals Uint8Array*/
+ var cipher, crypto, ii, iv, tmp;
+ // init key
+ tmp = key;
+ key = new Uint8Array(32);
+ for (ii = 0; ii < key.length; ii += 2) {
+ key[ii] = parseInt(tmp.slice(2 * ii, 2 * ii + 2), 16);
+ }
+ // base64
+ if (mode === 'base64') {
+ data = local.base64ToBuffer(data);
+ }
+ if (!(data instanceof Uint8Array)) {
+ data = new Uint8Array(data);
+ }
+ // init iv
+ iv = data.subarray(0, 16);
+ // optimization - create resized-view of data
+ data = data.subarray(16);
+ crypto = typeof window === 'object' && window.crypto;
+ /* istanbul ignore next */
+ if (!(crypto && crypto.subtle && typeof crypto.subtle.importKey === 'function')) {
+ setTimeout(function () {
+ crypto = require('crypto');
+ cipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
+ onError(null, Buffer.concat([cipher.update(data), cipher.final()]));
+ });
+ return;
+ }
+ crypto.subtle.importKey('raw', key, {
+ name: 'AES-CBC'
+ }, false, ['decrypt']).then(function (key) {
+ crypto.subtle.decrypt({ iv: iv, name: 'AES-CBC' }, key, data).then(function (data) {
+ onError(null, new Uint8Array(data));
+ }).catch(onError);
+ }).catch(onError);
+ };
+ }());
+}());
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
@@ -10919,6 +11013,22 @@
}
stats.loaded = stats.total = len;
var response = { url: xhr.responseURL, data: data };
+ if (data && window.modeMediaEncrypted && window.mediaEncryptedKey) {
+ var self = this;
+ local.cryptoAes256CbcByteDecrypt(
+ window.mediaEncryptedKey,
+ data,
+ function (error, data) {
+ response.data = typeof xhr.response === 'string'
+ ? new TextDecoder().decode(data)
+ : data;
+ stats.loaded = stats.total = data.byteLength;
+ self.callbacks.onSuccess(response, stats, context, xhr);
+ },
+ typeof xhr.response === 'string' && 'base64'
+ );
+ return;
+ }
this.callbacks.onSuccess(response, stats, context, xhr);
} else {
// if max nb of retries reached or if http status between 400 and 499 (such error cannot be recovered, retrying is useless), return error
我想知道如何使用 AES 和自定义密钥使用 WebCrypto API 加密和解密视频。我只找到这段代码,只说明如何加密视频而不说明如何解密,也使用随机密钥。提前谢谢你。
function processFile(evt) {
var file = evt.target.files[0],
reader = new FileReader();
reader.onload = function(e) {
var data = e.target.result,
iv = crypto.getRandomValues(new Uint8Array(16));
crypto.subtle.generateKey({ 'name': 'AES-CBC', 'length': 256 }, false, ['encrypt', 'decrypt'])
.then(key => crypto.subtle.encrypt({ 'name': 'AES-CBC', iv }, key, data) )
.then(encrypted => {
console.log(encrypted);
alert('The encrypted data is ' + encrypted.byteLength + ' bytes long'); // encrypted is an ArrayBuffer
})
.catch(console.error);
}
reader.readAsArrayBuffer(file);
}
您将在此处找到有关如何使用 AES-GCM 生成密钥、导入密钥、加密和解密的完整示例: https://github.com/diafygi/webcrypto-examples/blob/master/README.md#aes-gcm
您应该使用 GCM,因为它是一种经过身份验证的加密模式。 WebCrypto 没有流接口,因此您必须分块处理,否则非常直接。
您可能想使用 ECDH 来交换 AES 密钥。同一页面也有相关示例。
这里有一个使用 aes-256-cbc 解密和播放 HLS 视频的演示:
https://kaizhu256.github.io/node-demo-hls-encrypted/index.html
它是通过在 hls.js (https://github.com/video-dev/hls.js/blob/v0.8.9/dist/hls.js) 中破解 ajax-call 来解密 xhr.response 然后再传递给视频播放来实现的:
--- assets.hls.v0.8.9.js 2018-08-04 03:59:42.000000000 +0700
+++ assets.hls.v0.8.9.crypto.js 2018-08-04 03:59:42.000000000 +0700
@@ -1,3 +1,97 @@
+var local;
+(function () {
+ (function () {
+ local = local || {};
+ local.base64ToBuffer = function (b64, mode) {
+ /*
+ * this function will convert b64 to Uint8Array
+ * https://gist.github.com/wang-bin/7332335
+ */
+ /*globals Uint8Array*/
+ var bff, byte, chr, ii, jj, map64, mod4;
+ b64 = b64 || '';
+ bff = new Uint8Array(b64.length); // 3/4
+ byte = 0;
+ jj = 0;
+ map64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+ mod4 = 0;
+ for (ii = 0; ii < b64.length; ii += 1) {
+ chr = map64.indexOf(b64[ii]);
+ if (chr >= 0) {
+ mod4 %= 4;
+ if (mod4 === 0) {
+ byte = chr;
+ } else {
+ byte = byte * 64 + chr;
+ bff[jj] = 255 & (byte >> ((-2 * (mod4 + 1)) & 6));
+ jj += 1;
+ }
+ mod4 += 1;
+ }
+ }
+ // optimization - create resized-view of bff
+ bff = bff.subarray(0, jj);
+ // mode !== 'string'
+ if (mode !== 'string') {
+ return bff;
+ }
+ // mode === 'string' - browser js-env
+ if (typeof window === 'object' && window && typeof window.TextDecoder === 'function') {
+ return new window.TextDecoder().decode(bff);
+ }
+ // mode === 'string' - node js-env
+ Object.setPrototypeOf(bff, Buffer.prototype);
+ return String(bff);
+ };
+ local.cryptoAes256CbcByteDecrypt = function (key, data, onError, mode) {
+ /*
+ * this function will aes-256-cbc decrypt with the hex-key, Uint8Array data
+ * example usage:
+ key = '0000000000000000000000000000000000000000000000000000000000000000';
+ local.cryptoAes256CbcByteEncrypt(key, new Uint8Array([1,2,3]), function (error, data) {
+ console.assert(!error, error);
+ local.cryptoAes256CbcByteDecrypt(key, data, console.log);
+ });
+ */
+ /*globals Uint8Array*/
+ var cipher, crypto, ii, iv, tmp;
+ // init key
+ tmp = key;
+ key = new Uint8Array(32);
+ for (ii = 0; ii < key.length; ii += 2) {
+ key[ii] = parseInt(tmp.slice(2 * ii, 2 * ii + 2), 16);
+ }
+ // base64
+ if (mode === 'base64') {
+ data = local.base64ToBuffer(data);
+ }
+ if (!(data instanceof Uint8Array)) {
+ data = new Uint8Array(data);
+ }
+ // init iv
+ iv = data.subarray(0, 16);
+ // optimization - create resized-view of data
+ data = data.subarray(16);
+ crypto = typeof window === 'object' && window.crypto;
+ /* istanbul ignore next */
+ if (!(crypto && crypto.subtle && typeof crypto.subtle.importKey === 'function')) {
+ setTimeout(function () {
+ crypto = require('crypto');
+ cipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
+ onError(null, Buffer.concat([cipher.update(data), cipher.final()]));
+ });
+ return;
+ }
+ crypto.subtle.importKey('raw', key, {
+ name: 'AES-CBC'
+ }, false, ['decrypt']).then(function (key) {
+ crypto.subtle.decrypt({ iv: iv, name: 'AES-CBC' }, key, data).then(function (data) {
+ onError(null, new Uint8Array(data));
+ }).catch(onError);
+ }).catch(onError);
+ };
+ }());
+}());
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory();
@@ -10919,6 +11013,22 @@
}
stats.loaded = stats.total = len;
var response = { url: xhr.responseURL, data: data };
+ if (data && window.modeMediaEncrypted && window.mediaEncryptedKey) {
+ var self = this;
+ local.cryptoAes256CbcByteDecrypt(
+ window.mediaEncryptedKey,
+ data,
+ function (error, data) {
+ response.data = typeof xhr.response === 'string'
+ ? new TextDecoder().decode(data)
+ : data;
+ stats.loaded = stats.total = data.byteLength;
+ self.callbacks.onSuccess(response, stats, context, xhr);
+ },
+ typeof xhr.response === 'string' && 'base64'
+ );
+ return;
+ }
this.callbacks.onSuccess(response, stats, context, xhr);
} else {
// if max nb of retries reached or if http status between 400 and 499 (such error cannot be recovered, retrying is useless), return error