本文只讨论应用于浏览器环境的流媒体协议的加密。
背景
付费观看视频的模式是很多平台的核心业务,如果视频被录制并非法传播,付费业务将受到严重威胁。因此对视频服务进行加密的技术变得尤为重要。
本文所指的视频加密是为了让要保护的视频不能轻易被下载,即使下载到了也是加密后的内容,其它人解开加密后的内容需要付出非常大的代价。
无法做到严格的让要保护的视频不被录制,原因在于你需要在客户端播放出视频的原内容,解密的流程在客户端的话不法分子就能模拟整个流程,最保守也能用屏幕录制软件录制到视频的原内容(可以通过加水印的方法缓解下)。我们的目标是让他获取原内容的代价更大。
简介
起初是为了将业务中已有的基于 Flash 的视频播放器替换为不依赖 Flash 的html5 视频播放器,主要使用了现有的 video.js 开源播放器做的定制化开发。当完成视频播放器的制作后,在进一步延伸 WEB 端视频加密的相关内容时,开始了解并逐渐深入的研究了相关视频加密内容。
最终通过整理归纳,以及自身的理解,做了这个简单的 Demo。目的是为了能够给在视频加密这方面有相同目的的童鞋提供微薄的帮助,要是能起到抛砖引玉的效果,自然是再好不过了。
项目启动
1.安装项目环境
安装 node、npm 环境
根据 app 目录下的 package.json 安装对应的 npm 包
安装 ffmpeg
2.启动项目
在 app 目录下,输入 npm start ,启动项目
在浏览器中访问 http://localhost:3000
按照页面中的顺序进行相关操作
3.权限登录
用户名:admin
密码:admin
项目原理
本项目的核心原理其实就是讲解了一个视频源从正常的 mp4 格式如何变为加密后的m3u8 文件 + ts文件 + key秘钥文件,之后又如何在服务端被限制访问,最终能够在客户端正常播放的视频加密、解密并播放的流程。
项目原理图示
技术栈
nodejs + express 实现服务器开发
ffmpeg + fluent-ffmpeg 实现 node 环境下的视频转码、加密
socket.io 通过 websocket 相关的类库,实现实时输出 ffmpeg 进行的视频转码、加密操作
video.js + videojs-contrib-hls.js 实现客户端的视频解密及播放
html + css + js 实现简单的前端开发
源码简析
项目目录说明
video-hls-encrypt/ .............................. hls视频加密项目根目录 ├── app/ .............................. express框架默认的app根目录 │ ├── bin/ .............................. express框架启动的bin目录 │ │ └── www .............................. express框架启动的www文件 │ ├── controllers/ .............................. 项目控制器目录,服务器相关的逻辑代码 │ │ ├── encrypt.js .............................. 加密逻辑代码 │ │ └── upload.js .............................. 上传逻辑代码 │ ├── node_modules/ .............................. express框架需要的相关npm依赖包,即package.json文件相对应的依赖包 │ │ └── ... │ ├── public/ .............................. express框架静态文件目录,客户端请求的相关静态文件 │ │ ├── javascripts .............................. 客户端的js文件目录 │ │ │ ├── encrypt.js .............................. 加密功能相关逻辑代码 │ │ │ ├── index.js .............................. 主页相关逻辑代码 │ │ │ ├── player.js .............................. 播放器相关逻辑代码 │ │ │ ├── socket.io.js .............................. socket.io.js 类库源文件 │ │ │ └── utils.js .............................. 工具类 │ │ ├── key/ .............................. 秘钥相关目录 │ │ │ ├── encrypt.key .............................. 秘钥文件 │ │ │ └── key_info.key .............................. ffmpeg加密视频转换相关文件 │ │ ├── libs/ .............................. 第三方类库目录 │ │ │ ├── videojs/ .............................. videojs 相关代码 │ │ │ └── videojs-contrib-hls/ .............................. videojs-contrib-hls 相关代码 │ │ ├── stylesheets/ .............................. css样式目录 │ │ │ └── common.css .............................. 通用样式表 │ │ └── videos/ .............................. 视频资源目录 │ │ ├── encrypt/ .............................. 加密后的视频资源目录 │ │ └── noencrypt/ .............................. 加密前的视频资源目录 │ ├── routes/ .............................. express框架路由目录 │ │ └── router.js .............................. express路由 │ ├── views/ .............................. express框架ejs模板目录 │ │ ├── encrypt.ejs .............................. 视频加密页面 │ │ ├── error.ejs .............................. 错误页面 │ │ ├── index.ejs .............................. 主页 │ │ ├── login.ejs .............................. 登录页面 │ │ ├── player.ejs .............................. 播放器页面 │ │ └── upload.ejs .............................. 上传视频页面 │ ├── app.js .............................. express程序入口 │ ├── nodemon.json .............................. node服务器热更新插件nodemon对应的配置文件 │ └── package.json .............................. express框架需要的第三方依赖包配置文件 ├── .gitignore ├── README.md .............................. 项目说明文档 └── TODO-List.md .............................. 项目开发计划文档
源码简析
简单的权限判断,app.js中:
express 的中间件
判断请求的后缀
判断 session 中是否有用户名,有则允许访问 .key 文件;否则禁止访问
主要是保护 .key 文件,可以加入其它的权限手段,比如 token、session 有效时长等等
//静态资源访问限制 app.use(function (req, res, next) { var suffix = /(\.key)$/g;//后缀格式指定 if ( suffix.test(req.path)) { console.log(req.session.username,'++++请求key文件了'); if((req.session.username != 'admin')){ return res.send('请求非法'); }else{ console.log('+++++请求key文件了,并且已经登录,登录名为:',req.session.username); next(); } } else { next(); } });利用 FFmpeg 对视频进行加密、切片处理,在 encrypt.js 中:
利用了 FFmpeg 的切片和加密方法
建议可以深入研究 FFmpeg 框架的相关 api
可以根据实际业务来对视频进行更符合要求的切片处理
/** * 加密处理方法 * @param options 加密数据的相关参数 * @param socket socket输出 * @param callback 回调函数 */ function encryptFun(options,socket, callback) { var _name = options.fileName.split('.')[0]; var _type = options.fileName.split('.')[1]; var _encryptPath = options.encryptPath + '/' + _name; var _videoPath = options.noencryptPath + '/' + options.fileName; var _keyInfoPath = './public/key/key_info.key'; var _outputPath = _encryptPath + '/playlist.m3u8'; console.log('begin encrypt Fun'); if (_type == 'mp4') { ffmpegCommand(_videoPath) .addOption('-hls_time', '10') //设置每个片段的长度 .addOption('-hls_key_info_file', _keyInfoPath) .save(_outputPath) .on('end', function () { socket.emit('encrypt-event',{msg:'Encrypt the ' + options.fileName + ' file OK!',type:1}); callback(null, 'Encrypt the ' + options.fileName + ' file OK!'); }) .on('stderr', function (stderrLine) { console.log('Stderr output: ' + stderrLine); socket.emit('encrypt-event',{msg:stderrLine}); }) .on('error', function (err, stdout, stderr) { console.log('Cann