27、安全与隐私:DTLS-SRTP加密、权限管理、安全最佳实践

做WebRTC开发这么多年,我越来越觉得安全不是锦上添花,而是地基。你想想看,音视频数据一旦被窃听或篡改,后果有多严重?我早期做的一个项目,就因为忽略了DTLS握手超时配置,导致用户频繁断连,排查了整整两天才找到根儿。今天咱们就把安全这块彻底聊透。

DTLS-SRTP:你的音视频数据在裸奔吗?

WebRTC默认使用DTLS-SRTP来加密媒体流。说白了,DTLS负责握手和密钥交换,SRTP负责用这些密钥加密实际的音视频包。两者配合,确保你的通话内容不会被中间人偷听。

核心流程:

  1. DTLS握手阶段:双方交换证书,协商加密套件
  2. 导出SRTP密钥:握手完成后,DTLS层导出密钥材料
  3. SRTP加密:后续所有RTP/RTCP包都用这个密钥加密

我在项目中遇到过一个问题:某些老旧浏览器不支持DTLS 1.2,导致握手失败。解决办法是降级到DTLS 1.0,但安全性会打折扣。嗯,这里要注意,如果你的用户群体偏技术向,建议强制要求DTLS 1.2以上。

DTLS-SRTP的配置细节

实际开发中,你需要在SDP中明确指定加密方式。看下面这个例子:

// 创建PeerConnection时启用DTLS-SRTP
const config = {
  iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
  sdpSemantics: 'unified-plan',
  // 强制使用DTLS-SRTP
  certificates: [await RTCPeerConnection.generateCertificate({
    name: 'ECDSA',
    namedCurve: 'P-256'
  })]
};

const pc = new RTCPeerConnection(config);

我个人习惯用ECDSA证书而不是RSA,因为它的密钥更短,握手速度更快。你想想看,在移动端网络不稳定的情况下,每次握手少几十毫秒,体验差距就出来了。

避坑指南:我曾经在生产环境中发现,某些自签名证书会导致DTLS握手失败。后来统一改用浏览器生成的证书(如上例),问题就解决了。记住,不要手动生成证书,让浏览器自己搞定。

权限管理:别让摄像头随便被打开

WebRTC的权限管理主要涉及摄像头、麦克风和屏幕共享。浏览器会弹出权限请求框,但作为开发者,你还需要做更多。

我见过最糟糕的做法:页面一加载就请求摄像头权限。用户还没搞明白怎么回事,就被要求授权,结果直接拒绝。正确的做法是:

  • 延迟请求:等到用户点击"开始通话"按钮时再请求权限
  • 明确说明用途:告诉用户为什么需要摄像头,比如"为了视频通话"
  • 处理拒绝情况:用户拒绝后,优雅降级到纯音频或文字聊天
// 优雅处理权限拒绝
async function startVideoCall() {
  try {
    const stream = await navigator.mediaDevices.getUserMedia({
      video: true,
      audio: true
    });
    // 正常通话
  } catch (err) {
    if (err.name === 'NotAllowedError') {
      // 用户拒绝了权限
      console.warn('用户拒绝了摄像头/麦克风权限');
      // 降级到纯音频
      startAudioOnlyCall();
    } else if (err.name === 'NotFoundError') {
      console.warn('没有找到摄像头设备');
      // 提示用户连接设备
    }
  }
}

注意:在iOS Safari上,getUserMedia必须在用户手势事件(如click)中调用,否则会静默失败。我踩过这个坑,排查了半天才发现是iOS的隐私策略。

安全最佳实践:从实战中总结的6条铁律

做安全没有银弹,但有一些经过验证的实践可以大幅降低风险。我根据自己的项目经验,整理了下面这些要点:

实践 说明 我的经验
强制HTTPS WebRTC要求页面必须通过HTTPS加载 本地开发可以用localhost,但生产环境必须HTTPS
证书验证 DTLS握手时验证对端证书指纹 我曾经遇到过中间人攻击,就是因为跳过了证书验证
密钥更新 定期更新SRTP密钥,防止长期使用同一密钥 长会议场景下,建议每30分钟重新协商一次密钥
ICE候选者过滤 只允许可信的ICE候选者 我在做企业级应用时,会白名单内网IP段
日志脱敏 不要在日志中打印SDP或ICE候选者 有一次debug时不小心把SDP打到了日志里,赶紧清理了
超时控制 DTLS握手设置合理的超时时间 默认30秒太长,我一般设为10秒

DTLS-SRTP的完整握手流程

为了让你更直观地理解整个过程,我画了一张流程图。这张图展示了从建立连接开始,到媒体数据加密传输的完整链路。

DTLS-SRTP 完整握手与加密流程 发起方 (Alice) 接收方 (Bob) ① ClientHello (DTLS 1.2, 加密套件) ② ServerHello + 证书 + 密钥交换参数 ③ 证书验证 + 密钥交换 + Finished ④ Finished (握手完成) ⑤ 导出SRTP密钥材料 (Keying Material) ⑥ SRTP加密传输音视频数据 AES-GCM加密 + 完整性校验 整个握手过程通常在 1-2 个RTT内完成,之后所有媒体包都经过加密

这张图里最关键的是第⑤步——密钥导出。DTLS握手完成后,双方会协商出一个主密钥,然后通过密钥派生函数生成SRTP需要的加密密钥和盐值。这个过程是自动的,你不需要手动干预。

证书指纹验证:防止中间人攻击的最后一道防线

默认情况下,WebRTC的DTLS握手会验证证书链,但如果你用的是自签名证书(很多场景下都是),就需要手动验证证书指纹。

我曾经在一个金融类项目中,要求双方在带外交换证书指纹。具体做法是:

// 获取本地证书指纹
const certs = await RTCPeerConnection.getCertificates();
const localFingerprint = certs[0].getFingerprint();

// 通过信令通道发送给对端
signaling.send({ type: 'fingerprint', value: localFingerprint });

// 收到对端指纹后,在连接建立时验证
pc.addEventListener('connectionstatechange', () => {
  if (pc.connectionState === 'connected') {
    const remoteFingerprint = pc.getRemoteCertificates()[0].getFingerprint();
    if (remoteFingerprint !== expectedFingerprint) {
      console.error('证书指纹不匹配,可能存在中间人攻击!');
      pc.close();
    }
  }
});

重要提醒:证书指纹验证必须在带外进行,也就是通过你的信令服务器传递,而不是通过WebRTC的数据通道。否则攻击者可以同时篡改信令和媒体通道,验证就失去了意义。

权限管理的进阶技巧

除了基本的getUserMedia权限,还有一些容易被忽视的权限问题:

  • 屏幕共享权限:getDisplayMedia会弹出屏幕选择对话框,用户可以选择分享整个屏幕、某个窗口或浏览器标签页。注意,你不能强制用户分享特定内容。
  • 音频输出设备:setSinkId可以控制音频输出到哪个设备(如扬声器或耳机),但需要用户授权。
  • 媒体流暂停:用户可以通过浏览器自带的控件暂停摄像头或麦克风,你的应用需要监听这些事件并做出响应。
// 监听用户通过浏览器控件暂停媒体流
stream.getVideoTracks()[0].addEventListener('ended', () => {
  console.warn('用户关闭了摄像头');
  // 显示占位头像或文字提示
  showPlaceholderAvatar();
});

// 监听音频输出设备变化
audioElement.addEventListener('sinkchange', () => {
  console.log('音频输出设备已切换');
});

嗯,这里要注意,不同浏览器对权限的处理方式略有差异。比如Firefox允许用户单独禁用摄像头但保留麦克风,而Chrome则是整体拒绝。我在做跨浏览器兼容时,会针对不同浏览器写不同的降级逻辑。

总结一下

安全不是一蹴而就的,它需要贯穿整个开发周期。从DTLS-SRTP的配置,到权限管理的细节,再到证书验证的严谨,每一步都马虎不得。我个人习惯在项目初期就把安全方案定下来,而不是等到上线前再补。你想想看,一旦用户数据泄露,再多的功能优化也挽回不了信任。

最后送大家一句话:安全不是功能,而是态度。希望今天的分享能帮你少踩一些坑。

核心要点回顾:

  • DTLS-SRTP是WebRTC的默认加密方案,必须正确配置
  • 权限管理要延迟请求、明确用途、优雅降级
  • 证书指纹验证是防止中间人攻击的关键
  • 安全实践要贯穿开发始终,不要事后补救
公众号:蓝海资料掘金营,微信deep3321