# DTLS-SRTP 密钥导出 · 从握手到 SRTP 密钥

> DTLS 握手不直接加密媒体，而是导出一份「密钥材料」交给 SRTP。这一章读 pc/dtls_srtp_transport.h:DtlsSrtpTransport 如何在握手完成时用 ExportKeyingMaterial 抽取密钥、配置给 SrtpSession，以及为什么它「握手完成后才变 writable」。

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

# DTLS-SRTP 密钥导出 · 从握手到 SRTP 密钥

[DTLS 握手成功了](https://daiw.org/manual/webrtc/dtls-handshake)，[身份也验明了](https://daiw.org/manual/webrtc/certificates-fingerprints)。但 DTLS **本身不加密媒体**——[它只负责谈密钥](https://daiw.org/manual/webrtc/security-overview)。这一章讲清「谈好的密钥」怎么变成 [SRTP](https://daiw.org/manual/webrtc/srtp-encryption) 真正能用的钥匙——这一步叫**密钥导出**，是 DTLS-SRTP 的枢纽。

## 承上启下：DtlsSrtpTransport

干这个枢纽活的，是 **`DtlsSrtpTransport`**（`pc/dtls_srtp_transport.h`）。`g3doc/srtp.md` 一句话点透它的身份：

> `DtlsSrtpTransport` is a subclass of the `SrtpTransport` that **extracts the keying material when the DTLS handshake is done** and configures it in its base class.

<Callout type="info">
  **`DtlsSrtpTransport` 是 DTLS 和 SRTP 之间的桥。** 它继承自 [`SrtpTransport`](https://daiw.org/manual/webrtc/srtp-encryption)（管 [SRTP 加解密](https://daiw.org/manual/webrtc/srtp-encryption)），但多做一件事：**盯着 [DTLS 握手](https://daiw.org/manual/webrtc/dtls-handshake)，握手一完成，就从 DTLS 会话里抽取密钥材料，配置给自己的 SRTP 基类。** 于是一条清晰的接力：[ICE 打通路](https://daiw.org/manual/webrtc/p2p-transport-channel) → [DTLS 握手](https://daiw.org/manual/webrtc/dtls-handshake)谈密钥 → **`DtlsSrtpTransport` 导出密钥** → [SRTP](https://daiw.org/manual/webrtc/srtp-encryption) 拿密钥加密媒体。它站在 DTLS 的下游、SRTP 的上游，把两者接上。
</Callout>

## 导出：ExportKeyingMaterial

「从 DTLS 会话里抽密钥」的机制，是 TLS 标准的 **`ExportKeyingMaterial`**（密钥导出函数）。它的思想很优雅：

<Callout type="info">
  **DTLS 握手过程中，双方**其实已经**共同算出了一批共享密钥（主密钥及派生）；`ExportKeyingMaterial` 让上层能从这批密钥里，「导出」额外的、用于其他用途的密钥——这里就是 [SRTP](https://daiw.org/manual/webrtc/srtp-encryption) 的密钥。** 关键在于：导出是**确定性**的——双方用同一个「标签」（DTLS-SRTP 规定的字符串）和同样的握手密钥，导出**完全相同**的 SRTP 密钥材料，却**无需再传输任何密钥**。密钥从不上网，只在两端各自「算」出来，天然抗窃听。

  导出的材料按 [DTLS-SRTP 协商的套件](https://daiw.org/manual/webrtc/security-overview)切成：发送方向的密钥 + 盐、接收方向的密钥 + 盐。`DtlsSrtpTransport` 拿到后，分别配置给 SRTP 的[发送和接收会话](https://daiw.org/manual/webrtc/srtp-encryption)（`SetSend` / `SetReceive`）。至此，SRTP 万事俱备。
</Callout>

## 为什么握手完才 writable

`g3doc/srtp.md` 还提到 `DtlsSrtpTransport` 一个重要行为：

> It will also become **writable only once the DTLS handshake is done**.

这个「握手完才可写」是**安全的强约束**：在密钥导出之前，SRTP 没有钥匙，任何媒体包都无法加密——所以 `DtlsSrtpTransport` **在握手完成、密钥就位之前，拒绝发送任何媒体**。这从机制上**杜绝了明文媒体外泄**的可能：哪怕上层[编码器](https://daiw.org/manual/webrtc/source-map)已经出帧了，只要 DTLS 没握手完，这些帧就发不出去。**「没有密钥，就没有媒体」**——[强制加密](https://daiw.org/manual/webrtc/security-overview)的底线，在这里被 writable 状态硬性保证。

```mermaid
flowchart TD
    DH["DTLS 握手完成(第 DTLS 章)"] -->|"触发"| EXP["DtlsSrtpTransport:<br/>ExportKeyingMaterial 导出 SRTP 密钥材料"]
    EXP --> SET["配置给 SRTP 基类:<br/>SetSend(发送密钥) · SetReceive(接收密钥)"]
    SET --> W["DtlsSrtpTransport 变 writable"]
    W --> MEDIA["SRTP 加密的媒体可以开跑了"]
    NOKEY["握手未完成 → 无密钥 → 不 writable → 媒体发不出(防明文外泄)"]
```

## 小结与下一章

**DTLS-SRTP 密钥导出**是 [DTLS 握手](https://daiw.org/manual/webrtc/dtls-handshake)和 [SRTP 加密](https://daiw.org/manual/webrtc/srtp-encryption)之间的桥，由 **`DtlsSrtpTransport`**（`SrtpTransport` 的子类）完成：握手一结束，用 **`ExportKeyingMaterial`** 从 DTLS 共享密钥里**确定性地导出** SRTP 密钥材料（双方各自算出、完全一致、无需再传输），按[套件](https://daiw.org/manual/webrtc/security-overview)切成收发密钥配给 [SRTP 会话](https://daiw.org/manual/webrtc/srtp-encryption)。它**握手完成才 writable**——没密钥就不发媒体，硬性[杜绝明文外泄](https://daiw.org/manual/webrtc/security-overview)。

密钥到手，终于可以加密媒体了。下一章看真正干加解密的 [`SrtpSession`](https://daiw.org/manual/webrtc/srtp-encryption) 和它背后的 libsrtp。👉 [SRTP 加密媒体](https://daiw.org/manual/webrtc/srtp-encryption)。

## 参考资料

- [1] libwebrtc 源码.`pc/dtls_srtp_transport.h` / `.cc`（`DtlsSrtpTransport`、导出密钥、writable 时机）、`g3doc/srtp.md`。
- [2] libwebrtc 源码.`rtc_base/ssl_stream_adapter.h`（`ExportKeyingMaterial`）。
- [3] IETF RFC 5764「DTLS-SRTP」§ Key Derivation;RFC 5705「TLS 密钥材料导出」。
