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 负责传递证书,两者对上了,就安全了。
指纹验证的步骤:
- 发起方生成自签名证书,计算指纹
- 指纹通过信令通道发送给接收方
- 接收方在 DTLS 握手时拿到证书
- 接收方计算证书的哈希值,与信令中的指纹比对
- 一致则通过,否则断开连接
// 在 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 的安全默认是好的,但前提是你没有自己破坏它。比如,不要为了调试方便而关闭证书验证,不要在信令中传输明文密钥。