音视频加密

基于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[流式播放]

关键说明:

  1. 加密仅针对TS切片,m3u8文件仅存储「TS切片路径+密钥地址+加密算法」,不包含敏感信息;
  2. 密钥(Key)是解密核心,需通过服务端鉴权后分发(绝对禁止明文暴露在前端);
  3. 客户端解密由Video.js的videojs-http-streaming插件自动完成,无需手动实现AES解密逻辑。

三、技术栈准备

1. 核心工具与依赖

技术/工具 作用 官方/下载地址
FFmpeg 视频转码、切片、AES加密 官网:http://ffmpeg.org/download.html
Windows版:https://github.com/BtbN/FFmpeg-Builds/releases
fluent-ffmpeg Node.js封装FFmpeg命令 https://github.com/fluent-ffmpeg/node-fluent-ffmpeg
Video.js 客户端视频播放核心 https://github.com/videojs/video.js
videojs-http-streaming Video.js的HLS播放/解密插件 https://github.com/videojs/http-streaming
Node.js + Koa 搭建静态资源/密钥分发服务 Node.js:https://nodejs.org/
Koa:https://github.com/koajs/koa
socket.io(可选) 实时动态密钥分发 https://github.com/socketio/socket.io

2. 环境验证

1
2
3
4
5
6
# 验证FFmpeg安装(终端执行,输出版本即成功)
ffmpeg -version

# 验证Node.js环境
node -v
npm -v

四、分步实现示例

步骤1:生成AES-128加密密钥

AES-128要求密钥为16字节(128位),可通过OpenSSL或Node.js生成:

1
2
3
4
5
6
7
8
9
10
# 方式1:OpenSSL生成密钥(输出到key.key文件)
openssl rand 16 > key.key

# 方式2:Node.js生成密钥
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
# 核心命令:MP4转加密HLS(生成m3u8+加密TS)
ffmpeg -i input.mp4 \
-hls_time 10 \ # 每个TS切片时长(秒)
-hls_key_info_file key.info \ # 密钥信息文件
-hls_playlist_type vod \ # 点播模式(非直播)
-hls_segment_filename "video_%03d.ts" \ # TS切片命名规则
output.m3u8

# 关键:创建key.info文件(内容如下)
# key.info格式:
# 第1行:密钥文件路径(服务端本地)
# 第2行:密钥访问地址(客户端将通过该地址获取密钥)
# 第3行:加密算法(固定AES-128)
./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路径(Windows需指定,Linux/Mac可省略)
ffmpeg.setFfmpegPath('D:/ffmpeg/bin/ffmpeg.exe');

// 转码+加密
ffmpeg('./input.mp4')
.outputOptions([
'-hls_time 10', // 10秒一个切片
'-hls_key_info_file key.info', // 密钥信息
'-hls_playlist_type vod',
'-hls_segment_filename "video_%03d.ts"'
])
.output('./dist/output.m3u8') // 输出目录(需提前创建dist)
.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;

// 1. 静态资源服务:提供m3u8、TS切片
app.use(static(path.join(__dirname, 'dist')));

// 2. 密钥接口(核心:鉴权后返回密钥,示例仅做简单验证)
router.get('/api/getKey', async (ctx) => {
// 【重要】实际项目需添加鉴权逻辑(如Token、用户登录态、IP白名单)
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>
<!-- 引入Video.js核心样式和脚本 -->
<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>
<!-- 引入HLS插件(解密核心) -->
<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,
// HLS配置:自动处理密钥获取和解密
html5: {
hls: {
// 可选:自定义密钥请求(如添加鉴权Token)
fetchKey: function (uri) {
// 给密钥请求添加鉴权Token
const authUri = `${uri}?token=valid-token-123`;
return fetch(authUri)
.then(response => {
if (!response.ok) throw new Error('密钥获取失败');
return response.arrayBuffer();
});
}
}
}
});

// 加载加密的m3u8文件
player.src({
src: 'http://localhost:3000/output.m3u8',
type: 'application/x-mpegURL' // HLS的MIME类型
});

// 播放错误捕获
player.on('error', (err) => {
console.error('播放失败:', player.error());
});
</script>
</body>
</html>

步骤5:启动验证

  1. index.html放入dist目录(与m3u8/TS同目录);
  2. 启动Koa服务:node server.js
  3. 浏览器访问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
// 服务端添加socket.io
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'));
// 用新密钥加密当前播放的TS切片(需结合FFmpeg实时加密)
});

server.listen(PORT, () => { /* ... */ });

2. 防盗链补充

  • Referer验证:在密钥接口校验请求来源,仅允许白名单域名;
  • TS切片命名混淆:使用随机字符串命名TS文件,避免被猜测;
  • CDN分发:将加密后的m3u8/TS部署到CDN,减轻源站压力,同时配置CDN防盗链。

3. 兼容性优化

  • 低版本浏览器需引入hls.js作为兜底;
  • 移动端适配:设置fluid: true让播放器自适应屏幕。

六、关键注意事项

  1. 密钥安全
    • 密钥文件需存储在服务端私有目录,绝对禁止提交到代码仓库;
    • 密钥接口必须添加严格鉴权(Token/登录态/IP),避免密钥泄露;
    • 可定期轮换密钥,降低泄露风险。
  2. 性能优化
    • TS切片时长建议5-10秒,过短会增加请求数,过长会延长首屏加载时间;
    • 转码时生成多码率TS(如720p/480p),实现自适应码率播放。
  3. 兼容性
    • iOS Safari原生支持HLS加密,无需额外插件;
    • Android需依赖Video.js或hls.js,建议测试主流机型。

七、参考资料

  1. 开源示例:https://github.com/hauk0101/video-hls-encrypt
  2. FFmpeg HLS加密文档:https://ffmpeg.org/ffmpeg-formats.html#hls-2
  3. Video.js HLS文档:https://docs.videojs.com/guides/hls.html
  4. HLS协议官方规范:https://datatracker.ietf.org/doc/html/rfc8216

总结

关键点回顾

  1. HLS视频加密核心是AES-128加密TS切片,m3u8仅存储密钥地址和加密信息;
  2. 服务端需完成「转码加密+密钥鉴权分发」,客户端通过Video.js自动解密播放;
  3. 密钥安全是核心:必须鉴权、禁止明文暴露、定期轮换;
  4. 核心流程:FFmpeg生成加密m3u8/TS → Koa提供资源+鉴权密钥 → Video.js解密播放。

该方案可直接落地到视频点播、付费课程等场景,兼顾安全性与易用性,同时可根据业务需求扩展动态密钥、多码率适配等能力。


音视频加密
https://cszy.top/20210531-音视频加密/
作者
csorz
发布于
2021年5月31日
许可协议