基于HLS流媒体协议的视频加密与解密播放完整解决方案 一、方案概述 你希望获取一份可落地的基于HLS(HTTP Live Streaming)协议的视频加密与解密播放解决方案,核心目标是通过加密TS切片实现视频版权保护,同时在客户端安全解密播放。本方案整合FFmpeg转码加密、Video.js解密播放、Node.js/Koa密钥服务,从原理到实战分步拆解,兼顾易用性与安全性。
核心价值
版权保护 :防止视频文件被直接下载盗用,仅授权客户端可解密播放;
兼容性广 :HLS协议兼容绝大多数浏览器、移动端设备(iOS原生支持,Android需Video.js适配);
流式播放 :切片传输,支持断点续播、自适应码率(可按需扩展)。
二、核心原理 HLS协议的视频加密采用AES-128对称加密 (行业标准),核心流程分为「服务端加密」和「客户端解密」两部分:
graph TD
A[原始视频文件(MP4/MP3)] --> B[FFmpeg转码]
B --> C[切割为TS切片]
C --> D[AES-128加密TS切片]
D --> E[生成带密钥信息的m3u8索引文件]
E --> F[Koa服务提供m3u8/TS/密钥接口]
G[客户端播放页] --> H[Video.js请求m3u8]
H --> I[解析m3u8获取密钥地址+TS切片列表]
I --> J[请求密钥(鉴权后返回)]
J --> K[Video.js解密TS切片]
K --> L[流式播放]
关键说明:
加密仅针对TS切片,m3u8文件仅存储「TS切片路径+密钥地址+加密算法」,不包含敏感信息;
密钥(Key)是解密核心,需通过服务端鉴权后分发(绝对禁止明文暴露在前端);
客户端解密由Video.js的videojs-http-streaming插件自动完成,无需手动实现AES解密逻辑。
三、技术栈准备 1. 核心工具与依赖
2. 环境验证 1 2 3 4 5 6 ffmpeg -version node -v npm -v
四、分步实现示例 步骤1:生成AES-128加密密钥 AES-128要求密钥为16字节(128位),可通过OpenSSL或Node.js生成:
1 2 3 4 5 6 7 8 9 10 openssl rand 16 > key.key const crypto = require('crypto' ); const fs = require('fs' ); // 生成16字节随机密钥 const key = crypto.randomBytes(16); // 保存密钥到服务端(绝对禁止上传到前端/公开仓库) fs.writeFileSync('./secret/key.key' , key);
步骤2:服务端视频转码+加密(FFmpeg) 方式1:直接使用FFmpeg命令行(快速验证) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 ffmpeg -i input.mp4 \ -hls_time 10 \ -hls_key_info_file key.info \ -hls_playlist_type vod \ -hls_segment_filename "video_%03d.ts" \ output.m3u8 ./secret/key.key http://localhost:3000/api/getKey AES-128
方式2:Node.js封装(fluent-ffmpeg,便于集成到业务) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const ffmpeg = require ('fluent-ffmpeg' );const path = require ('path' ); ffmpeg.setFfmpegPath ('D:/ffmpeg/bin/ffmpeg.exe' );ffmpeg ('./input.mp4' ) .outputOptions ([ '-hls_time 10' , '-hls_key_info_file key.info' , '-hls_playlist_type vod' , '-hls_segment_filename "video_%03d.ts"' ]) .output ('./dist/output.m3u8' ) .on ('end' , () => { console .log ('转码加密完成' ); }) .on ('error' , (err ) => { console .error ('转码失败:' , err); }) .run ();
步骤3:搭建Koa服务(提供资源+密钥鉴权) 1. 安装依赖 1 npm install koa koa-static koa-router crypto
2. 核心服务代码(server.js) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 const Koa = require ('koa' );const static = require ('koa-static' );const Router = require ('koa-router' );const fs = require ('fs' );const crypto = require ('crypto' );const path = require ('path' );const app = new Koa ();const router = new Router ();const PORT = 3000 ; app.use (static (path.join (__dirname, 'dist' ))); router.get ('/api/getKey' , async (ctx) => { const authToken = ctx.query .token ; if (!authToken || authToken !== 'valid-token-123' ) { ctx.status = 403 ; ctx.body = '鉴权失败,禁止获取密钥' ; return ; } const key = fs.readFileSync ('./secret/key.key' ); ctx.set ('Content-Type' , 'application/octet-stream' ); ctx.set ('Cache-Control' , 'no-cache' ); ctx.body = key; }); app.use (router.routes ()).use (router.allowedMethods ()); app.listen (PORT , () => { console .log (`服务启动:http://localhost:${PORT} ` ); });
步骤4:客户端解密播放(Video.js) 1. 前端页面(index.html) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 <!DOCTYPE html > <html lang ="zh-CN" > <head > <meta charset ="UTF-8" > <title > HLS加密视频播放</title > <link href ="https://cdn.jsdelivr.net/npm/video.js@8/dist/video-js.min.css" rel ="stylesheet" > <script src ="https://cdn.jsdelivr.net/npm/video.js@8/dist/video.min.js" > </script > <script src ="https://cdn.jsdelivr.net/npm/@videojs/http-streaming@3/dist/videojs-http-streaming.min.js" > </script > <style > #player { width : 800px ; height : 450px ; margin : 20px auto; } </style > </head > <body > <video id ="player" class ="video-js vjs-big-play-centered" controls preload ="auto" > </video > <script > const player = videojs ('player' , { autoplay : false , controls : true , responsive : true , fluid : false , html5 : { hls : { fetchKey : function (uri ) { const authUri = `${uri} ?token=valid-token-123` ; return fetch (authUri) .then (response => { if (!response.ok ) throw new Error ('密钥获取失败' ); return response.arrayBuffer (); }); } } } }); player.src ({ src : 'http://localhost:3000/output.m3u8' , type : 'application/x-mpegURL' }); player.on ('error' , (err ) => { console .error ('播放失败:' , player.error ()); }); </script > </body > </html >
步骤5:启动验证
将index.html放入dist目录(与m3u8/TS同目录);
启动Koa服务:node server.js;
浏览器访问http://localhost:3000/index.html,即可播放加密视频。
五、进阶优化(可选) 1. 动态密钥(socket.io) 为提升安全性,可通过socket.io生成一次性动态密钥,每次播放请求生成新密钥:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 const { Server } = require ('socket.io' );const http = require ('http' );const server = http.createServer (app.callback ());const io = new Server (server); io.on ('connection' , (socket ) => { console .log ('客户端连接' ); const dynamicKey = crypto.randomBytes (16 ); socket.emit ('key' , dynamicKey.toString ('hex' )); }); server.listen (PORT , () => { });
2. 防盗链补充
Referer验证 :在密钥接口校验请求来源,仅允许白名单域名;
TS切片命名混淆 :使用随机字符串命名TS文件,避免被猜测;
CDN分发 :将加密后的m3u8/TS部署到CDN,减轻源站压力,同时配置CDN防盗链。
3. 兼容性优化
低版本浏览器需引入hls.js作为兜底;
移动端适配:设置fluid: true让播放器自适应屏幕。
六、关键注意事项
密钥安全 :
密钥文件需存储在服务端私有目录,绝对禁止提交到代码仓库;
密钥接口必须添加严格鉴权(Token/登录态/IP),避免密钥泄露;
可定期轮换密钥,降低泄露风险。
性能优化 :
TS切片时长建议5-10秒,过短会增加请求数,过长会延长首屏加载时间;
转码时生成多码率TS(如720p/480p),实现自适应码率播放。
兼容性 :
iOS Safari原生支持HLS加密,无需额外插件;
Android需依赖Video.js或hls.js,建议测试主流机型。
七、参考资料
开源示例:https://github.com/hauk0101/video-hls-encrypt
FFmpeg HLS加密文档:https://ffmpeg.org/ffmpeg-formats.html#hls-2
Video.js HLS文档:https://docs.videojs.com/guides/hls.html
HLS协议官方规范:https://datatracker.ietf.org/doc/html/rfc8216
总结 关键点回顾
HLS视频加密核心是AES-128加密TS切片,m3u8仅存储密钥地址和加密信息;
服务端需完成「转码加密+密钥鉴权分发」,客户端通过Video.js自动解密播放;
密钥安全是核心:必须鉴权、禁止明文暴露、定期轮换;
核心流程:FFmpeg生成加密m3u8/TS → Koa提供资源+鉴权密钥 → Video.js解密播放。
该方案可直接落地到视频点播、付费课程等场景,兼顾安全性与易用性,同时可根据业务需求扩展动态密钥、多码率适配等能力。