17、安全解码:DRM与Widevine、安全通路(Secure Path)、加密流解码、TEE集成
说实话,做视频开发这么多年,最让我头疼的其实不是编码效率,也不是渲染性能,而是——安全解码。
你可能遇到过这种情况:明明播放器写好了,解码也正常,但一播付费内容就黑屏。或者,某些设备上能播,换一台就不行了。嗯,这大概率是 DRM 和安全通路没搞定。
这一章,我就带你把这些硬骨头啃下来。
17.1 DRM 是什么?为什么需要它?
DRM,全称 Digital Rights Management,数字版权管理。说白了,就是防止你偷偷把视频录下来、截屏、或者把解密后的数据拷走。
我参与过一个 OTT 项目,客户要求所有 1080p 以上的内容必须走 Widevine L1 安全级别。当时我还不理解,觉得「不就是播个视频吗?」后来才知道,内容方对安全的要求有多严——一旦泄露,罚款是天文数字。
目前主流的 DRM 方案有几种:
- Widevine:Google 出品,Android 原生支持,分 L1、L2、L3 三个安全级别
- PlayReady:微软的,常用于 Windows 和 Xbox
- FairPlay:苹果的,iOS 和 macOS 上用
- ClearKey:明文密钥,仅用于测试,千万别上生产
在 Android 上,Widevine 是绝对的主流。你想想看,全球几十亿台 Android 设备,内容方不可能为每款机型单独适配,所以 Widevine 成了事实标准。
17.2 Widevine 的三个安全级别
Widevine 分三个级别,我习惯用一句话概括:L3 是软件解密,L2 是部分硬件,L1 是全硬件安全通路。
| 级别 | 解密方式 | 安全通路 | 典型场景 |
|---|---|---|---|
| L3 | 软件解密(CPU) | 无 | 移动端标清内容 |
| L2 | 硬件解密(GPU/VPU) | 部分 | 部分电视盒子 |
| L1 | 硬件解密 + TEE | 完整 | 4K/HDR 付费内容 |
我在项目中踩过一个坑:某款平板设备宣称支持 Widevine L1,但实际测试时发现,它只在特定固件版本下才生效。升级系统后,L1 降级成了 L3。后来查了半天,原来是厂商的 TEE 驱动没跟上。
17.3 安全通路(Secure Path)
安全通路,英文叫 Secure Path。它指的是从解密到渲染的整条链路,都处于硬件保护之下。
你想想看,如果解密后的视频数据能被 CPU 读到,那恶意软件就能直接 dump 出来。所以 L1 的要求是:解密后的数据永远不离开硬件安全区域。
具体流程是这样的:
- 加密流进入 MediaCodec
- MediaCodec 把数据传给硬件解码器
- 硬件解码器从 TEE 获取密钥,在安全区域内解密
- 解密后的 YUV 数据直接送到显示硬件
- 整个过程中,CPU 和普通内存都看不到明文数据
我刚开始做这个的时候,一直有个疑问:那 Surface 怎么拿到数据去渲染?后来才明白,Surface 在 L1 模式下走的也是安全通路,它拿到的只是一个「安全句柄」,不是真正的像素数据。
17.4 加密流解码实战
加密流解码,说白了就是给 MediaCodec 喂数据的时候,多传一个 CryptoInfo。代码其实不复杂,但细节容易出错。
来看一个典型的加密流解码流程:
// 1. 配置 MediaCodec 为安全解码模式
MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, width, height);
format.setFeatureEnabled(MediaCodecInfo.CodecCapabilities.FEATURE_SecurePlayback, true);
// 注意:这个 flag 必须设,否则解码器不会走安全通路
// 2. 创建 CryptoInfo
CryptoInfo cryptoInfo = new CryptoInfo();
cryptoInfo.set(
numSubSamples, // 子样本数量
clearBytes, // 每个子样本的明文字节数
encryptedBytes, // 每个子样本的加密字节数
key, // 密钥(从 DRM 会话获取)
iv, // 初始向量
mode // 加密模式(AES-CTR 或 AES-CBC)
);
// 3. 提交加密数据
int inputIndex = codec.dequeueInputBuffer(TIMEOUT_US);
if (inputIndex >= 0) {
ByteBuffer inputBuffer = codec.getInputBuffer(inputIndex);
// 注意:这里不要直接 put 数据,要用 queueSecureInputBuffer
MediaCodec.QueueRequest request = codec.createQueueRequest(inputIndex);
request.setCryptoInfo(cryptoInfo);
// 设置时间戳、结束标志等
request.setPresentationTimeUs(presentationTimeUs);
request.setFlags(flags);
request.queue();
}
这里有个坑,我当年栽过:CryptoInfo 的 key 和 iv 必须从 DRM 会话中获取,不能自己随便生成。而且 key 的长度通常是 16 字节,iv 也是 16 字节,但有些内容方会用不同的长度,一定要看文档。
17.5 TEE 集成:安全解码的基石
TEE,Trusted Execution Environment,可信执行环境。它是运行在 CPU 中的一个独立安全区域,有自己的操作系统和内存,普通 Android 系统访问不到。
Widevine L1 之所以安全,就是因为密钥和解密操作都在 TEE 里完成。Android 系统本身都不知道密钥长什么样。
TEE 集成通常涉及以下几个部分:
- DRM 插件:在 Java 层通过 MediaDrm API 与 DRM 服务通信
- DRM 服务:系统服务,负责管理 DRM 会话和密钥
- TEE 驱动:内核驱动,负责与 TEE 通信
- TEE 中的 Trusted App:运行在 TEE 里的安全应用,执行实际的解密操作
我曾经调试过一个 TEE 相关的问题:某款设备播放 L1 内容时,每隔几分钟就会卡顿一下。抓了 log 发现,是 TEE 和主系统之间的通信超时了。后来厂商更新了 TEE 固件才解决。
17.6 知识体系总览
说了这么多,我画了一张图帮你理清思路。安全解码的核心就是:密钥在 TEE 里,解密在硬件里,数据不落地。
这张图从上到下,就是一次安全解码的完整链路。每一层都依赖下一层提供安全保障。如果某一层出了问题,比如 TEE 没集成好,那 L1 就会降级成 L3,甚至直接播放失败。
嗯,安全解码这块内容确实比较深,但只要你理解了「密钥在 TEE 里、解密在硬件里、数据不落地」这三个原则,大部分问题都能迎刃而解。
我在实际项目中,最常用的调试手段就是先确认设备支持的 Widevine 级别,然后用 ClearKey 测试解密流程,最后再切到真正的 DRM 系统。这样一步步排查,问题定位会快很多。
公众号:蓝海资料掘金营,微信deep3321