一、视频加密技术原理
1.1 视频加密的本质
视频加密技术的核心是:使用加密算法将普通视频转换成密文,只有拥有正确解密密钥的播放器才能恢复视频数据并进行播放。
普通视频播放器能够直接读取 MP4、MKV 等文件,是因为这些文件采用公开的容器格式和音视频编码标准。
如果使用 AES 等加密算法对视频数据进行加密,原有的视频结构就会被转换成无法直接解析的密文。
例如:
原始视频:movie.mp4
|
v
AES 加密
|
v
加密视频:movie.svp
|
v
普通播放器无法直接播放
专用播放器的作用,就是在视频解码之前增加一个解密环节。
加密视频 movie.svp
|
v
专用播放器读取文件
|
v
使用密钥解密视频
|
v
恢复原始视频数据
|
v
视频解码
|
v
显示画面、播放声音
需要注意:
- 修改视频扩展名不属于真正的加密。
- 自定义文件格式本身不能代替密码学加密。
- 视频加密的安全性主要取决于加密算法、密钥管理和播放器的实现方式。
- 专用播放器并不意味着视频绝对无法被破解或录制。
1.2 视频文件为什么可以加密
普通视频文件本质上是一组二进制数据。
例如,MP4 文件通常包含:
- 文件头及容器元数据
- 视频编码数据
- 音频编码数据
- 时间戳
- 视频索引信息
正常播放器的工作流程:
MP4 文件
|
v
读取二进制数据
|
v
解析视频容器
|
v
分离音频流和视频流
|
v
解码音视频数据
|
v
输出画面和声音
如果将整个 MP4 文件作为普通二进制数据进行加密,播放器就无法直接识别原来的文件结构。
专用播放器只需要先解密,再将恢复的数据交给标准视频解码器即可。
这种方式的优势是:
- 不需要自己开发 H.264、H.265 等视频编解码算法。
- 不需要改变原视频编码。
- 可以兼容不同视频容器。
- 能够复用 FFmpeg 等成熟的视频解码库。
- 加密和视频解码逻辑可以相互独立。
二、视频加密算法原理
2.1 对称加密
视频文件通常体积较大,因此适合采用对称加密算法。
对称加密的特点是:
加密和解密使用同一把密钥。
视频密钥
|
+--------+--------+
| |
v v
加密程序 解密程序
| |
v v
视频密文 原始视频
常用算法包括:
| 算法 | 特点 |
|---|---|
| AES-128-GCM | 128 位密钥,认证加密 |
| AES-256-GCM | 256 位密钥,认证加密 |
| AES-256-CTR | 支持随机访问,但不提供完整性认证 |
| ChaCha20-Poly1305 | 认证加密,适用于多种平台 |
本项目推荐使用 AES-256-GCM。
2.2 AES-256-GCM 的工作原理
AES-256-GCM 是一种认证加密算法。
它不仅能够加密数据,还能够检测加密数据是否遭到篡改。
加密时需要:
- 明文数据(Plaintext)
- 256 位密钥(Key)
- 唯一的随机数或计数值(Nonce)
- 可选的关联认证数据(AAD)
加密后产生:
- 密文(Ciphertext)
- 认证标签(Authentication Tag)
基本过程:
原始视频数据
|
v
AES-256-GCM
|
+---- Key
|
+---- Nonce
|
+---- AAD
|
v
密文 + 认证标签
解密时必须使用正确的密钥、Nonce 和 AAD,并验证认证标签。
如果认证失败,数据应被视为无效,不能交给视频解码器。
重要原则:同一密钥下,GCM Nonce 绝对不能重复使用。
推荐每个视频生成独立的随机密钥,并为每个加密块生成唯一的 Nonce。
2.3 为什么每个视频使用独立密钥
假设所有视频共用同一个密钥:
Video A ──┐
Video B ──┼── Master Key
Video C ──┘
一旦该密钥泄露,所有视频都可能被解密。
更合理的方式:
Video A ---> Key A
Video B ---> Key B
Video C ---> Key C
这样即使某个视频密钥泄露,也不会直接导致其他视频被解密。
因此推荐:
- 每个视频使用独立的数据加密密钥(DEK)。
- 密钥通过安全随机数生成器生成。
- 视频文件中只保存视频 ID,不保存明文密钥。
- 服务端通过密钥加密密钥(KEK)保护 DEK。
- 播放器获得授权后,才能取得对应视频的 DEK。
三、视频分块加密原理
3.1 为什么不能简单地整文件解密
假设有一个 20GB 的视频文件。
如果播放器必须先解密整个文件才能播放,会产生以下问题:
- 视频启动时间较长。
- 解密过程可能占用大量磁盘空间。
- 容易生成完整的临时明文文件。
- 视频进度条跳转效率低。
- 不适合大容量高清视频。
因此更适合采用分块加密技术。
3.2 分块加密的实现原理
将原始视频按固定大小拆分为多个数据块。
例如,每块 4MB:
原始视频 movie.mp4
|
v
+----------------+
| Block 0:4MB |
| Block 1:4MB |
| Block 2:4MB |
| Block 3:4MB |
| ... |
| Block N |
+----------------+
|
v
分别进行 AES-GCM 加密
|
v
生成加密视频 movie.svp
每个加密块独立进行认证加密。
Block 0 -> Nonce 0 -> Ciphertext 0 + Tag 0
Block 1 -> Nonce 1 -> Ciphertext 1 + Tag 1
Block 2 -> Nonce 2 -> Ciphertext 2 + Tag 2
播放器可以根据需要读取指定的数据块,而不必解密整个文件。
3.3 分块加密如何支持快进
例如,用户将视频进度条拖动到第 30 分钟。
播放器执行以下操作:
用户拖动进度条
|
v
FFmpeg 定位视频时间戳
|
v
请求对应的文件数据
|
v
定位加密数据块
|
v
读取并解密数据块
|
v
验证认证标签
|
v
返回解密后的数据
|
v
FFmpeg 解码
|
v
显示目标时间附近的画面
需要注意:
视频时间位置不等于文件字节位置。
因此,不能简单地通过视频时长计算加密块编号。
正确的方式是让 FFmpeg 负责容器解析和时间戳定位,由自定义文件读取模块负责将明文字节偏移映射到加密块。
四、自定义加密视频格式原理
4.1 为什么需要自定义格式
虽然可以直接对 MP4 文件加密,但专用播放器还需要知道:
- 文件是否属于自己的加密格式。
- 使用哪种加密算法。
- 视频对应的唯一 ID。
- 原始文件大小。
- 每个加密块的位置。
- 每个加密块的认证信息。
- 文件格式版本。
因此,需要设计一个自定义的加密文件容器。
例如:
原始视频:movie.mp4
加密视频:movie.svp
.svp 只是自定义扩展名,真正的安全性来自内部数据加密。
4.2 SVP 文件结构
建议设计如下:
+--------------------------------+
| Magic Number:SVP1 |
+--------------------------------+
| Format Version |
+--------------------------------+
| Video ID |
+--------------------------------+
| Encryption Algorithm |
+--------------------------------+
| Original File Size |
+--------------------------------+
| Chunk Size |
+--------------------------------+
| Chunk Count |
+--------------------------------+
| Nonce Parameters |
+--------------------------------+
| Chunk Index |
+--------------------------------+
| Encrypted Block 0 |
+--------------------------------+
| Encrypted Block 1 |
+--------------------------------+
| Encrypted Block 2 |
+--------------------------------+
| ... |
+--------------------------------+
其中:
| 字段 | 作用 |
|---|---|
| Magic Number | 识别文件格式 |
| Format Version | 文件格式版本 |
| Video ID | 视频唯一标识 |
| Encryption Algorithm | 加密算法标识 |
| Original File Size | 原始视频大小 |
| Chunk Size | 数据块大小 |
| Chunk Count | 数据块数量 |
| Nonce Parameters | Nonce 生成参数 |
| Chunk Index | 数据块位置索引 |
| Encrypted Blocks | 实际加密视频数据 |
文件头、索引及影响解密解释的元数据应受到认证保护。
实际文件格式需要明确字段长度、字节序、最大文件大小、Nonce 构造方式以及错误处理规则。
4.3 普通播放器为什么无法播放 SVP
普通播放器一般按照 MP4、MKV 等容器格式解析文件。
而 SVP 文件包含自定义文件头和加密数据。
普通播放器
|
v
打开 movie.svp
|
v
无法识别有效视频容器
|
v
播放失败
专用播放器则先解析 SVP 格式,然后解密数据。
专用播放器
|
v
识别 SVP 文件
|
v
解析加密文件头
|
v
取得解密密钥
|
v
解密视频数据
|
v
交给 FFmpeg
|
v
正常播放
五、专用播放器边解密边播放的原理
5.1 核心思想
专用播放器不需要将整个视频解密成 MP4 文件。
只需要将当前播放所需的数据解密到内存,再交给视频解码器。
这种方式称为按需解密播放。
加密视频 movie.svp
|
v
读取加密数据块
|
v
AES-GCM 解密
|
v
内存缓冲区
|
v
FFmpeg 解码
|
v
视频画面 + 音频
这种方式的优势:
- 不生成完整的临时明文视频。
- 支持大文件播放。
- 可以实现随机访问。
- 降低磁盘明文泄露风险。
- 可以复用标准视频解码器。
但是,视频数据在解密和解码过程中仍然会以明文形式短暂存在于内存中。
因此无法保证绝对防止内存提取和录屏。
5.2 FFmpeg 自定义 IO 原理
FFmpeg 提供 AVIOContext,允许开发者自定义文件读取方式。
普通播放方式:
FFmpeg
|
v
读取 MP4 文件
|
v
解码
加密播放方式:
FFmpeg
|
v
自定义 AVIOContext
|
v
SVPReader
|
v
读取加密块
|
v
AES-GCM 解密
|
v
返回明文数据
|
v
FFmpeg 解码
关键是实现两个回调:
read_packet
当 FFmpeg 需要读取数据时:
- 获取当前逻辑文件偏移。
- 计算对应的加密块。
- 从磁盘读取密文。
- 使用 AES-GCM 解密并验证。
- 将所需明文字节返回给 FFmpeg。
seek
当 FFmpeg 需要跳转时:
- 接收新的逻辑文件位置。
- 更新当前读取偏移。
- 根据新的位置定位加密块。
- 后续读取从新位置开始。
同时需要正确处理文件大小查询、越界、EOF 和解密失败等情况。
5.3 FFmpeg 与 Qt 的分工
FFmpeg 负责:
- 读取解密后的数据。
- 解析 MP4、MKV 等容器。
- 分离音视频流。
- 解码 H.264、H.265 等视频。
- 解码音频。
- 提供时间戳信息。
播放器核心负责:
- 解码线程。
- 音视频同步。
- 播放状态管理。
- 缓冲队列。
- 跳转控制。
- 错误处理。
Qt 负责:
- 播放器图形界面。
- 视频画面显示。
- 音频输出设备接入。
- 播放按钮。
- 进度条。
- 音量调节。
- 全屏切换。
- 网络授权请求。
六、在线视频授权与密钥管理原理
6.1 为什么需要在线授权
如果播放器内置固定的解密密钥,攻击者可以通过逆向分析程序尝试提取密钥。
因此,专用播放器不应内置所有视频的解密密钥。
推荐使用在线授权服务器管理视频密钥。
基本流程:
用户打开加密视频
|
v
播放器读取 Video ID
|
v
连接授权服务器
|
v
验证用户和设备
|
v
检查视频播放权限
|
v
授权成功?
/ \
否 是
| |
v v
拒绝播放 安全发放视频密钥
|
v
内存解密播放
6.2 授权服务器的作用
授权服务器负责:
- 管理用户。
- 管理视频。
- 管理激活码。
- 管理设备绑定。
- 管理授权有效期。
- 检查视频播放权限。
- 管理加密视频的密钥。
- 撤销指定用户或设备的授权。
6.3 密钥发放原理
视频加密时,生成独立的 DEK。
服务器使用 KEK 对 DEK 进行加密封装。
视频原始数据
|
v
使用 DEK 加密
|
v
生成加密视频 SVP
DEK
|
v
使用 KEK 保护
|
v
授权服务器保存加密后的 DEK
播放时:
播放器请求授权
|
v
服务器验证权限
|
v
服务器解封装 DEK
|
v
通过受保护的通信会话发放 DEK
|
v
播放器内存解密
|
v
播放视频
通信必须使用 HTTPS,并正确验证服务器证书。
设备绑定不能只依赖一个容易伪造的硬件序列号,建议采用设备密钥对与挑战响应认证。
6.4 远程撤销的限制
在线授权能够控制后续密钥发放,但不能保证已经交付到客户端的密钥被彻底收回。
因此建议:
- 每次播放前重新授权。
- 使用短期播放会话。
- 播放过程中定期检查授权状态。
- 不长期缓存明文视频密钥。
- 授权撤销后,正常播放器停止播放。
七、C++ / Qt 简单实现方案
前面六章主要介绍技术原理,下面给出具体开发方案。
7.1 技术选型
| 模块 | 技术方案 |
|---|---|
| 开发语言 | C++20 |
| 图形界面 | Qt 6 |
| 项目构建 | CMake |
| 加密解密 | OpenSSL 3.x |
| 视频解码 | FFmpeg |
| 音频输出 | Qt Multimedia |
| 网络请求 | Qt Network |
| 授权服务器 | C++ HTTP 框架 |
| 数据库 | PostgreSQL |
| 平台 | Windows、macOS、Linux |
建议优先完成桌面播放器和加密工具,再接入在线授权服务器。
7.2 软件模块划分
整个项目分为三个程序。
① SVEncrypt:视频加密软件
功能:
- 选择普通视频。
- 生成随机视频密钥。
- 使用 AES-256-GCM 分块加密。
- 生成自定义 SVP 文件。
- 将视频 ID 和加密后的密钥登记到服务器。
- 支持批量加密。
输入:
movie.mp4
输出:
movie.svp
② SVPlayer:专用视频播放器
功能:
- 打开
.svp文件。 - 解析自定义文件格式。
- 在线验证播放权限。
- 获取视频解密密钥。
- 按需解密视频。
- 使用 FFmpeg 解码。
- 使用 Qt 显示视频。
- 支持播放、暂停、跳转、音量和全屏。
③ SVLicenseServer:授权服务器
功能:
- 用户认证。
- 激活码管理。
- 设备绑定。
- 视频权限管理。
- 视频密钥管理。
- 授权有效期管理。
- 远程撤销。
- 播放会话管理。
7.3 推荐项目目录
SVPlayer/
│
├── CMakeLists.txt
│
├── common/
│ ├── crypto/
│ │ ├── CryptoEngine.h
│ │ └── CryptoEngine.cpp
│ │
│ └── format/
│ ├── SVPFormat.h
│ └── SVPFormat.cpp
│
├── encryptor/
│ ├── main.cpp
│ ├── VideoEncryptor.h
│ └── VideoEncryptor.cpp
│
├── player/
│ ├── main.cpp
│ │
│ ├── ui/
│ │ ├── MainWindow.h
│ │ └── MainWindow.cpp
│ │
│ ├── core/
│ │ ├── SVPReader.h
│ │ ├── SVPReader.cpp
│ │ ├── FFmpegPlayer.h
│ │ └── FFmpegPlayer.cpp
│ │
│ └── network/
│ ├── LicenseClient.h
│ └── LicenseClient.cpp
│
├── license-server/
│ ├── main.cpp
│ ├── LicenseService.h
│ ├── LicenseService.cpp
│ ├── KeyService.h
│ └── KeyService.cpp
│
└── tests/
├── CryptoTests.cpp
├── FormatTests.cpp
└── PlaybackTests.cpp
7.4 核心类设计
加密模块:CryptoEngine
class CryptoEngine {
public:
bool encryptChunk(
const std::vector<uint8_t>& plaintext,
const std::vector<uint8_t>& key,
const std::vector<uint8_t>& nonce,
std::vector<uint8_t>& ciphertext,
std::vector<uint8_t>& tag
);
bool decryptChunk(
const std::vector<uint8_t>& ciphertext,
const std::vector<uint8_t>& key,
const std::vector<uint8_t>& nonce,
const std::vector<uint8_t>& tag,
std::vector<uint8_t>& plaintext
);
};
生产实现还需要支持 AAD、参数校验和认证失败时的安全清理。
视频加密模块:VideoEncryptor
class VideoEncryptor {
public:
bool encryptFile(
const std::string& inputPath,
const std::string& outputPath
);
};
加密视频读取模块:SVPReader
class SVPReader {
public:
bool open(const std::string& path);
bool setKey(
const std::vector<uint8_t>& key
);
int read(uint8_t* buffer, int size);
int64_t seek(int64_t offset, int whence);
int64_t size() const;
void close();
};
视频播放模块:FFmpegPlayer
class FFmpegPlayer {
public:
bool open(const std::string& path);
void play();
void pause();
void stop();
void seek(double seconds);
void setVolume(int volume);
void close();
};
在线授权模块:LicenseClient
class LicenseClient {
public:
bool activate(
const std::string& activationCode
);
bool authorizePlayback(
const std::string& videoId
);
bool getVideoKey(
const std::string& videoId,
std::vector<uint8_t>& key
);
};
以上均为架构示例接口,完整实现还需要错误处理、资源管理、异步执行和密钥保护。
八、开发顺序
建议按照以下顺序开发。
第一阶段:实现视频加密
- 使用 OpenSSL 实现 AES-256-GCM。
- 实现视频分块加密。
- 设计 SVP 文件格式。
- 编写视频加密工具。
- 编写解密测试程序。
- 验证文件完整性。
目标:能够将 MP4 加密为 SVP,并正确恢复原文件。
第二阶段:实现视频播放
- 实现 SVPReader。
- 实现内存解密。
- 接入 FFmpeg AVIOContext。
- 实现视频解码。
- 实现音频解码。
- 实现播放、暂停和进度跳转。
目标:能够不生成完整临时 MP4,直接播放 SVP 视频。
第三阶段:开发 Qt 图形界面
- 创建播放器窗口。
- 添加视频显示区域。
- 添加播放控制。
- 添加进度条。
- 添加音量控制。
- 添加全屏功能。
目标:完成可交互的桌面播放器。
第四阶段:实现在线授权
- 开发 C++ 授权服务器。
- 建立用户、设备、视频和授权数据表。
- 实现激活码。
- 实现视频密钥管理。
- 实现 HTTPS 授权接口。
- 播放器接入授权验证。
- 实现设备绑定和授权撤销。
目标:未授权用户无法通过正常播放器获取视频密钥并播放视频。
第五阶段:跨平台测试与优化
- Windows 编译测试。
- macOS 编译测试。
- Linux 编译测试。
- 大文件播放测试。
- 快进与进度跳转测试。
- 音视频同步测试。
- 解密失败与文件篡改测试。
- 内存占用和性能优化。
- 安装包构建和依赖许可证检查。
九、总结
视频加密与专用播放器的核心原理并不复杂,本质上是将密码学加密技术与标准视频解码技术结合。
核心技术链路:
普通视频
|
v
AES-256-GCM 分块加密
|
v
自定义 SVP 加密文件
|
v
专用播放器
|
v
在线授权验证
|
v
获取解密密钥
|
v
SVPReader 按需读取
|
v
AES-GCM 内存解密
|
v
FFmpeg AVIOContext
|
v
音视频解码
|
v
Qt 视频显示和音频输出
最重要的三个技术点:
- AES-256-GCM 分块加密:保证视频文件在没有密钥时无法直接恢复。
- FFmpeg 自定义 AVIOContext:实现边解密边播放,无需完整解密到硬盘。
- 在线授权与密钥管理:将视频密钥管理与播放器分离,实现用户、设备和播放权限控制。
最终采用 C++20 + Qt 6 + OpenSSL + FFmpeg + CMake,构建一套支持 Windows、macOS、Linux 的视频加密工具、专用播放器和在线授权系统。
浙公网安备 33010602011771号