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的握手过程,我画了个图,你看一眼就明白了:
握手完成后,双方就有了共享的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鉴权方案,流程如下:
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鉴权形同虚设。别学他们,安全这事,宁可过度设计,也不能留死角。