18、安全与加密:DTLS-SRTP加密、证书管理、安全最佳实践

聊到WebRTC的安全,我得先跟你掏心窝子说一句:WebRTC从一开始就把安全刻在了骨子里。你想想看,一个浏览器直接跟另一个浏览器建立P2P连接,音视频数据在公网上裸奔,如果没有加密,那简直就是把自家客厅的摄像头直播给全世界看。

所以,WebRTC强制要求所有媒体流必须加密。不加密?对不起,浏览器直接拒绝连接。这一点,我个人觉得是WebRTC最值得称赞的设计之一。

18.1 DTLS-SRTP:双重保险的加密方案

WebRTC用了两套加密体系:DTLSSRTP。它们不是二选一,而是配合使用。

  • DTLS(Datagram Transport Layer Security):说白了就是给UDP套上了TLS的外衣。它负责信令通道的安全,以及后续的密钥协商。
  • SRTP(Secure Real-time Transport Protocol):专门用来加密音视频数据包。它比普通加密更轻量,延迟更低。

为什么需要两套?嗯,这里有个关键点:SRTP的密钥是通过DTLS握手协商出来的。DTLS先建立安全通道,然后双方交换SRTP的密钥材料。这个过程叫做DTLS-SRTP。

核心流程:

  1. 双方通过信令交换DTLS指纹(证书的哈希值)
  2. 建立DTLS连接,验证对方证书
  3. 通过DTLS安全通道协商SRTP密钥
  4. 后续音视频数据全部走SRTP加密通道

我在项目中遇到过一个问题:有些开发者以为只要用了SRTP就万事大吉,结果发现密钥交换过程是明文的。这就像你给保险箱上了把好锁,但钥匙却直接挂在保险箱外面。所以,DTLS是SRTP安全的前提,缺一不可。

18.2 证书管理:自签名证书就够了

WebRTC的证书管理和HTTPS不太一样。HTTPS需要CA签发的证书,但WebRTC内部通信用的是自签名证书。为什么?因为WebRTC的证书验证不是靠CA,而是靠指纹比对

具体来说,每个WebRTC端点都会生成一个自签名证书,然后计算这个证书的SHA-256指纹。这个指纹通过信令通道(通常是WebSocket)传给对方。对方收到后,在DTLS握手时验证证书的指纹是否匹配。

// 生成自签名证书的示例(Node.js环境)
const crypto = require('crypto');
const { RTCPeerConnection } = require('wrtc');

// 默认情况下,RTCPeerConnection会自动生成证书
const pc = new RTCPeerConnection({
  certificates: await RTCPeerConnection.generateCertificate({
    name: 'ECDSA',
    namedCurve: 'P-256'
  })
});

// 获取本地证书指纹
const fingerprint = pc.localDescription.sdp.match(
  /a=fingerprint:sha-256\s(.+)/i
)[1];
console.log('本地指纹:', fingerprint);

我的建议:使用ECDSA证书而不是RSA。ECDSA的密钥更短,握手更快,对移动端更友好。我曾经在低端Android设备上测试过,ECDSA的握手时间比RSA快了将近30%。

证书的有效期呢?WebRTC规范建议证书有效期不要太长,一般24小时就够了。因为每次重新连接都会生成新证书,没必要搞个一年两年的。而且,短有效期证书可以降低证书泄露的风险。

18.3 安全最佳实践:别给自己挖坑

做了这么多年WebRTC开发,我踩过的坑不少。下面这些最佳实践,是我用真金白银换来的经验。

18.3.1 强制使用HTTPS

WebRTC的getUserMedia()和RTCPeerConnection在非安全上下文中是无法使用的。什么是安全上下文?就是HTTPS或者localhost。我曾经见过有人为了省事,在HTTP环境下调试,结果折腾了半天才发现是协议问题。

注意:即使你的信令服务器是HTTP,WebRTC的媒体通道也必须使用DTLS-SRTP加密。但信令通道本身如果不加密,攻击者可以篡改SDP,从而实施中间人攻击。所以,信令通道也建议走WSS(WebSocket Secure)

18.3.2 验证指纹,不要跳过

有些开发者为了调试方便,会关闭证书验证。这在生产环境是绝对禁止的。我曾经接手过一个项目,前任开发者把证书验证关了,结果用户数据在公网上裸奔了三个月。

// 错误的做法:跳过证书验证
const pc = new RTCPeerConnection({
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
  // 不要设置这个!
  // peerIdentity: undefined
});

// 正确的做法:验证指纹
pc.oniceconnectionstatechange = () => {
  if (pc.iceConnectionState === 'connected') {
    // 获取远程证书指纹
    const remoteFingerprint = pc.remoteDescription.sdp.match(
      /a=fingerprint:sha-256\s(.+)/i
    )[1];
    // 与信令中收到的指纹比对
    if (remoteFingerprint !== expectedFingerprint) {
      console.error('指纹不匹配!可能存在中间人攻击');
      pc.close();
    }
  }
};

18.3.3 使用STUN/TURN的认证

如果你使用了TURN服务器(用于穿透NAT),一定要开启认证。否则,任何人都可以用你的TURN服务器转发流量,你不仅要承担带宽费用,还可能被用于非法用途。

配置项 说明 建议
username TURN认证用户名 使用临时令牌,不要硬编码
credential 认证密码或HMAC 使用HMAC-SHA1,密码定期轮换
ttl 凭证有效期 建议3600秒(1小时)

18.3.4 限制ICE候选人数量

默认情况下,WebRTC会收集所有网络接口的ICE候选人,包括内网地址、VPN地址等。这可能会泄露你的网络拓扑信息。我建议在非必要情况下,只使用host和srflx类型的候选人。

// 限制ICE候选人类型
const pc = new RTCPeerConnection({
  iceTransportPolicy: 'relay' // 只使用TURN中继
});

// 或者更精细的控制
pc.onicecandidate = (event) => {
  if (event.candidate) {
    // 过滤掉内网地址
    if (event.candidate.type === 'host' && 
        event.candidate.address.startsWith('192.168.')) {
      return; // 丢弃内网候选人
    }
  }
};

18.4 知识体系总览

下面这张图,是我梳理的WebRTC安全体系的核心逻辑。你可以把它当作一个检查清单,看看自己的实现有没有遗漏。

WebRTC安全体系核心逻辑 信令安全 WSS / HTTPS DTLS握手 证书验证 + 密钥协商 SRTP加密 音视频数据加密 证书管理 自签名证书 · ECDSA P-256 指纹比对 · 短有效期 安全最佳实践 强制HTTPS · 验证指纹 TURN认证 · 限制ICE候选人 安全不是功能,而是基础 每一层安全措施都不可或缺,否则整个系统就是纸糊的

18.5 避坑指南

最后,分享几个我亲身踩过的坑:

  • 证书生成时机:不要在每次创建RTCPeerConnection时都生成新证书,这会导致握手延迟。建议复用证书,或者提前生成。
  • 指纹比对大小写:指纹字符串的大小写要统一,否则比对会失败。我习惯全部转成小写再比较。
  • TURN凭证过期:如果TURN凭证过期了,ICE连接会失败。记得在凭证过期前刷新,或者使用更长的有效期。

一个小技巧:在开发阶段,可以用chrome://webrtc-internals/ 查看所有加密细节。包括DTLS握手状态、SRTP密钥、证书指纹等。调试安全问题的时候,这个页面是你的好帮手。

安全这件事,说白了就是不要信任任何未经验证的输入。WebRTC已经帮你做好了大部分工作,但最终的安全防线,还是掌握在开发者自己手里。


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