DTLS-SRTP工作原理
WebRTC 的媒体加密由两层协议协作完成:DTLS 负责密钥协商与身份认证,SRTP 负责实际的 RTP 包加密。两者通过 RFC 5764 定义的 use_srtp TLS 扩展绑定在一起,构成浏览器默认强制、无法关闭的端到点加密链路。
本篇只讨论"点到点"(peer-to-SFU 或 peer-to-peer)链路加密。SFU 持有 SRTP 密钥,要做真正的端到端加密见
端到端加密E2EE.md。
1. 证书生成
1.1 为什么需要证书
DTLS 握手要求双方都出示 X.509 证书完成身份认证。但 WebRTC 场景里,对端身份由信令层 + SDP 指纹保证(见第 2 节),证书本身只是承载公钥的载体,所以浏览器一律使用自签名证书,CA 链没有意义。
1.2 算法选型:ECDSA P-256 vs RSA
| 维度 | ECDSA P-256 | RSA 2048 |
|---|---|---|
| 握手 CPU | 低(约 RSA 的 1/5) | 高 |
| 证书体积 | 小(约 250 字节) | 大(约 1200 字节) |
| 握手 RTT 内的包大小 | 不易触发 IP 分片 | 容易触发分片 |
| 浏览器默认 | Chrome / Firefox / Safari 默认 ECDSA | 兼容性回落选项 |
结论:除非要兼容只支持 RSA 的旧设备,统一用 ECDSA P-256。Chrome 自 M52 起默认 ECDSA。
1.3 生命周期
- 浏览器调
RTCPeerConnection时若未传certificates,会临时生成一张,仅当前 PC 生命周期内有效。 - 显式生成的证书可通过
RTCPeerConnection.generateCertificate()拿到,可缓存到 IndexedDB 复用(避免每次新建 PC 都重算 ECDSA 密钥对)。 - 浏览器对自签证书有效期上限是 365 天(W3C 规范要求),超期生成会被拒绝。
- 证书过期不影响正在进行的连接,只影响新建 PC 的握手。
1.4 代码:生成、缓存与复用
/**
* 生成或复用 DTLS 证书。
* 复用动机:ECDSA 密钥对生成约 5~20ms,移动端冷启动累积明显。
*/
async function getOrCreateCertificate() {
const cached = await idbGet('rtc-cert');
if (cached && cached.expires > Date.now() + 60_000) {
return cached.cert;
}
const cert = await RTCPeerConnection.generateCertificate({
name: 'ECDSA',
namedCurve: 'P-256',
// expires 单位毫秒,最大 365 天
expires: 7 * 24 * 60 * 60 * 1000,
});
await idbSet('rtc-cert', { cert, expires: cert.expires });
return cert;
}
const cert = await getOrCreateCertificate();
const pc = new RTCPeerConnection({
iceServers: [...],
certificates: [cert],
});
// 拿指纹用于自检(浏览器实际不暴露 fingerprint 字段,需要从 localDescription 里 parse)
console.log(cert.getFingerprints());
// [{ algorithm: 'sha-256', value: '8A:7F:...:E1' }]
什么时候不适用:iOS Safari 在跨 origin storage partition 下,IndexedDB 可能被清空,复用收益不稳定,可退化为每次现生成。
1.5 失败时的样子
- 用 RSA 1024:现代浏览器握手直接拒绝(
InvalidAccessError)。 - 证书过期:
createOffer()不报错,但握手时对端发bad_certificatealert,连接iceConnectionState卡在checking→failed。 - 时间不同步(设备时钟错乱超 365 天):自签证书
notBefore/notAfter校验失败,表现同上。
2. 指纹交换
2.1 SDP 中的 a=fingerprint
DTLS 证书的指纹(SHA-256 摘要)通过 SDP 在信令层传递:
m=audio 9 UDP/TLS/RTP/SAVPF 111 103
c=IN IP4 0.0.0.0
a=ice-ufrag:F7gI
a=ice-pwd:x9cml/YzichV2+XlhiMu8g
a=fingerprint:sha-256 8A:7F:1C:D2:91:3B:5E:04:6A:E2:7C:43:9F:88:50:21:5C:6B:7A:E8:34:DD:9C:11:F0:42:B6:73:C5:99:E1:90
a=setup:actpass
a=mid:0
a=fingerprint是证书公钥的哈希,不是证书本身。- 算法必须与证书签名算法匹配强度(SHA-256 是当前默认;SHA-1 仍被部分老网关使用,但 RFC 8122 已不推荐)。
a=setup:actpass/active/passive决定 DTLS 角色,见第 3 节。
2.2 与 ICE 的绑定(防 MITM)
只有指纹不够,攻击者完全可以在信令链路里替换指纹 + 公钥完成 MITM。WebRTC 的防护是:
- 信令必须走 wss + 鉴权(业务责任,不在 WebRTC 规范内)。
- ICE 候选交换时 STUN 消息带
MESSAGE-INTEGRITY,用ice-ufrag/ice-pwd鉴权。ice-pwd与 fingerprint 都在同一段 SDP 里,篡改任一项都会失败。 - DTLS 握手完成后,比对对端证书指纹与 SDP 中
a=fingerprint,不一致直接 close。
什么时候不适用:纯 P2P 没有信令服务器(罕见),需要带外信道传指纹,否则无法防 MITM。
2.3 失败时的样子
- 信令篡改指纹:DTLS 握手成功(攻击者出示自己的证书),但浏览器对比 SDP 指纹失败,
connectionState进入failed,控制台报DTLS fingerprint mismatch。 - SHA-1 指纹被对端拒绝:常见于和老旧 SBC/网关互通,需要双方协商升级。
3. DTLS 1.2 握手
3.1 为什么用 DTLS 而不是 TLS
WebRTC 媒体走 UDP,TLS 要求底层有序可靠,所以用 DTLS(RFC 6347)。DTLS 自带:
- 显式序列号(应对乱序)。
- 重传机制(Handshake 消息分片 + 计时重传)。
- HelloVerifyRequest cookie(防 DoS 反射)。
3.2 角色:actpass / active / passive
a=setup:actpass:offer 方默认值,表示"我可以做主动也可以做被动"。a=setup:active:answer 方主动发起 DTLS ClientHello(WebRTC 客户端默认值)。a=setup:passive:被动等待 ClientHello。
WebRTC 客户端永远倾向于 active,SFU 通常 passive。
3.3 完整握手流程
3.4 关键字段解释
| 阶段 | 关键字段 | 作用 |
|---|---|---|
| ClientHello | cipher_suites、extensions.use_srtp | 协商加密套件 + SRTP 套件 |
| HelloVerifyRequest | cookie | 服务端无状态验证客户端可达,防 IP 伪造反射 DoS |
| Certificate | DER 编码 X.509 | 出示自签证书 |
| ClientKeyExchange | ECDHE 公钥 | 完成密钥协商 |
| CertificateVerify | 签名 | 证明私钥归属 |
| Finished | 整段握手 HMAC | 确认握手未被篡改 |
3.5 Cookie 防 DoS
无 cookie 的 DTLS 会成为放大攻击的反射器:攻击者伪造源 IP 发 ClientHello,服务端的 ServerHello + Certificate 体积放大数十倍打到受害者。HelloVerifyRequest 让服务端在确认客户端确实持有源 IP 之前不分配任何状态、不发大包。
什么时候不适用:纯内网部署 + 受信网络可关闭 cookie 检查降低 1 个 RTT,但不推荐。
3.6 失败时的样子
- 没有公共 ECDHE 曲线:
handshake_failurealert。 - 重传 4 次仍超时(默认 60s):DTLS abort,
iceConnectionState→failed。 - 客户端发了 ClientHello 但 ICE 没切到 nominated pair:包丢失,握手卡死。
4. DTLS 1.3 握手
4.1 为什么
DTLS 1.3(RFC 9147,2022 年定稿)把握手压缩到 1-RTT,密钥派生更安全(HKDF 替代 PRF),并去除了 1.2 里多个不安全的设计(RSA 密钥交换、CBC 套件等)。
4.2 1-RTT 握手
花括号 {} 表示已用 handshake traffic key 加密。
4.3 0-RTT 的风险
DTLS 1.3 支持 0-RTT early data(用上次会话的 PSK 立即发数据),WebRTC 不使用 0-RTT:
- 0-RTT 不防重放,攻击者可重放抓到的早期数据。
- WebRTC 媒体首包延迟容忍度足够(ICE 本身就要 RTT),省那一个 RTT 没意义。
结论:实现层应主动禁用 0-RTT。
4.4 新密钥派生
DTLS 1.3 用 HKDF-Expand-Label 派生密钥,SRTP key 提取的 label 仍是 EXTRACTOR-dtls_srtp(RFC 5764 第 4.2 节,DTLS 1.3 沿用),但底层 PRF 改为 HKDF。实现层无感,只要 DTLS 库版本足够新。
4.5 失败时的样子
- 老 SFU 不支持 DTLS 1.3:浏览器自动回落 1.2。
- 中间盒(防火墙)做 DTLS 深检但只认 1.2 record layer:1.3 record header 加密后被误判丢弃,表现为握手卡死,抓包看到 ClientHello 但没有任何回包。
5. SRTP 套件
SRTP(RFC 3711、RFC 7714)定义了三类常见套件:
| 套件 | 类别 | 加密 | 认证 | 推荐度 |
|---|---|---|---|---|
SRTP_AES128_CM_HMAC_SHA1_80 | 流式 | AES-128-CTR | HMAC-SHA1 80bit | 兼容老网关时用 |
SRTP_AES128_CM_HMAC_SHA1_32 | 流式 | AES-128-CTR | HMAC-SHA1 32bit | 不要用,认证太短 |
SRTP_AEAD_AES_128_GCM | AEAD | AES-128-GCM | 内建 | 现代默认 |
SRTP_AEAD_AES_256_GCM | AEAD | AES-256-GCM | 内建 | 高安全需求 |
5.1 为什么 AEAD 优于 CM+HMAC
- AEAD(GCM)一次操作完成加密 + 认证,CPU 友好(硬件加速广泛)。
- 单次 nonce 推导避免 CM+HMAC 各算一次的复杂度。
- HMAC-SHA1-80 截断会限制安全裕度。
5.2 浏览器现状
- Chrome / Edge / Firefox / Safari 自 2019 年起默认协商
AEAD_AES_128_GCM。 - 回落到
AES_CM_128_HMAC_SHA1_80仅当对端不支持 GCM(多见于老 SBC、老版本 freeswitch)。
什么时候不适用:要互通 RFC 3711 严格实现的老网关时,必须保留 SHA1-80 套件,但应监控握手结果,避免长期回落而不自知。
5.3 失败时的样子
- 两端
use_srtp扩展无交集:DTLS 握手成功,但 SRTP 包全部解不开,表现为媒体黑屏 / 静音,getStats看到inboundRtp.packetsReceived增长但framesDecoded为 0。
6. 密钥派生
6.1 Extractor
DTLS 握手完成后,两端各自从 DTLS master secret 用 PRF/HKDF 派生 SRTP 密钥材料:
extractor_output = PRF(master_secret,
"EXTRACTOR-dtls_srtp",
client_random || server_random,
2 * (key_len + salt_len))
输出按顺序切成:
| client_write_SRTP_master_key |
| server_write_SRTP_master_key |
| client_write_SRTP_master_salt|
| server_write_SRTP_master_salt|
| 套件 | key_len | salt_len |
|---|---|---|
| AES_CM_128_HMAC_SHA1_80 | 16 | 14 |
| AEAD_AES_128_GCM | 16 | 12 |
| AEAD_AES_256_GCM | 32 | 12 |
6.2 SRTP session key
master key/salt 再经过 KDF(RFC 3711 §4.3,AES-CM 模式的 PRF)派生出三类 session key:
| 用途 | label |
|---|---|
| 加密 RTP payload | 0x00 |
| RTP 认证 tag | 0x01 |
| RTP salt | 0x02 |
| 加密 SRTCP | 0x03 |
| SRTCP 认证 tag | 0x04 |
| SRTCP salt | 0x05 |
session key 在到达 kdr (key derivation rate) 包数后会自动滚动,避免单 key 用太久。
6.3 失败时的样子
- 两端
client_random/server_random不一致(极少见,通常意味着 DTLS 状态机被破坏):session key 不同,SRTP 解包全失败。 - 实现错把
client_write当server_write用:单向能通、反向全坏,常见于自研 SFU 的早期 bug。
7. SRTCP
7.1 为什么独立加密
SRTCP(RFC 3711 §3.4)承载 RTCP 控制报文(SR/RR/NACK/PLI/REMB/TWCC 等)。它有独立 session key、独立 index、独立认证 tag。原因:
- RTCP 报文频率远低于 RTP(默认带宽 5%),共用 RTP 的滑动窗口会造成 index 难管理。
- RTCP 必须强制认证(认证 tag 长度规范要求 80bit,不像 RTP 可选 32bit)。
7.2 索引与防重放
SRTCP 包头包含一个 31-bit SRTCP index(明文),加密标记位 E(1bit)共同构成 32-bit。接收端维护防重放窗口(默认 64 packets),收到的 index 必须:
- 大于窗口右边界 → 接收 + 滑动窗口;
- 在窗口内且未收过 → 接收 + 标记;
- 已收过或小于左边界 → 丢弃。
SRTCP 包结构(payload 加密前)
+----+-+--------------+---------+
| V P RC PT length | SSRC... | ← RTCP 公共头(明文)
+--------------------+----------+
| 报告/控制内容 | ← 加密体
+--------------------+
| E | SRTCP index (31b) | ← 明文
| Authentication tag (10 字节) | ← HMAC-SHA1-80 / GCM tag
+-------------------------------+
7.3 失败时的样子
- 重放窗口太小(实现错配置成 16):弱网乱序时合法包被误判为重放,NACK 丢失 → 卡顿。
- 实现忘记加密 SRTCP(只加密 SRTP):抓包能看到明文 RTCP,泄露 SSRC 关系、网络估计带宽等信息。
8. 重协商与密钥更新
8.1 为什么要 rekey
- 单 key 加密的数据量有理论上限(GCM 在同一 key 下安全 nonce 数 ~2^32,AES-CM 有 2^48 包上限)。
- 长会议(小时级)通话累积包数接近上限时必须换 key。
8.2 DTLS rekey 方式
| 版本 | 机制 |
|---|---|
| DTLS 1.2 | 触发新一轮握手(renegotiation),用旧 epoch 保护新握手消息 |
| DTLS 1.3 | KeyUpdate 消息(RFC 9147 §4.5.3),无需重新握手 |
DTLS 1.3 的 KeyUpdate 更轻量:单方发送,对端确认后双方各自滚动 traffic secret。SRTP key 通过 extractor 重新派生。
8.3 浏览器是否会主动 rekey
Chrome / Firefox 在普通 1v1 通话中不主动 rekey,因为单次会话很难触达套件上限。SFU 在长会议(>4 小时)场景下应主动监控并触发。
8.4 失败时的样子
- 一端 rekey 一端没切:旧 key 还在解码新包,packetsLost 飙升后通话断开。
- 实现里 rekey 时未做"过渡窗口"(一段时间内同时尝试新旧 key):rekey 边界丢包率短暂飙升。
9. 与 ICE/STUN 的交互顺序
9.1 严格顺序
9.2 为什么 DTLS 必须在 ICE 之后
- ICE 决定走哪条路径(host / srflx / relay)。在 nominated pair 之前发 DTLS 包属于"对不确定的目标握手",浪费且容易被 NAT 丢。
- DTLS 握手包大(含证书),ICE 未通时大概率 MTU 受限被丢。
- 实现上 ICE agent 选 pair 后才把 socket 绑给 DTLS 层。
9.3 ICE restart 是否影响 DTLS
ICE restart 只换 ice-ufrag / ice-pwd,不影响已建立的 DTLS 会话。SRTP key 不变,媒体不中断。这也是 ICE restart 能做无缝网络切换的前提。
9.4 失败时的样子
- 实现把 DTLS ClientHello 在 ICE 之前发:被 NAT 丢,握手永远超时。
- ICE 还在
checking,应用层尝试发 RTP:浏览器内部 SRTP 还没准备好,包直接丢,无任何报错。
10. Wireshark 抓包字段
抓包时关注以下字段(Wireshark filter:dtls || stun || srtp):
[ STUN Binding Request ]
Message Type : 0x0001
USERNAME : ufragB:ufragA
MESSAGE-INTEGRITY: <HMAC-SHA1 of ice-pwd>
PRIORITY : 0x6E7F1EFF
USE-CANDIDATE : (present in nominated check)
FINGERPRINT : 0x...
[ DTLS 1.2 ClientHello ]
Version : DTLS 1.2 (0xFEFD)
Random : 32 bytes
Session ID : 0
Cookie : 0 (first hello) / N bytes (after HelloVerifyRequest)
Cipher Suites : TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, ...
Extension: use_srtp
Protection Profile: SRTP_AEAD_AES_128_GCM (0x0007)
Protection Profile: SRTP_AES128_CM_HMAC_SHA1_80 (0x0001)
Extension: supported_groups
Named Group: secp256r1
[ DTLS Certificate ]
Certificate Length: 280
Signature Algorithm: ecdsa-with-SHA256
Subject : CN=WebRTC
Issuer : CN=WebRTC ← 自签 = 同一个
Subject Public Key: id-ecPublicKey, secp256r1
[ SRTP ]
RTP Payload Type : 111 (opus)
Sequence : 13422
Timestamp : 384720
SSRC : 0x1A2B3C4D
SRTP Encrypted Payload: <bytes>
SRTP Auth Tag : 16 bytes (GCM tag)
Wireshark 解 SRTP 需要导入 SRTP master key(从 Chrome chrome://webrtc-internals 的 getStats 或专门的 SSLKEYLOGFILE 导出)。生产环境禁止导出 key 用于调试。
11. 反模式
| 反模式 | 后果 | 应该怎么做 |
|---|---|---|
| 用 365 天上限的过期证书 | 新建 PC 直接失败 | 缓存证书时校验 expires,提前 1 天刷新 |
不校验 SDP a=fingerprint | MITM 可解密全部媒体 | 浏览器默认会校验,自研 SFU 也必须做 |
信令走 ws:// | 指纹被替换 → MITM | 强制 wss:// + 服务端鉴权 |
用 SRTP_AES128_CM_HMAC_SHA1_32 | 32bit 认证 tag 易被伪造 | 至少用 SHA1-80,新部署直接上 GCM |
| 把 DTLS 握手放在 ICE 之前 | 握手包被丢,连接超时 | DTLS 必须等 ICE nominated pair |
| 启用 DTLS 1.3 0-RTT | early data 可被重放 | WebRTC 场景一律禁用 0-RTT |
| 长会议不监控 rekey | 触达套件包数上限崩溃 | SFU 监控每 SSRC 包数,主动触发 rekey |
| 用 RSA 1024 / SHA-1 指纹 | 现代浏览器直接拒绝 | ECDSA P-256 + SHA-256 |
| 自研 SFU 共用 client/server key | 单向通另一向坏 | 严格按 RFC 3711 §4.3 区分方向 |
调试时打开 SSLKEYLOGFILE 上线 | 生产媒体可被解密 | 仅本地调试环境启用 |
12. 自检清单
- 客户端用 ECDSA P-256 自签证书,缓存且不超过 30 天复用
- SDP 中存在
a=fingerprint:sha-256 ...,且与对端实际证书匹配 - ICE nominated pair 选定后才看到 DTLS ClientHello
- DTLS 协商出
SRTP_AEAD_AES_128_GCM或更强 - 信令链路
wss://+ JWT 鉴权 - SFU 长会议主动 rekey 策略已上线
- DTLS 1.3 部署时 0-RTT 已禁用
- 抓包工具与
SSLKEYLOGFILE仅限本地调试
13. 权威资料
- RFC 5763 Framework for Establishing a SRTP Security Context Using DTLS: https://www.rfc-editor.org/rfc/rfc5763
- RFC 5764 DTLS Extension to Establish Keys for SRTP: https://www.rfc-editor.org/rfc/rfc5764
- RFC 8827 WebRTC Security Considerations: https://www.rfc-editor.org/rfc/rfc8827
- RFC 3711 The Secure Real-time Transport Protocol (SRTP): https://www.rfc-editor.org/rfc/rfc3711
- RFC 7714 AES-GCM Authenticated Encryption in SRTP: https://www.rfc-editor.org/rfc/rfc7714
- RFC 9147 DTLS Protocol Version 1.3: https://www.rfc-editor.org/rfc/rfc9147
- RFC 8122 Connection-Oriented Media over TLS in SDP: https://www.rfc-editor.org/rfc/rfc8122
- W3C WebRTC 1.0: https://www.w3.org/TR/webrtc/
- W3C WebRTC
RTCCertificate: https://www.w3.org/TR/webrtc/#rtccertificate-interface - 核对日期:2026-06-22