跳到主要内容

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 8827:a=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 Secret—DTLS 握手协商出的主密钥,经 KDF 导出 SRTP 密钥
SRTP Master Key—用于加密单个 RTP 会话的对称密钥(128/256 bit)
SRTP Salt—与 Master Key 配合使用的盐值,参与 IV 派生
Authentication Tag—SRTP 包尾的 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 Streams—WebRTC 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 方选择 active 或 passive,最终一方为 Client、一方为 Server。双方不能同时 passive——否则握手永不开始。

2.2 指纹算法​

WebRTC 要求支持 SHA-256(a=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 Number、HelloVerifyRequest(防 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 中可观察 dtlsState 从 new → connecting → connected 的过渡。

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]

其中 random1 和 random2 是 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 Key 和 Server 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 等商用方案

SFrame(draft-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-internals 中 dtlsState 变为 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

探索技术、设计与分布式系统的边界。构建面向未来的开发者工具。

留言与建议

© 2026 RainLib. 为未来构建。(Built for the Future)
版权所有。
系统正常