必须在应用层做端到端加密(E2EE),因wss://仅保障TLS传输安全,服务端内存、日志、恶意扩展仍可获取明文;密钥须动态下发、AES-GCM加密、IV每次随机且12字节、附加时间戳和client_id校验。

WebSocket 本身不加密消息内容,wss:// 只保障传输层 TLS 安全,一旦连接落地到服务端内存、被日志误打、或被恶意扩展读取,明文就暴露了。真正防窃听、防篡改、防日志泄露,必须在应用层做端到端加密(E2EE),且密钥不能写死、不能复用、不能绕过校验。
用 Web Crypto API 做 AES-GCM 加密(推荐)
现代浏览器原生支持 window.crypto.subtle,无需引入 CryptoJS 等第三方库,更安全、无依赖、性能好:
- 密钥必须由服务端动态下发(例如绑定
session_id或短期access_token),绝不可硬编码在 JS 里 - 固定使用
AES-GCM:它自带认证标签(tag),解密失败自动丢弃,避免填充预言攻击 - IV 每次随机生成,严格为 12 字节(96 bit),和密文一起 base64 传给服务端;重复使用 IV 会导致密文可被破解
- 加密前把 JSON 消息转成
Uint8Array,用TextEncoder编码,避免中文乱码或 URI 编码污染 - 附加数据(
additionalData)建议传时间戳 + client_id,服务端解密后可校验是否超时、来源是否合法
用 RSA + AES 混合加密(适合需角色隔离的场景)
当服务端需向不同角色广播同一消息(如管理员看全量、普通用户看脱敏字段),纯对称加密不够用,需混合方案:
- 前端每次建立 WebSocket 连接前,先通过 HTTPS 接口获取服务端 RSA 公钥(建议带签名防篡改),存入
sessionStorage - AES 密钥必须每次会话随机生成,长度为 32 字节(AES-256),IV 同样随机且不可复用
- 用公钥加密 AES 密钥(非消息体),再用该 AES 密钥加密实际消息,结构统一为:
{ encryptedKey, iv, data } - 服务端用私钥解出 AES 密钥,再解密消息体;不同角色可配不同私钥或解密策略,实现细粒度控制
关键避坑点(实操高频问题)
很多项目上线后被审计发现“看似加密,实则白加”,往往栽在这几处:
立即学习“Java免费学习笔记(深入)”;
- Nginx 配了
wss代理,但后端 WebSocket 服务走的是内网ws://或 HTTP,TLS 在 Nginx 终止,之后全程明文 - 前端
onmessage回调里直接console.log(event.data),调试器或内存 dump 一抓一个准 - 服务端日志未脱敏,
logger.info(msg)打的是解密后的原始 JSON,日志系统成了明文数据库 - IV 复用、密钥写死、用
AES-CBC无认证、字符串直接encodeURI编码——这些都会让加密形同虚设
解密逻辑必须落在客户端接收后
端到端加密的核心是:服务端只透传密文,不接触明文。解密动作必须在 websocket.onmessage 回调中完成:
- 收到消息后,先提取
iv、data、tag(GCM)或encryptedKey(RSA+AES) - 从本地安全上下文(如已缓存的密钥对象或 sessionStorage 中的密钥标识)获取对应密钥
- 调用
crypto.subtle.decrypt解密,失败则丢弃,不抛错、不重试、不打明文日志 - 解密成功后,再做业务解析(如
JSON.parse),并立即清除敏感中间变量(如Uint8Array缓冲区)


















