1 场景
视频采用m3u8 ts切片方式,采用切片默认的aes-128加密方式不安全,key值也是16位明文的,随便下个video.js插件就可以破解(其它播放插件也可以),让后端做来源判断或者加请求标识个人感觉安全系数也不是很大。
2 m3u8视频防盗方案
-
方式一: 后端对m3u8返回内容进行加密处理,前端请求m3u8时可以包括加SIGN验证,根据视频id加请求参数验证等,以便后端判断请求是否给你返回内容。
-
关于方式一解密(加解密方案前后端自己定即可,以下均是修改插件源码并以标明位置函数,实测前后端以调通)
//插件:video.js - 7.0.5(此版本默认不包含hls)
//处理请求添加字段证明身份
function _createXHR(options) {
...
var failureResponse = {
body: undefined,
headers: {},
statusCode: 0,
method: method,
url: uri,
rawRequest: xhr
};
// 自定义 - 开始
if (options.uri.indexOf('自定义地址') > -1) {
/**
*
* 加密算法得出加密值
*
*/
headers = { "SIGN": 加密值, 'Content-Type': 'application/x-www-form-urlencoded' };
method = "POST";
} else {
method = xhr.method = options.method || "GET";
headers = xhr.headers = options.headers || {};
}
// 自定义 - 结束
if ("json" in options && options.json !== false) {
isJson = true;
headers["accept"] || headers["Accept"] || (headers["Accept"] = "application/json"); //Don't override existing accept header declared by user
if (method !== "GET" && method !== "HEAD") {
headers["content-type"] || headers["Content-Type"] || (headers["Content-Type"] = "application/json"); //Don't override existing accept header declared by user
body = JSON.stringify(options.json === true ? body : options.json);
}
}
...
}
//插件:videojs-contrib-hls.js - 5.9.0
var handleKeyResponse = function handleKeyResponse(segment, finishProcessingFn) {
...
if (errorObj) {
return finishProcessingFn(errorObj, segment);
}
// 解密二次加密后的key - 开始
if(请求key返回的正确code值){
if (response.key !== "") {
/**
*
* 咔咔咔写自己的解密
*
*/
var view = new DataView(解密得到的值);
segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
return finishProcessingFn(null, segment);
}
}else{
// 用于初始化判断key长度
if (response.byteLength !== 16) {
return finishProcessingFn({
status: request.status,
message: 'Invalid HLS key at URL: ' + request.uri,
code: REQUEST_ERRORS.FAILURE,
xhr: request
}, segment);
}
}
// 解密二次加密后的key - 结束
// 将原有的注释
// var view = new DataView(response);
// segment.key.bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
// return finishProcessingFn(null, segment);
...
}
haveMetadata = function (xhr, url) {
...
// any in-flight request is now finished
request = null;
loader.state = 'HAVE_METADATA';
// 解析m3u8正确格式 - 开始
if (typeof (req.responseText) == "string") {
/**
* 解密m3u8得到的值
*/
var _responseText = 解密后的内容;
} else {
var _responseText = req.responseText;
}
// 解析m3u8正确格式 - 结束
parser = new _m3u8Parser2['default'].Parser();
parser.push(_responseText);
parser.end();
...
}
loader.start = function () {
...
if (error) {
loader.error = {
status: req.status,
message: 'HLS playlist request error at URL: ' + srcUrl,
responseText: req.responseText,
// MEDIA_ERR_NETWORK
code: 2
};
if (loader.state === 'HAVE_NOTHING') {
loader.started = false;
}
return loader.trigger('error');
}
// 解析m3u8正确格式 - 开始
if (typeof (req.responseText) == "string") {
/**
* 解密m3u8得到的值
*/
var _responseText = 解密后的内容;
} else {
var _responseText = req.responseText;
}
// 解析m3u8正确格式 - 结束
parser = new _m3u8Parser2['default'].Parser();
parser.push(_responseText);
parser.end();
...
}
复制代码
-
方式二:如果感觉第一种麻烦可以让后端对key进行特殊二次加密(当然前提是前端知道后端是如何加密的以方便如何解密)
-
方式二解密:
// video.js 7.15.0 (这个版本自带hls功能)
var handleKeyResponse = function handleKeyResponse(segment, objects, finishProcessingFn) {
return function (error, request) {
var response = request.response;
var errorObj = handleErrors(error, request);
if (errorObj) {
return finishProcessingFn(errorObj, segment);
}
/**
*
* 1、咔咔咔写自己拿到key返回内容进行解密
* 2、最后得到的是一个16位字符串
* 3、必须将字符串转换为ArrayBuffer
* 注:假如某个节视频key解密后为 2y7d9iwu8rc3fa0x
*/
//字符串转字符串ArrayBuffer
function str2ab(s,f) {
var b = new Blob([s],{type:'text/plain'});
var r = new FileReader();
r.readAsArrayBuffer(b);
r.onload = function (){if(f)f.call(null,r.result)}
}
// 此处是为了演示,实际应该填写每次解密后的字符串变量
str2ab('2y7d9iwu8rc3fa0x',function(ab){
//ab为ArrayBuffer
var view = new DataView(ab);
var bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
console.log(bytes);
for (var i = 0; i < objects.length; i++) {
objects[i].bytes = bytes;
}
return finishProcessingFn(null, segment);
});
// 将原来的方法注释掉
// var view = new DataView(new Int8Array(hexAesStr('516aa4d465a4085baddcac890cbcbf7e')).buffer);
// var bytes = new Uint32Array([view.getUint32(0), view.getUint32(4), view.getUint32(8), view.getUint32(12)]);
// for (var i = 0; i < objects.length; i++) {
// objects[i].bytes = bytes;
// }
// return finishProcessingFn(null, segment);
};
};
复制代码
3 最后
- 两种方式都是经过web端测试可行的,以此作为记录,欢迎各位大佬指教