【WebRtc】DTLS-SRTP 加密完整流程详解

WebRTC 强制要求所有媒体流加密。DTLS-SRTP 是唯一合规方案:用 DTLS 握手协商密钥,用 SRTP 加密 RTP 媒体包。


一、为什么 WebRTC 必须加密?

RFC 8827 明确规定:所有 WebRTC 媒体流必须使用 SRTP 加密,且密钥必须通过 DTLS 协商(不允许静态预共享密钥)。

这意味着:

  • 没有 DTLS 握手 = 没有媒体流
  • PeerConnectionState.CONNECTED 才是真正的连接完成标志(不是 ICE CONNECTED)
  • 代码里 "DTLS connected" 才触发通话建立
java 复制代码
// CallActivity.java
// ICE 连通不代表通话建立
// 真正的里程碑是 DTLS 握手成功:
if (newState == PeerConnectionState.CONNECTED) {
    logAndToast("DTLS connected, delay=" + delta + "ms");  // line 924
}

二、完整握手时序

scss 复制代码
发起方 (Initiator / DTLS Client)          接收方 (Responder / DTLS Server)
         |                                         |
 [ICE 连通,UDP 通道就绪]                [ICE 连通,UDP 通道就绪]
         |                                         |
         |--- DTLS ClientHello ------------------>|
         |       (支持的密码套件, 随机数)             |
         |                                         |
         |<-- DTLS ServerHello -------------------|
         |       (选定密码套件, 服务端证书, 随机数)    |
         |                                         |
  [验证证书指纹 vs SDP a=fingerprint]        [验证证书指纹 vs SDP a=fingerprint]
         |                                         |
         |--- DTLS ClientKeyExchange ------------>|
         |--- DTLS CertificateVerify ------------->|
         |--- DTLS ChangeCipherSpec ------------->|
         |--- DTLS Finished --------------------->|
         |                                         |
         |<-- DTLS ChangeCipherSpec --------------|
         |<-- DTLS Finished ----------------------|
         |                                         |
  [双方从 DTLS master secret 导出 SRTP 密钥]
         |                                         |
         |=== SRTP 加密的 RTP/RTCP 媒体流 =======>|

三、证书与指纹:防中间人攻击

DTLS 证书类型

WebRTC 引擎在 PeerConnectionFactory 初始化时自动生成自签名证书,本项目使用 ECDSA(比 RSA 快 10 倍握手):

java 复制代码
// PeerConnectionClient.createPeerConnectionInternal()
rtcConfig.keyType = PeerConnection.KeyType.ECDSA;  // line 606

ECDSA 密钥更短(256 bit vs 2048 bit),握手计算量更低,移动端友好。

指纹验证流程

  1. 本端在创建 PeerConnection 时自动生成证书,提取 SHA-256 指纹
  2. 指纹写入 SDP,通过信令发给对端:
ini 复制代码
// SDP 中的指纹字段
a=fingerprint:sha-256 4A:6F:2B:...(64个十六进制字节)
a=setup:actpass
  1. DTLS 握手时,收到对端真实证书后,计算其 SHA-256 指纹,与 SDP 里的 a=fingerprint 比对
  2. 不一致 = 中间人攻击,立即断开

这就是为什么信令通道只传 SDP(不传密钥),但仍然安全:攻击者就算劫持信令,也无法伪造合法证书通过指纹校验。


四、SDP 中的 DTLS 相关字段

ini 复制代码
// 传输协议声明(RTP over DTLS over SRTP)
m=audio 9 UDP/TLS/RTP/SAVPF 111 103

// 本端证书指纹(SHA-256)
a=fingerprint:sha-256 4A:6F:2B:C1:...

// DTLS 角色协商
a=setup:actpass   ← 发起方:我可以当 client 或 server,你决定
a=setup:active    ← 接收方:我当 client(主动发 ClientHello)
a=setup:passive   ← 被动方:我当 server(等待 ClientHello)

a=setup 角色协商规则

本端 setup 对端 setup 结果
actpass active 本端 = DTLS Server,对端 = DTLS Client
actpass passive 本端 = DTLS Client,对端 = DTLS Server
active passive 本端 = DTLS Client,对端 = DTLS Server

发起方(Offer)通常设置 actpass,接收方(Answer)选择 active,所以接收方发起 DTLS ClientHello


五、SRTP 密钥导出

DTLS 握手成功后,双方从 DTLS 的 master secret 通过 DTLS-SRTP Key Material Exporter(RFC 5764)导出 SRTP 密钥材料:

python 复制代码
SRTP_KEY_MATERIAL = PRF(master_secret, "EXTRACTOR-dtls_srtp", client_random + server_random, 60 bytes)

拆分为:
├── client_write_SRTP_master_key   (16 bytes)  ← 发起方加密用
├── server_write_SRTP_master_key   (16 bytes)  ← 接收方加密用
├── client_write_SRTP_master_salt  (14 bytes)
└── server_write_SRTP_master_salt  (14 bytes)

每个方向的 RTP 加密独立使用各自的密钥,无需在网络上传输任何密钥(密钥从 master secret 本地计算)。


六、SRTP 包结构:RTP vs SRTP

普通 RTP 包(明文,WebRTC 不允许)

java 复制代码
| RTP Header (12 bytes) | RTP Payload (明文音视频数据) |

SRTP 包(加密后)

java 复制代码
| RTP Header (12 bytes,不加密) | 加密的 Payload | SRTP Auth Tag (10 bytes) |
  • Header 不加密:路由节点需要读取 SSRC、Seq、Timestamp 做 QoS
  • Payload 加密:AES-128-CM(Counter Mode)加密音视频内容
  • Auth Tag:HMAC-SHA1 认证,防止篡改(即使攻击者无法解密,也无法伪造合法包)

七、代码中的关键约束

强制启用 DTLS-SRTP

java 复制代码
// PeerConnectionClient.java line 112
private static final String DTLS_SRTP_KEY_AGREEMENT_CONSTRAINT = "DtlsSrtpKeyAgreement";

// MediaConstraints 中添加(旧版 API,现代 WebRTC 已默认启用)
// keyAgreement = true 表示强制使用 DTLS-SRTP 密钥协商

Loopback 模式特例(禁用加密)

单设备自测时,loopback 模式会关闭加密,避免自握手的复杂性:

java 复制代码
// PeerConnectionClient.createPeerConnectionInternal()  line 444-445
if (peerConnectionParameters.loopback) {
    options.disableEncryption = true;
    options.networkIgnoreMask = 0;  // CallActivity line 376
}

生产环境绝不能设置 disableEncryption = true

DTLS 连接状态监听

java 复制代码
// PCObserver.onConnectionChange()  PeerConnectionClient.java line 1248-1258
@Override
public void onConnectionChange(PeerConnectionState newState) {
    executor.execute(() -> {
        if (newState == PeerConnectionState.CONNECTED) {
            events.onConnected();       // → CallActivity: "DTLS connected"
        } else if (newState == PeerConnectionState.DISCONNECTED) {
            events.onDisconnected();    // → CallActivity: "DTLS disconnected"
        } else if (newState == PeerConnectionState.FAILED) {
            reportError("DTLS connection failed.");  // ICE 通但 DTLS 失败
        }
    });
}

八、完整时间线(从信令到媒体流)

ini 复制代码
t=0    用户点击"连接"
t=1    HTTP 获取房间参数(STUN/TURN 服务器列表)
t=2    WebSocket 建立,注册 clientId
t=3    Offer SDP 生成(含 a=fingerprint, a=setup:actpass)
t=4    Offer 通过信令发给对端
t=5    对端生成 Answer SDP(含自己的 a=fingerprint, a=setup:active)
t=6    ICE Gathering:采集 host/srflx/relay candidates
t=7    ICE candidates 通过信令交换
t=8    ICE Connectivity Check:STUN ping/pong
t=9    IceConnectionState → CONNECTED(UDP 通道打通)
t=10   DTLS ClientHello(接收方发起,因为 a=setup:active)
t=11   证书交换 + 指纹验证
t=12   DTLS Finished(双向)
t=13   SRTP 密钥从 master secret 导出
t=14   PeerConnectionState → CONNECTED
       logAndToast("DTLS connected, delay=" + delta + "ms")
t=15   SRTP 加密的 RTP 包开始流动(媒体传输开始)

九、常见问题

ICE CONNECTED 了但 DTLS FAILED?

原因:

  1. 两端 SDP 里的 a=fingerprint 不一致(信令篡改?证书重新生成?)
  2. 网络防火墙拦截了 DTLS 握手包(UDP 443/5349 端口被封)
  3. 两端时钟偏差过大导致证书有效期校验失败(DTLS 证书通常有效期很短)

代码中触发路径:PeerConnectionState.FAILEDreportError("DTLS connection failed.") → CallActivity 显示错误并挂断。

为什么不用 TLS 而用 DTLS?

RTP 基于 UDP,UDP 无序、不可靠,TLS 要求底层可靠传输(TCP)。DTLS(Datagram TLS)是 TLS 的 UDP 版本,增加了:

  • 消息重传机制(握手包可能丢失)
  • 消息序号(防止重排)
  • 握手分片(避免超过 MTU)

十、一句话总结

组件 作用
DTLS 在 UDP 上进行 TLS 握手,完成双向身份认证和密钥协商
a=fingerprint SDP 中携带证书指纹,防止中间人替换证书
SRTP 密钥导出 从 DTLS master secret 本地派生,密钥从不在网络传输
SRTP 对 RTP Payload 加密(AES-128-CM)+ 认证(HMAC-SHA1)
PeerConnectionState.CONNECTED DTLS 握手完成的信号,媒体流才真正开始
相关推荐
陈随易41 分钟前
pm2 替代品,nodejs&bun 线上部署必备工具
前端·后端·程序员
单线程_0144 分钟前
从案例分析 Vue3 Tokenizer 源码一
前端·javascript·vue.js
BigTopOne1 小时前
【WebRtc】-ICE Candidate 与 STUN/TURN 原理详解
前端
l1258651 小时前
# LangGraph Memory机制深度解析:短期记忆与长期记忆的工程实践
前端·人工智能·python·langchain·bootstrap
码视野2 小时前
基于 Spring Boot + Vue3 的【大学英语四六级 (CET-4/6) 作文智能评分与句式润色系统】设计与实现(含PRD/三端高保真源码/大屏)
java·前端·人工智能·spring boot·后端·vue3
大模型码小白2 小时前
AI 对话流性能调优:万级消息的虚拟滚动落地
java·大数据·前端·javascript·人工智能·算法·机器学习
xm_xm_xm_12 小时前
react17版本以前类组件常用操作
前端·javascript·react.js
ly76893 小时前
Web 性能优化实战:从 Core Web Vitals 到工程化性能治理
前端·性能优化
冬奇Lab4 小时前
一天一个开源项目(第200篇):next-forge - 生产级 Next.js SaaS 启动模板
前端·前端框架·开源