25、安全与加密:DTLS-SRTP加密、信令安全、Token鉴权机制

做WebRTC开发,最容易被忽视的就是安全。我刚开始接触这个领域时,总觉得“先把功能跑通再说”,结果被安全坑过好几次。有一次线上会议,媒体流被第三方抓包解析,虽然没出大事,但吓得我连夜补安全方案。

说白了,WebRTC的安全不是可选项,是必选项。你想想看,用户的音视频数据在公网上裸奔,这画面太美我不敢看。今天我们就来聊聊WebRTC的三层安全防护:传输加密、信令安全、身份鉴权

核心观点:WebRTC强制要求所有媒体流必须加密,这是规范写死的。不加密的WebRTC实现,压根就不是合法的WebRTC。

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

DTLS-SRTP,全称是Datagram Transport Layer Security – Secure Real-time Transport Protocol。名字很长,但逻辑很简单:用DTLS握手协商密钥,然后用SRTP加密媒体数据。

为什么不用传统的TLS?因为TLS跑在TCP上,而WebRTC的媒体流走的是UDP。DTLS就是TLS的UDP版本,专门为实时通信设计的。

25.1.1 握手流程

DTLS-SRTP的握手过程,我画了个图,你看一眼就明白了:

DTLS-SRTP 握手流程 客户端 (Offerer) 服务端 (Answerer) ① ClientHello (支持SRTP扩展) ② ServerHello + 证书 + 证书请求 ③ 客户端证书 + 密钥交换 ④ 服务端完成 + ChangeCipherSpec ⑤ 客户端完成 + ChangeCipherSpec ✅ SRTP密钥协商完成,开始加密通信

握手完成后,双方就有了共享的SRTP主密钥。后续所有的音频、视频包,都会用这个密钥加密传输。

25.1.2 Android端的实现

在Android平台上,WebRTC库已经帮我们封装好了DTLS-SRTP。你只需要在创建PeerConnection时,配置好相关参数:

// 创建PeerConnection的配置
PeerConnection.RTCConfiguration config = new PeerConnection.RTCConfiguration(iceServers);

// 强制使用DTLS-SRTP加密
config.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN;
config.enableDtlsSrtp = true;  // 默认就是true,但显式设置更安全

// 创建PeerConnection
PeerConnectionFactory factory = PeerConnectionFactory.builder()
    .setOptions(new PeerConnectionFactory.Options())
    .createPeerConnectionFactory();

PeerConnection connection = factory.createPeerConnection(config, observer);

我的经验:我曾经遇到过一个问题,某些定制ROM的WebRTC库默认关闭了DTLS。排查了整整两天,最后发现是enableDtlsSrtp被设成了false。所以建议你在初始化时,显式设置这个参数,别依赖默认值。

25.2 信令安全:容易被忽视的薄弱环节

很多人把精力都放在媒体加密上,却忘了信令通道。你想想看,如果信令被篡改,攻击者可以注入假的SDP,或者劫持ICE候选者信息。媒体加密得再好,也架不住信令被调包。

信令安全的核心就两点:传输加密消息完整性校验

25.2.1 传输层加密

信令通道必须跑在WSS(WebSocket Secure)上,而不是裸WebSocket。WSS就是WebSocket over TLS,和HTTPS一个道理。

// 正确的做法:使用WSS
WebSocketFactory factory = new WebSocketFactory();
WebSocket ws = factory.createSocket("wss://your-signaling-server.com/ws");

// 错误的做法:使用WS(明文传输)
// WebSocket ws = factory.createSocket("ws://your-signaling-server.com/ws");

25.2.2 消息完整性校验

光加密还不够,你还需要确保消息没有被篡改。我习惯的做法是,在信令消息中加入HMAC签名:

// 服务端生成签名
String secret = "your-server-secret";
String message = "{\"type\":\"offer\",\"sdp\":\"...\"}";
String hmac = HmacSHA256(secret, message);

// 发送带签名的消息
JSONObject payload = new JSONObject();
payload.put("data", message);
payload.put("signature", hmac);

// 客户端验证签名
String receivedHmac = payload.getString("signature");
String expectedHmac = HmacSHA256(secret, payload.getString("data"));
if (!receivedHmac.equals(expectedHmac)) {
    // 签名不匹配,丢弃消息
    return;
}

注意:HMAC的密钥绝对不能硬编码在客户端代码里。我见过有人把密钥写在Java代码中,结果APK被反编译后密钥直接暴露。正确的做法是:客户端登录时从服务端获取临时密钥,或者使用非对称加密。

25.3 Token鉴权机制:谁在连接你的房间?

最后一道防线是身份鉴权。你不能让任何人都能加入你的视频会议。WebRTC本身不提供鉴权机制,这需要我们在应用层实现。

我推荐使用Token鉴权方案,流程如下:

Token鉴权流程 Android客户端 鉴权服务 信令服务器 ① 登录请求 ② 返回Token ③ 携带Token连接信令服务器 ④ 验证Token有效性 ⑤ 验证通过,允许连接

25.3.1 Token的生成与验证

Token通常使用JWT(JSON Web Token)格式。服务端签发,客户端持有,信令服务器验证。

// 服务端生成JWT Token
// 使用io.jsonwebtoken库
String token = Jwts.builder()
    .setSubject("user_12345")
    .setIssuedAt(new Date())
    .setExpiration(new Date(System.currentTimeMillis() + 3600_000)) // 1小时过期
    .claim("room", "room_abc")
    .claim("role", "host")
    .signWith(SignatureAlgorithm.HS256, secretKey)
    .compact();

// Android客户端携带Token连接信令服务器
JSONObject authMessage = new JSONObject();
authMessage.put("type", "auth");
authMessage.put("token", token);
webSocket.send(authMessage.toString());

25.3.2 Token的常见陷阱

我在项目中踩过几个坑,分享给你:

  • Token过期时间太短:会议开到一半Token过期,客户端被迫重连。建议设置合理的过期时间,或者支持Token刷新机制。
  • Token中不包含房间信息:攻击者可以用一个Token加入任意房间。一定要在Token中绑定用户可访问的房间ID。
  • Token未做签名校验:客户端可以伪造Token。服务端必须验证签名,拒绝未签名的Token。

我的建议:Token中除了用户ID和房间ID,还可以加入权限级别。比如普通用户只能发言,主持人可以踢人、静音。这样一套Token系统,就能覆盖大部分权限控制需求。

25.4 安全配置清单

最后,我整理了一份安全配置清单,你可以对照检查自己的项目:

安全项 要求 检查方法
DTLS-SRTP 必须开启 抓包查看SRTP包是否加密
信令通道 必须使用WSS 检查WebSocket URL是否为wss://
消息签名 建议开启 检查信令消息是否包含HMAC
Token鉴权 必须实现 检查连接时是否携带有效Token
Token过期 建议设置 检查Token中是否包含exp字段
房间隔离 必须实现 检查Token中是否绑定房间ID

嗯,安全这块就聊这么多。说白了,WebRTC的安全没有银弹,需要层层设防。从媒体加密到信令安全,再到身份鉴权,每一层都不能少。我见过太多项目只做了DTLS-SRTP,结果信令通道裸奔,Token鉴权形同虚设。别学他们,安全这事,宁可过度设计,也不能留死角。

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