Skip to main content

WebRTC 全景实战 (7):DTLS 握手与 SRTP 加密体系

Rainy
雨落无声,代码成诗 —— 致力于技术与艺术的极致平衡
Rainy
22 MIN READ... VIEWS

Ch5 ICE 连通 UDP 通道后,媒体并非明文传输。WebRTC 强制走 DTLS 握手 → 导出 SRTP 密钥 → 加密 RTP,这是 RFC 8827 WebRTC Security Architecture 的核心要求。

理解这一层的设计意图:Gmail 语音视频聊天时代,安全是事后补丁;WebRTC 从标准化之初就将 默认加密 作为不可协商的底线(参见 Curious — WebRTC 历史 中「安全是重中之重」)。Google 工程师在 2010 年前后推动 DTLS-SRTP 成为唯一媒体保护方案,彻底告别了早期 VoIP 明文 RTP 的惯例。

Curious 历史 中,Serge Lachapelle 回忆:收购 GIPS 后,团队内部曾激烈讨论「是否允许开发者关闭加密」。最终结论是一刀切——媒体必须加密,没有开关。这一决策直接写进了 RFC 8827a=crypto(SDES)被废弃,DTLS-SRTP 是唯一允许的密钥协商方式。对开发者而言,这意味着你永远不会在 chrome://webrtc-internals 里看到明文 RTP 载荷——除非主动做 E2EE 之上的二次加密。

本章覆盖 RFC 6347 DTLS 握手、RFC 5764 密钥导出、RFC 3711 SRTP 包处理、Certificate Fingerprint 验证,以及 Insertable Streams 端到端加密入门。


本篇术语表

术语英文解释
DTLSDatagram Transport Layer Security在 UDP 上运行的 TLS 变体,RFC 6347 定义
SRTPSecure Real-time Transport ProtocolRTP 的加密扩展,RFC 3711 定义
SRTCPSecure RTCPRTCP 控制包的加密版本,与 SRTP 共用密钥材料
DTLS-SRTP从 DTLS 握手导出 SRTP 密钥的 profile,RFC 5764
FingerprintCertificate FingerprintSDP a=fingerprint 中声明的证书哈希,绑定信令与 DTLS
Master SecretDTLS 握手协商出的主密钥,经 KDF 导出 SRTP 密钥
SRTP Master Key用于加密单个 RTP 会话的对称密钥(128/256 bit)
SRTP Salt与 Master Key 配合使用的盐值,参与 IV 派生
Authentication TagSRTP 包尾的 MAC,验证完整性与真实性
AEADAuthenticated Encryption with Associated Data现代 SRTP 加密模式(AES-GCM),同时提供加密与认证
use_srtpDTLS Extension告知对端握手完成后密钥用于 SRTP 而非 DTLS 应用数据
setupSDP AttributeDTLS 角色:active/passive/actpass
Hop-by-hop默认模式:SFU 解密再加密,中间节点可见明文
E2EEEnd-to-End Encryption端到端加密,中间 SFU 无法解密
Insertable StreamsWebRTC Encoded Transform API,在编码帧上插入自定义加密

一、安全栈在协议栈中的位置

WebRTC 媒体路径上的安全是分层的:ICE 只解决「包能送到哪」,DTLS 解决「密钥如何协商」,SRTP 解决「每个 RTP 包如何加密」。

标准作用
DTLSRFC 6347在 UDP 上完成 TLS 式握手,协商密钥
DTLS-SRTPRFC 5764从 DTLS 导出 SRTP 会话密钥
SRTPRFC 3711RTP 包加密(AES-CM / AEAD)与认证(HMAC-SHA1)
SRTCPRFC 3711RTCP 控制包同样加密
与 HTTPS 的对比

HTTPS = TLS over TCP,WebRTC = DTLS over UDP。UDP 不保证顺序和可靠送达,DTLS 必须处理丢包、重传和乱序——这也是为什么 DTLS 握手必须在 ICE 连通之后、RTP 发送之前完成。

Ch6 Data Channel 对比:Data Channel 走 SCTP over DTLS(应用数据在 DTLS 记录层内传输),而音视频走 DTLS-SRTP(DTLS 只用于密钥协商,媒体数据在 SRTP 层加密后直发 UDP)。

1.1 为什么不用 TLS?

TCP 的三次握手 + 队头阻塞与实时媒体不兼容。ICE 已经选定了 UDP 五元组,在此之上叠加 DTLS 是自然选择。DTLS 1.2 是 WebRTC 的强制基线;DTLS 1.3 在部分实现中逐步引入,但核心密钥导出逻辑仍遵循 RFC 5764 profile。

1.2 SAVPF 与 SRTP 的关系

Ch4 SDP 中 m-line 的 UDP/TLS/RTP/SAVPF 已经声明了完整协议栈:

  • TLS → 实际指 DTLS
  • SAVP → Secure AV Profile(SRTP)
  • F → Feedback(RTCP 反馈,Ch8 详解)

二、Certificate Fingerprint:SDP 与 DTLS 的绑定

WebRTC 不使用传统 CA 证书链验证对端身份,而是用 自签名证书 + SDP 指纹交叉验证。这是 WebRTC 安全模型最独特的设计之一。

2.1 SDP 中的指纹与角色

a=fingerprint:sha-256 4A:AD:BA:62:79:B9:59:F4:7D:BD:65:F4:4C:87:3D:F4:2B:2B:5E:9E:8D:8E:8B:8A:8C:8D:8E:8F:90:91:92
a=setup:actpass
setup含义典型场景
active本端主动发起 DTLS ClientHelloAnswer 方常设为 active
passive本端等待对端发起被动等待方
actpass可主可客Offer 方常用

角色协商规则:Offer 方声明 actpass,Answer 方选择 activepassive,最终一方为 Client、一方为 Server。双方不能同时 passive——否则握手永不开始。

2.2 指纹算法

WebRTC 要求支持 SHA-256a=fingerprint:sha-256)。旧实现可能还支持 SHA-1,但现代浏览器已弃用。指纹计算方式:

fingerprint = hash_algorithm( DER_encoded_certificate )

DER 是证书的 ASN.1 二进制编码,不是 PEM 文本。浏览器在 createOffer() 时自动将指纹写入 SDP,开发者通常无需手动计算。

2.3 设计意图与安全边界

信令通道(WebSocket)可能是 HTTP 而非 HTTPS,指纹机制确保即使信令被窃听,攻击者也无法在 DTLS 层冒充对端——因为 SDP 中的指纹与 DTLS 证书必须一致。

指纹验证的局限

指纹机制防的是 DTLS 层的中间人,不防 信令层的冒充。如果攻击者能篡改 SDP 中的 fingerprint 和 ICE 凭证,仍可实施 MITM。因此生产环境信令必须使用 WSS + 身份认证。

2.4 读取本地证书信息

// chrome://webrtc-internals 中可查看,也可用 getStats 间接获取
pc.addEventListener("connectionstatechange", async () => {
if (pc.connectionState === "connected") {
const stats = await pc.getStats();
stats.forEach((report) => {
if (report.type === "transport") {
console.log("DTLS state:", report.dtlsState);
console.log("Selected cipher:", report.tlsVersion, report.dtlsCipher);
}
});
}
});

三、DTLS 握手完整时序(RFC 6347)

DTLS 在 UDP 上的握手与 TLS 类似,但增加了 Epoch/Sequence NumberHelloVerifyRequest(防 DoS)和 分片重传 机制。

3.1 use_srtp 扩展

use_srtp 是 DTLS 握手的核心扩展,告知对端:握手完成后密钥将用于 SRTP 而非普通 DTLS 应用数据。扩展结构(RFC 5764):

use_srtp extension:
SRTPProtectionProfile profiles[] // 如 AES128_CM_HMAC_SHA1_80
MKI (optional)

WebRTC 中常见的 SRTP Protection Profile:

Profile加密认证说明
AES128_CM_HMAC_SHA1_80AES-128 Counter ModeHMAC-SHA1 80-bit tag经典 SRTP,RFC 3711
AES128_CM_HMAC_SHA1_32AES-128 CMHMAC-SHA1 32-bit tag较短 tag,较少使用
AEAD_AES_128_GCMAES-128 GCM内建认证现代 AEAD 模式
AEAD_AES_256_GCMAES-256 GCM内建认证更强密钥长度

ClientHello 和 ServerHello 中各自声明支持的 profile 列表,最终取交集中优先级最高者。

3.2 DTLS 与 ICE 的时序依赖

关键约束

  1. DTLS 握手在 ICE connected 之后才开始
  2. SRTP 加密在 DTLS connected 之后才开始
  3. 浏览器在 DTLS 完成前不会发送可解码的 RTP 媒体

3.3 DTLS 重传与丢包

UDP 上 DTLS 记录可能丢失。RFC 6347 规定:

  • 发送方维护重传定时器
  • 收到重复的握手消息时静默丢弃(状态机已前进)
  • 应用数据(Finished 之后)由上层处理丢包

在弱网环境下,DTLS 握手可能比 ICE 连通多花数百毫秒。chrome://webrtc-internals 中可观察 dtlsStatenewconnectingconnected 的过渡。

3.4 DTLS 记录层(RFC 6347 Section 4)

DTLS 在 UDP 上复用 TLS 记录格式,但每条记录前增加 Epoch(2 字节)Sequence Number(48 bit)RFC 6347 §4):

Length 之后为 Fragment 载荷;Certificate 等握手消息超过 MTU 时会按 RFC 6347 §4.1 分片,每条分片复用同一 Sequence Number 并携带 Fragment Offset。

Content Type含义
change_cipher_spec20通知对端切换到协商好的密码套件
alert21警告或致命错误(如 fingerprint 不匹配)
handshake22ClientHello / Certificate / Finished 等
application_data23Data Channel 的 SCTP 载荷(不是 SRTP)

Epoch 机制:握手完成前 Epoch=0,ChangeCipherSpec 之后 Epoch=1。接收方按 Epoch 维护独立的 anti-replay 窗口——这与 SRTP 的重放保护是两套独立机制。

分片与重组:UDP MTU 通常 1200–1500 字节,大型 Certificate 消息会被 DTLS 分片。若中间防火墙丢弃 oversized UDP 包,常见症状是 DTLS 永远停在 connecting——排查时可对比 TURN relay 与 host 直连路径。

3.5 自定义证书(RTCCertificate)

默认情况下,浏览器为每个 PeerConnection 自动生成 ECDSA 自签名证书。如需固定证书(例如 SFU 需要预注册 fingerprint),可使用 RTCPeerConnection.generateCertificate()

const cert = await RTCPeerConnection.generateCertificate({
name: "ECDSA",
namedCurve: "P-256",
});

const pc = new RTCPeerConnection({
iceServers: ICE_SERVERS,
certificates: [cert],
});
证书有效期

generateCertificate 生成的证书默认有效期约 30 天(实现相关)。长会话若跨越证书过期需 renegotiation。生产 SFU 应能处理对端证书轮换而不中断媒体。


四、SRTP 密钥导出(RFC 5764)

DTLS 握手完成后,双方各持有一个 DTLS Master Secret。RFC 5764 定义了标准的密钥导出函数(KDF),将其变换为 SRTP 会话密钥。

4.1 导出公式

RFC 5764 Section 4.2 定义的伪代码:

SRTP_key = TLS-PRF(master_secret, "EXTRACTOR-dtls_srtp", random1 + random2)[0..key_len-1]
SRTP_salt = TLS-PRF(master_secret, "EXTRACTOR-dtls_srtp", random1 + random2)[key_len..key_len+salt_len-1]

其中 random1random2 是 ClientHello 和 ServerHello 中的 client_random / server_random。Client 和 Server 各自导出对方写方向的密钥——即 A 用 Server Write Key 解密 B 发来的包,用 Client Write Key 加密自己发出的包。

4.2 密钥材料长度

ProfileKey LengthSalt Length
AES128_CM_HMAC_SHA1_8016 bytes (128 bit)14 bytes (112 bit)
AEAD_AES_128_GCM16 bytes12 bytes (96 bit)
AEAD_AES_256_GCM32 bytes (256 bit)12 bytes

4.3 密钥生命周期

  • 每次 PeerConnection 重建(重新 offer/answer)会生成新证书新密钥
  • ICE restart 如果伴随 renegotiation,同样轮换密钥
  • 没有「会话内 rekey」——长通话密钥不变(E2EE 方案可能自行实现 rekey)

4.4 方向性:谁加密、谁解密

RFC 5764 的密钥命名遵循 TLS 惯例——Client Write KeyServer Write Key 分别用于 Client→Server 和 Server→Client 方向。WebRTC 中:

接收端用对端 Write Key 解密——A 发送时用 A 的 Write Key 加密,B 用对应的 Read Key(即 A 的 Write Key)解密。搞混方向是自建 SFU 时最常见的 SRTP 解密失败原因。


五、SRTP 包处理(RFC 3711)

SRTP 在 RTP 包头之后、UDP 载荷末尾添加加密与认证,不改变 RTP 头本身的语义(SSRC、Seq、Timestamp 仍明文)。

线上 SRTP 包布局(RFC 3711)——RTP 头明文,载荷加密,尾部追加认证标签:

AEAD_AES_128_GCM 模式下 Auth Tag 为 128 bit,GCM 认证内建于加密过程,不再单独追加 HMAC。

5.1 IV(初始化向量)派生

AES Counter Mode 下,每个包的 IV 由以下输入派生:

k_s = session key
salt = session salt
SSRC = RTP 同步源
index = RTP sequence number (作为 counter)

重要:SSRC 和 Seq 虽在 RTP 头中明文传输,但攻击者无法据此解密——没有 session key 和 salt 就无法恢复 counter 值。

5.2 认证标签

模式Tag 长度位置
HMAC-SHA1_8010 bytes (80 bit)RTP 载荷末尾
HMAC-SHA1_324 bytes (32 bit)RTP 载荷末尾
AEAD_GCM16 bytes (128 bit)内建于 GCM

接收端验证 tag 失败 → 静默丢弃包,不触发重传。这与 Ch8 中 NACK 丢包机制形成互补:SRTP 丢弃是安全策略,NACK 丢包是网络策略。

5.3 SRTCP 加密

RTCP 包(SR/RR/NACK/PLI 等)使用同一套密钥材料加密,称 SRTCP。SRTCP trailer 含 SRTCP Index(32 bit) 与可选 MKI

a=rtcp-mux 下 RTP 和 SRTCP 在同一 UDP 端口,通过包类型区分(RTP PT vs RTCP PT 范围 200-207)。

5.4 重放保护

SRTP 维护一个 重放窗口(默认 64 或 128 包)。序号落在窗口之外的包被视为重放攻击而丢弃。WebRTC 中 RTP Seq 16 bit 循环,窗口足够覆盖正常乱序。

5.5 ROC:Rollover Counter 与包索引

RTP Sequence Number 只有 16 bit,约 65536 个包后回绕。SRTP 内部维护 ROC(Rollover Counter) 将 seq 扩展为 48 bit 的 packet index

packet_index = (ROC << 16) | sequence_number

IV 派生和重放检测都基于 packet index,而非裸 seq。当 seq 从 65535 跳到 0 时,ROC 递增。自建媒体服务器若未正确维护 ROC,会在 seq 回绕时出现大面积解密失败——症状是「通话约 20 分钟后突然无声无画」。

概念位数用途
RTP Sequence Number16 bit线上可见,丢包检测
ROC32 bitSRTP 内部,处理 seq 回绕
Packet Index48 bitIV 派生、重放窗口

5.6 MKI(Master Key Index)

RFC 3711 允许在 SRTP 包中携带 MKI 字段,标识使用哪组 Master Key——用于会话内 rekey。WebRTC 浏览器实现不使用 MKI(每次 renegotiation 整体换密钥),但阅读 Wireshark 或自建 SFU 源码时会遇到此字段。


六、E2EE:从 Hop-by-hop 到端到端

默认 SRTP 保护的是 传输链路,不是 端到端内容。SFU 必须解密 RTP 才能做路由、转码、混流。

模式SFU 能否看到明文密钥管理适用场景
默认 SRTP能(SFU 参与 DTLS-SRTP)浏览器自动生成一般会议
Insertable Streams E2EE不能应用层分发医疗、金融、高合规
SFrame (Frame Encryption)不能集中式或 MLSZoom 等商用方案

SFramedraft-ietf-mmusic-sframe)是 IETF 标准化的帧级 E2EE 方案,与 Insertable Streams 思路类似但定义了统一的帧头格式和密钥轮换语义。Zoom、Google Meet 的部分 E2EE 模式基于 SFrame 或变体。浏览器原生 API 仍以 Insertable Streams / Encoded Transform 为主——应用层可自行实现 SFrame 封包格式。

6.1 Insertable Streams / Encoded Transform

Insertable Streams API(现称 WebRTC Encoded Transform)允许在编码后、网络发送前插入自定义加密层:

// 发送端:在 Worker 中注册 Transform
const worker = new Worker("e2ee-worker.js");
const sender = pc.getSenders().find((s) => s.track?.kind === "video");

if ("transform" in sender) {
sender.transform = new RTCRtpScriptTransform(worker, {
operation: "encrypt",
key: derivedKey, // 应用层协商的 E2EE 密钥
});
}

// 接收端
const receiver = pc.getReceivers().find((r) => r.track?.kind === "video");
if ("transform" in receiver) {
receiver.transform = new RTCRtpScriptTransform(worker, {
operation: "decrypt",
key: derivedKey,
});
}
// e2ee-worker.js 简化示例
onrtctransform = (event) => {
const { transformer } = event;
const reader = transformer.readable.getReader();
const writer = transformer.writable.getWriter();

async function process() {
while (true) {
const { value: frame, done } = await reader.read();
if (done) break;

// frame 是 EncodedVideoFrame / EncodedAudioFrame
const encrypted = await encryptFrame(frame, key);
await writer.write(encrypted);
}
}
process();
};

6.2 E2EE 密钥分发

SRTP 密钥由 DTLS 自动协商,但 E2EE 密钥需要应用层分发:

常见方案:MLS(Messaging Layer Security)、Signal 的 Double Ratchet、或会议加入时由主持人分发对称密钥。

6.3 E2EE 的代价

能力默认 SRTPE2EE
SFU 转码支持不支持(密文无法解码)
服务端录制支持需客户端录制或密钥托管
服务端混流支持不支持
带宽自适应SFU 可看内容决策仅依赖 TWCC/GCC

6.4 生产环境安全清单

检查项要求常见失误
信令通道WSS + 身份认证HTTP 明文 WebSocket
SDP 完整性签名或 E2E 加密信令中间人篡改 fingerprint
TURN 凭证短期 token,按 room 隔离长期共享 username/password
证书轮换SFU 支持 renegotiation固定密钥永不轮换
E2EE 密钥独立于 SRTP,前向安全复用 SRTP 密钥做 E2EE
日志脱敏不记录 SDP 全文日志泄露 ice-pwd

七、常见问题与排查

问题原因解决
DTLS 握手超时ICE 未真正连通 / 防火墙阻断 UDP检查 ICE state、TURN 配置
dtlsState: failedfingerprint 不匹配确保 SDP 未被篡改;信令用 WSS
双方同时 passiveAnswer 未正确设置 setup:active检查 SDP setup 属性
SRTP 解密失败密钥导出 profile 不匹配确认双方支持相同 SRTP profile
音视频无声无画但 ICE connectedDTLS 未完成等待 connectionState === 'connected'
E2EE 花屏Transform 中帧边界处理错误保留 frame metadata(timestamp, type)
证书频繁轮换每次 renegotiation 生成新证书正常行为;注意 SFU 需处理 re-key

7.1 Wireshark 解密技巧

Wireshark 可通过 Edit → Preferences → Protocols → DTLS 配置 Pre-Master-Secret log,但浏览器不直接导出。更实用的方法:

  1. 过滤 dtls 观察握手消息
  2. 过滤 stun || dtls || rtp 看完整建立过程
  3. chrome://webrtc-internals 导出 SDP 对比两端 fingerprint

7.2 fingerprint 不匹配实验

// 仅用于 Lab 调试,不要在生产使用
const offer = await pc.createOffer();
// 篡改 fingerprint 中的一个字节
offer.sdp = offer.sdp.replace(
/(a=fingerprint:sha-256 )([0-9A-F]{2})/,
"$1FF" // 将第一个字节改为 FF
);
await pc.setLocalDescription(offer);
// 预期:对端 DTLS 握手失败,connectionState → failed

八、实战 Lab

Lab 1:观察 DTLS 握手

  1. 建立 P2P 连接(可用 Ch2 的 demo)
  2. Wireshark 过滤 dtls,观察 ClientHello → ServerHello → Certificate → Finished
  3. 在 ClientHello 中展开 use_srtp 扩展,记录协商的 Protection Profile

Lab 2:指纹篡改实验

  1. 在信令服务器中拦截 SDP,修改 a=fingerprint 的一个十六进制字符
  2. 观察浏览器控制台 / webrtc-internalsdtlsState 变为 failed
  3. 恢复正确指纹,确认连接成功

Lab 3:getStats 监控 DTLS 状态

setInterval(async () => {
const stats = await pc.getStats();
stats.forEach((r) => {
if (r.type === "transport") {
console.log({
dtlsState: r.dtlsState,
selectedCandidatePairId: r.selectedCandidatePairId,
bytesSent: r.bytesSent,
bytesReceived: r.bytesReceived,
});
}
});
}, 1000);

Lab 4:E2EE Transform 骨架

  1. 创建 e2ee-worker.js,实现 passthrough transform(不加密,只透传帧)
  2. 注册到 sender/receiver 的 transform 属性
  3. 确认视频通话仍正常——验证 Transform 管线可用
  4. 逐步加入 AES-GCM 加密逻辑

Lab 5:对比 Data Channel 与 SRTP 的 DTLS 用法

  1. 同一 PeerConnection 上同时开启音视频 + Data Channel
  2. Wireshark 中观察:Data Channel 消息在 DTLS Application Data 中,RTP 在 SRTP 加密层
  3. 理解「一个 DTLS 会话,两种数据路径」

九、本章小结

要点内容
安全底线WebRTC 媒体强制加密,不可协商关闭 SRTP
身份验证自签名证书 + SDP fingerprint 交叉验证,非 CA 链
密钥来源DTLS 握手 → RFC 5764 KDF → SRTP Master Key + Salt
加密范围RTP 载荷加密 + 认证;RTP 头明文
E2EEInsertable Streams 在 SRTP 之内再加一层,SFU 无法解密

下一篇(Ch8)RTP/RTCP 媒体传输——SRTP 加密保护的 RTP 包如何承载 Opus/VP8 帧,以及 RTCP 如何反馈丢包与驱动拥塞控制。


系列导航

章节主题状态
0架构全景与协议栈地图✅ 已发布
1浏览器媒体 API 与设备管理✅ 已发布
2第一个 P2P 视频通话✅ 已发布
3信令服务器设计与会话状态机✅ 已发布
4SDP 解剖与媒体协商✅ 已发布
5ICE、STUN、TURN 与 NAT 穿透✅ 已发布
6Data Channel 与 SCTP over DTLS✅ 已发布
7DTLS 握手与 SRTP 加密体系✅ 已发布
8RTP/RTCP 媒体传输与 QoS✅ 已发布
9音视频编解码与 Simulcast 入门✅ 已发布
10带宽估计与拥塞控制 GCC✅ 已发布
11Simulcast、SVC 与选择性订阅✅ 已发布
12SFU/MCU/Mesh 架构与 Pion 实战✅ 已发布
13调试工具链与可观测性✅ 已发布
14TURN 集群部署与多区域扩展✅ 已发布
15Capstone 生产级视频会议系统✅ 已发布

References

Logo
RainLib

Exploring the frontiers of technology, design, and distributed systems. Building tools for the future developers.

Suggestions & Feedback

© 2026 RainLib. Built for the Future.
All rights reserved.
System Normal