# DTLS 握手 · 在 ICE 通路上建安全信道

> DTLS 是「跑在 UDP 上的 TLS」。这一章读 g3doc/dtls_transport.md 与 dtls 源码：DtlsTransport 如何在 ICE 变 writable 后握手、怎么在同一条路上把 DTLS/SRTP/STUN 三种包多路复用区分开，以及握手产出的两样东西——SRTP 密钥材料与远端证书指纹。

- 作者：David（道雾轩）
- 专栏：音视频与 WebRTC 详解（https://daiw.org/manual/webrtc.md）
- 最后更新：2026-07-14
- 原文：https://daiw.org/manual/webrtc/dtls-handshake
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# DTLS 握手 · 在 ICE 通路上建安全信道

[安全总览](https://daiw.org/manual/webrtc/security-overview)说 DTLS 负责「协商密钥」。这一章看它具体怎么在[刚打通的 ICE 通路](https://daiw.org/manual/webrtc/p2p-transport-channel)上握手，建起安全信道。

## DTLS = 跑在 UDP 上的 TLS

**DTLS（Datagram TLS）** 就是**为不可靠数据报（UDP）改造过的 TLS**。TLS 保护 HTTPS，但它假设底层是可靠有序的 TCP；而 WebRTC 的媒体走 [UDP](https://daiw.org/manual/webrtc/what-is-webrtc)（为了低延迟），包会丢、会乱序。DTLS 在 TLS 基础上加了应对丢包 / 乱序的机制（重传、显式序号），让 TLS 的那套「握手 → 建立密钥 → 加密」能在 UDP 上跑。它给 WebRTC 的，正是 [TLS 那套久经考验的安全](https://daiw.org/manual/webrtc/security-overview)。

## 握手时机：ICE 通了才开始

关键的时序，`g3doc/dtls_transport.md` 讲得很清楚：

> The DTLS handshake happens **after the ICE transport becomes writable and has found a valid pair**.

<Callout type="info">
  **DTLS 握手必须等 [ICE 先打通一条路](https://daiw.org/manual/webrtc/connection-and-checks)。** 逻辑上顺理成章：握手也是要收发包的，得先有一条[能通的 `Connection`](https://daiw.org/manual/webrtc/connection-and-checks)（[writable](https://daiw.org/manual/webrtc/connection-and-checks) 且找到有效候选对），DTLS 的握手消息才有路可走。所以[通话建立的顺序](https://daiw.org/manual/webrtc/call-lifecycle)是：先 ICE 打通（第 2 部分），`P2PTransportChannel` [`SignalIceTransportStateChanged`](https://daiw.org/manual/webrtc/p2p-transport-channel) 通知「路通了」，DTLS 才在这条路上启动握手。**握手本身跑在 ICE 选定的那条通路上**——ICE 是 DTLS 的「承运人」。
</Callout>

## DtlsTransport：握手的执行者

执行握手的是 `DtlsTransport`（W3C 的 `RTCDtlsTransport` 在 C++ 的落地）。它的结构（`g3doc/dtls_transport.md`）：

- **`DtlsTransport`**(`pc/`)—— 对外的包装，可注册 `DtlsTransportObserverInterface` 观察者，通过 `DtlsTransportInformation` 汇报状态（连接状态、远端证书、SRTP 套件）。
- **`DtlsTransportInternal`** —— 接口，实现是 **`DtlsTransportInternalImpl`**（`dtls/`）。它**通过底层的 [ICE 传输](https://daiw.org/manual/webrtc/p2p-transport-channel)收发网络包**，并驱动 [BoringSSL](https://daiw.org/manual/webrtc/boringssl-integration) 完成握手的状态机。

握手完成后，`DtlsTransport` 的状态变为 connected，并携带两样至关重要的产出（下面详述）。

## 一条路上三种包：多路复用

这里有个精妙的工程细节。DTLS 握手、[SRTP 媒体](https://daiw.org/manual/webrtc/srtp-encryption)、[STUN 探测](https://daiw.org/manual/webrtc/stun-protocol)——**三种包全都跑在同一条 [ICE 通路](https://daiw.org/manual/webrtc/p2p-transport-channel)上**（[BUNDLE](https://daiw.org/manual/webrtc/jsep-transport-controller)时更是所有媒体挤一条）。收到一个 UDP 包，怎么知道它是哪种？

<Callout type="info">
  **靠包的第一个字节区分——这是 RFC 5764 定义的多路复用（demultiplexing）方案。** `DtlsTransportInternalImpl` 会「demultiplex DTLS packets and SRTP packets」（`g3doc/dtls_transport.md`）：

  - 第一字节在某个范围 → **STUN** 包（[连通性检查 / 保活](https://daiw.org/manual/webrtc/stun-protocol)），交给 ICE。
  - 第一字节在另一范围 → **DTLS** 包（握手消息），交给 DTLS。
  - 第一字节在再一个范围 → **SRTP** 包（[加密媒体](https://daiw.org/manual/webrtc/srtp-encryption)），解密后交给 RTP。

  三种协议**共用一个 UDP 端口、一条通路**，靠首字节的取值范围岔开——既省了端口（呼应 [BUNDLE 的节约哲学](https://daiw.org/manual/webrtc/jsep-transport-controller)），又让 STUN 保活、DTLS 握手、SRTP 媒体能在同一条来之不易的路上和谐共处。这是 WebRTC「一条路承载一切」设计的又一处体现。
</Callout>

## 握手的两样产出

DTLS 握手成功，产出两样东西（`g3doc/dtls_transport.md`），它们是接下来两章的主题：

1. **一套派生密钥（keying material）** —— 用来[给 SRTP 生成加密密钥](https://daiw.org/manual/webrtc/dtls-srtp-key-export)（[DTLS-SRTP](https://daiw.org/manual/webrtc/security-overview)）。
2. **远端证书的指纹** —— 拿它和 [SDP 里 `a=fingerprint:` 那行](https://daiw.org/manual/webrtc/certificates-fingerprints)**比对**，确认「握手的对端，正是[信令](https://daiw.org/manual/webrtc/call-lifecycle)里说的那个人」——[防中间人](https://daiw.org/manual/webrtc/certificates-fingerprints)。

```mermaid
flowchart TD
    ICE["ICE 通路 writable(第 2 部分)"] -->|"触发"| DH["DtlsTransport 启动握手"]
    DH -->|"握手消息经 ICE 通路收发"| BSSL["BoringSSL 驱动 DTLS 状态机"]
    BSSL --> OUT{"握手成功"}
    OUT -->|"产出 ①"| KEY["SRTP 密钥材料 → DTLS-SRTP 导出"]
    OUT -->|"产出 ②"| FP["远端证书指纹 → 比对 SDP a=fingerprint"]
```

## 小结与下一章

**DTLS 是「跑在 UDP 上的 TLS」**，给 WebRTC 提供久经考验的安全握手。它**必须等 [ICE 打通一条 writable 的路](https://daiw.org/manual/webrtc/connection-and-checks)**才开始，握手消息跑在这条 ICE 通路上。`DtlsTransport` / `DtlsTransportInternalImpl` 是执行者，驱动 [BoringSSL](https://daiw.org/manual/webrtc/boringssl-integration)。同一条路上，**DTLS / SRTP / STUN 三种包靠首字节范围多路复用**（RFC 5764）。握手产出两样：**SRTP 密钥材料**（[下下章导出](https://daiw.org/manual/webrtc/dtls-srtp-key-export)）和**远端证书指纹**（下一章用于[防中间人](https://daiw.org/manual/webrtc/certificates-fingerprints)）。

握手要用证书，而证书指纹是防中间人的关键。下一章看证书怎么生成、指纹怎么把 DTLS 安全和[信令](https://daiw.org/manual/webrtc/call-lifecycle)绑在一起。👉 [证书与指纹](https://daiw.org/manual/webrtc/certificates-fingerprints)。

## 参考资料

- [1] libwebrtc 源码.`g3doc/dtls_transport.md`（握手时机、多路复用、`DtlsTransport` / `DtlsTransportInternalImpl`）。
- [2] libwebrtc 源码.`dtls/dtls_transport_internal_impl.h`、`api/dtls_transport_interface.h`。
- [3] IETF RFC 6347「DTLS 1.2」、RFC 5764「DTLS-SRTP 多路复用」。
