22、WebRTC安全:DTLS-SRTP加密、证书指纹验证、信令通道安全(WSS)、常见安全攻击与防范

WebRTC 的安全,说白了就是三件事:数据加密、身份验证、通道保护。我刚开始接触 WebRTC 时,觉得安全是浏览器帮我们搞定的,自己不用操心。后来在一次内部测试中,有人直接用抓包工具看到了未加密的媒体流……嗯,从那以后我再也不敢轻视安全配置了。

这一章,我们就把 WebRTC 的安全体系拆开来看。你想想看,一个实时音视频系统,如果被人窃听或篡改,后果有多严重?所以,DTLS-SRTP、证书指纹、WSS 这些概念,你必须吃透。

一、DTLS-SRTP:媒体流的加密基石

WebRTC 的媒体数据走的是 SRTP(Secure Real-time Transport Protocol)。但 SRTP 的密钥怎么协商?答案就是 DTLS(Datagram Transport Layer Security)。

DTLS 是 TLS 的 UDP 版本。它负责在 PeerConnection 建立时,完成一次握手,生成共享密钥。然后这个密钥被喂给 SRTP,用来加密每一帧音频和视频数据。

核心流程:

  • DTLS 握手:基于 UDP 的证书交换和密钥协商
  • 导出 SRTP 密钥:通过 DTLS 的导出器(exporter)生成
  • SRTP 加密:后续所有 RTP/RTCP 包都使用该密钥加密

我在项目中遇到过一个问题:某些老旧设备不支持 DTLS 1.2,导致握手失败。后来我们强制降级到 DTLS 1.0,但安全性打了折扣。所以,建议客户端和服务端都使用最新的 DTLS 版本

// 在浏览器中,DTLS 是自动启用的
// 但你可以通过 RTCPeerConnection 的配置来强制要求加密
const config = {
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
  // 强制使用 DTLS,默认就是 true
  // 但显式设置可以避免某些浏览器行为不一致
  certificates: [], // 可以传入自定义证书
};

const pc = new RTCPeerConnection(config);

个人经验: 如果你在服务端使用 libwebrtc 或 GStreamer,记得检查 DTLS 的日志。握手失败时,通常会有 "DTLS handshake failed" 的明确提示。我曾经花了一整天排查一个加密问题,最后发现是服务端的证书链不完整。

二、证书指纹验证:你是谁?

DTLS 握手时,双方会交换自签名证书。但自签名证书怎么信任?靠的就是证书指纹(fingerprint)

指纹是证书的哈希值,比如 SHA-256。它在信令通道中交换。接收方拿到对方的指纹后,与 DTLS 握手时收到的证书进行比对。如果一致,说明对方就是信令中声称的那个人。

说白了,这就是一个带外验证机制。信令通道负责传递指纹,DTLS 负责传递证书,两者对上了,就安全了。

指纹验证的步骤:

  1. 发起方生成自签名证书,计算指纹
  2. 指纹通过信令通道发送给接收方
  3. 接收方在 DTLS 握手时拿到证书
  4. 接收方计算证书的哈希值,与信令中的指纹比对
  5. 一致则通过,否则断开连接
// 在 SDP 中,指纹是这样出现的
// a=fingerprint:sha-256 4A:AD:B9:B1:3F:82:18:3B:54:02:12:DF:3E:5D:49:6B:19:E5:7C:AB:9B:6F:2E:1D:1A:8B:4F:76:49:BC:EE:1F

// 在代码中,你可以这样获取本地指纹
pc.createOffer().then(offer => {
  const sdp = offer.sdp;
  // 从 SDP 中提取 fingerprint
  const match = sdp.match(/a=fingerprint:(\S+)/);
  if (match) {
    console.log('本地指纹:', match[1]);
  }
});

避坑指南: 我曾经遇到过一个情况:信令服务器被中间人攻击,指纹被篡改了。结果两端都验证了错误的指纹,但依然建立了连接。为什么?因为攻击者同时伪造了两端的证书。所以,信令通道本身必须是安全的。这就是我们接下来要说的 WSS。

三、信令通道安全(WSS)

信令通道是 WebRTC 的指挥中心。SDP 和 ICE 候选者都在这里传递。如果信令通道被窃听或篡改,整个通话就暴露了。

解决方案很简单:使用 WSS(WebSocket Secure)。WSS 就是基于 TLS 的 WebSocket,它提供了传输层的加密和身份验证。

我个人习惯在信令服务器上强制使用 WSS,并且只允许 TLS 1.2 以上的版本。你想想看,如果信令通道是明文的,那 DTLS 的指纹交换就毫无意义了——攻击者可以轻松替换指纹。

通道类型 加密 适用场景
WS(明文) 仅用于局域网测试
WSS(加密) TLS 生产环境必选
// 服务端示例(Node.js + ws + https)
const https = require('https');
const WebSocket = require('ws');
const fs = require('fs');

const server = https.createServer({
  cert: fs.readFileSync('/path/to/cert.pem'),
  key: fs.readFileSync('/path/to/key.pem'),
});

const wss = new WebSocket.Server({ server });

wss.on('connection', (ws) => {
  console.log('安全的信令连接已建立');
  // 处理信令消息
});

server.listen(443);

我的建议: 不要自己管理证书,使用 Let's Encrypt 或云厂商的证书管理服务。我曾经手动更新证书时漏掉了一个中间证书,导致 WSS 连接在部分 Android 设备上失败。自动化证书管理能避免这种低级错误。

四、常见安全攻击与防范

WebRTC 虽然内置了加密,但并不是万能的。以下是我在实际工作中遇到或了解过的几种攻击方式。

4.1 中间人攻击(MITM)

攻击者拦截信令通道,篡改 SDP 或指纹。防范方法就是 WSS + 指纹验证。两者缺一不可。

4.2 STUN/TURN 反射攻击

攻击者利用 STUN 服务器进行放大攻击。防范方法:限制 STUN 请求频率,使用认证机制。

4.3 媒体流注入

攻击者伪造 RTP 包,插入恶意内容。SRTP 的认证标签可以防止这一点。但要注意,如果密钥泄露,这个防护就失效了。

4.4 浏览器级别的漏洞

比如 CVE-2021-21222,V8 引擎的漏洞可能导致远程代码执行。防范方法:及时更新浏览器和 WebRTC 库。

安全清单:

  • ✅ 强制使用 DTLS 1.2+
  • ✅ 信令通道使用 WSS
  • ✅ 验证证书指纹
  • ✅ 限制 ICE 候选者类型(如只允许 relay)
  • ✅ 定期更新 WebRTC 库
  • ✅ 使用安全的随机数生成器生成 ICE 凭证

五、知识体系图

下面这张图展示了 WebRTC 安全的核心结构。你可以看到,信令通道、DTLS 握手、SRTP 加密三者是如何协同工作的。

WebRTC 安全体系结构 发起方 (Offerer) 接收方 (Answerer) 信令通道 (WSS) 交换 SDP(含指纹)、ICE 候选者 DTLS 握手 证书指纹验证 SRTP 加密媒体流 音频/视频采集 音频/视频渲染 安全三要素:加密传输(WSS)→ 身份验证(指纹)→ 媒体加密(SRTP) 常见攻击:MITM、注入、反射放大 → 防范:WSS + 指纹 + 最新库

最后提醒: 安全不是一次性的配置,而是一个持续的过程。我见过太多团队在开发阶段忽略安全,上线后被打得措手不及。记住:WebRTC 的安全默认是好的,但前提是你没有自己破坏它。比如,不要为了调试方便而关闭证书验证,不要在信令中传输明文密钥。

公众号:蓝海资料掘金营,微信deep3321