前面几篇讲的都是「怎么让画面又快又稳」,这一篇换个话题:**怎么防止别人把你的播放链接偷走用在别的地方,怎么防止别人伪造一个推流请求冒充你的主播。**
这两个问题看起来都是「做个签名校验」就完了,但它们其实是两类不同的安全需求:播放链接通常**只需要证明没被篡改,内容公开也没关系**;而推流凭据往往**需要携带敏感信息,内容本身要保密**。搞清楚这个区别,才知道为什么专业系统会对这两个场景用两种不同的密码学手段。
> 本文只讲设计原理和常见做法,不涉及任何具体产品的实现细节或密钥格式。落地时请结合自己的威胁模型评估。
一、播放侧防盗链:时间戳 + HMAC
传统 CDN 时代就有、至今仍然有效的防盗链方案是「**规范化资源 + 过期时间 + HMAC**」。
1.1 机制:对「这条流」和「这个过期时间」做认证
客户端拿到的播放链接(或换取播放地址的请求)带一个凭据,里面包含:
-
**过期时间戳**:一个 Unix 秒级时间戳,**明文可见**,不加密;
-
**HMAC 摘要**:用服务端共享密钥对「规范化后的路径 + 过期时间」计算的消息认证码。
服务端收到后,用同样的规则重算一遍摘要,比对通过才放行。
这里要澄清一个常见误解:**HMAC 是共享密钥的消息认证码(MAC),不是公私钥数字签名。** 任何持有密钥的一方都既能生成也能验证,所以这把密钥**绝不能下发到不可信的客户端**。
1.2 三个设计点,决定了它是不是真的防得住
**第一,把 streamName(路径)纳入摘要。** 这是「防盗链」的核心------摘要认证的是「这一条流的路径 + 这个过期时间」,同一个值换一个路径就验证不过。即使有人拿到了 A 直播间的合法链接,也没法原样搬到 B 直播间用。
**第二,过期时间要明文可见。** 链接一旦泄露(被转发、被截图分享),也只在过期时间之前有效,不会变成一个永久可用的后门。
**第三,过期时间不等于防重放。** 带 HMAC 的链接仍然是**持有者凭据(bearer credential)**:攻击者只要在有效期内截获,就能原样重放。所以必须:
-
全程使用 TLS;
-
避免令牌进入 URL、Referer、访问日志和分析系统;
-
设置尽可能短的 TTL;
-
需要「一次性」语义时,引入服务端保存并原子消费的 `jti`/nonce,或把令牌绑定到会话/客户端密钥。
只靠 HMAC 和过期时间,做不到防重放。
1.3 两个容易被忽略的验证细节
-
**常量时间比较。** 验证摘要时不能用提前退出的字符串比较,否则比较函数本身会通过耗时泄漏「匹配了多少前缀」的信息。要使用常量时间比较函数。完整的安全还需要避免其他分支、日志或错误响应形成侧信道。
-
**失败原因区分要按「是否泄露安全信息」来定。** 一个合理做法是:把「已过期」单独返回(过期本身不是秘密,客户端需要据此决定要不要重新取一个签名),而把「签名不匹配」和「资源不存在」收敛成同一个通用错误,不给尝试破解的人提供「改进下一次尝试」的线索。
二、密钥轮换:为什么格式里要预留一个 keyId
一个容易被忽略、但关系到「能不能优雅换密钥」的问题是:**如果签发的凭据里不带密钥标识,轮换密钥会发生什么?**
答案是:**所有已经签发出去、还没过期的旧链接会瞬间全部失效**,因为服务端不知道该用哪把密钥去验证它们。
所以成熟的凭据格式会预留一个 **`keyId`(密钥标识)** 字段,为下面这套轮换流程铺路:
```
时间线 ──────────────────────────────────────────▶
旧密钥 keyId=2026-06 生效
│ 旧链接仍在观众手里,会持续到各自过期
▼
触发轮换:新密钥 keyId=2026-07 开始签发新链接
├─ 新请求 ──▶ 用 keyId=2026-07 签发
└─ 旧链接 ──▶ 服务端仍保留旧密钥,正常验证通过
▼
旧密钥对应的所有链接自然过期
▼
运维侧可以安全下线旧的 keyId
```
要点是:**格式支持 keyId 只是第一步,服务端还得真正实现「按 keyId 查多版本密钥」和「按版本撤销」的存储能力。** 否则 `keyId` 就只是一个没接线的预留字段。规划密钥轮换时,这两件事必须一起做。
此外,旧式方案里常见的 MD5 拼接摘要(`secret + path + time` 直接做 MD5)缺少明确的域分离,**不适合新设计**。新签发应只使用 HMAC-SHA256 这类现代原语,并把 legacy 兼容放进独立开关、设明确下线日期。
三、推流侧:这里用的是「加密」,不是「签名」
推流凭据的需求和播放链接不同。它需要携带更多结构化信息:设备标识、目标应用和流名、绑定的编解码器、签发时间和过期时间。如果用「明文字段 + 签名」,这些字段就都明文暴露在凭据里------签名只能证明「没被篡改」,**做不到「内容保密」**。
所以推流场景通常选另一条路:**用认证加密(AEAD,如 AES-256-GCM)把整个凭据内容加密封装。**
3.1 AEAD 的价值:一次运算同时拿到保密性和防篡改
-
把结构化字段序列化成明文,再用 AES-256-GCM 加密;
-
每次加密生成新的随机 nonce,并把 nonce 与密文一起编码进凭据------**同一密钥下 nonce 必须唯一**,否则会严重破坏机密性和完整性;
-
解密时会自动校验完整性:**任何人篡改了密文,解密这一步就会直接失败**,不需要像签名方案那样额外附加一段独立的签名字段。
这就是它和播放侧 HMAC 的本质区别:**HMAC 是「明文 + 独立签名」两段式,AEAD 是「加密和防篡改揉进同一次运算」。**
3.2 解密之后还要做的校验
解密成功不代表凭据有效。通常还要额外检查:
-
**过期时间**(`exp`)和签发时间(`iat`),以及允许的 clock-skew 容差;
-
**绑定字段是否匹配**:比如凭据里绑定了 H264,拿去请求 HEVC 路径就应该被拒绝,多轨场景下两个编解码器的权限互不越权;
-
**目标应用 / 流 / 声明的 action 和 audience**。
还有一条安全前提:用于加密的密钥必须**由密码学安全的随机源生成、具有足够熵**。如果产品允许人类口令,就不能只是「对配置字符串做个确定性哈希」,而应使用带 salt 和成本参数的 KDF(如 Argon2id / scrypt / PBKDF2),不同用途还应做域分离。
3.3 AEAD 也不防重放
这一点必须强调:**AES-GCM 只提供机密性、完整性和来源真实性,不提供重放防护。** 随机 nonce 解决的是「加密安全所需的唯一性」,不是「防重放」。推流凭据同样是 bearer token:在过期前被窃取就可被重放。高风险场景需要服务端一次性 `jti`/nonce 消费记录,或把凭据绑定到设备持有的密钥。
四、两种机制的对比
| | 播放侧(时间戳 + HMAC) | 推流侧(AEAD 加密) |
| --- | --- | --- |
| 密码学原语 | HMAC-SHA256(共享密钥 MAC) | AES-256-GCM(认证加密) |
| 内容是否明文可见 | 是(路径、过期时间明文,摘要防篡改) | 否(claims 加密封装) |
| 谁需要验证 | 任意持有密钥的一方能自己算并验证 | 只有持有加密密钥的一方能解密 |
| 设计目的 | 轻量、无状态、可自行计算,适合短期播放链接 | 保密 + 防篡改一次到位,适合携带敏感结构化字段 |
| 重放属性 | 有效期内可重放 | 有效期内可重放 |
一句话总结核心判断:**「只需要认证内容、明文公开也没关系」可用 MAC;「内容需要保密且要防篡改」可用认证加密。** 两者都不天然防 bearer 重放,授权范围和凭据生命周期仍需单独设计。
五、总结:安全是边界设计,不是「加个签名」
-
播放侧防盗链靠「路径 + 过期时间的 HMAC」,核心是把**资源路径纳入认证**;
-
过期时间不等于防重放,必须配合 TLS、短 TTL,必要时加 `jti`/nonce 一次性消费;
-
密钥轮换要在凭据格式里预留 `keyId`,并真正实现多版本密钥存储,否则轮换=全部旧链接失效;
-
推流侧用 AEAD 加密,一次拿到保密性和防篡改,但要记住它也不防重放;
-
两类凭据都需要 TLS、短 TTL、严格的 claims 绑定和日志脱敏。
下一篇,我们回到弱网这个老话题:除了协议层的降级,还有哪些工程手段能增强直播的弱网抗性。
**系列导航**
- 上一篇:03 · 传输协议与多档画质(03-传输协议与多档画质-RTMP-SRT-WHIP与Simulcast.md)