前端用WebSocket加密时,怎么安全交换密钥避免被中间人截获?
我在用WebSocket做实时通信时,想给消息加密,但卡在密钥交换环节。试过让客户端生成RSA密钥对,把公钥发给服务器,但发现握手时公钥是明文传输的,如果中间人截获公钥再伪造响应,不是照样能解密吗?
后来改用Diffie-Hellman算法生成共享密钥,但浏览器控制台报错ERR_SSL_PROTOCOL_ERROR,怀疑是自签名证书的问题。现在纠结是应该通过HTTPS先交换密钥参数,还是直接在WebSocket握手阶段加密传输?有没有更安全的实现方式?
// 客户端尝试发送公钥
const ws = new WebSocket('wss://example.com');
ws.onopen = () => {
const keyPair = crypto.generateKeyPairSync('rsa', { modulusLength: 2048 });
ws.send(keyPair.publicKey.export({ type: 'spki' }));
};
解决方式其实不复杂:先用HTTPS交换密钥参数,再用WebSocket加密通信。因为HTTPS有证书验证机制,能确保你连接的是目标服务器而不是中间人。WebSocket本身是基于TCP的裸协议,没有内置的身份验证,所以密钥协商应该放在HTTPS接口里完成。
下面是一个简单流程:
1. 客户端通过HTTPS请求服务器,获取服务器的RSA公钥(或者DH参数)
2. 客户端生成一个对称密钥(比如AES密钥),用服务器的公钥加密后发回去
3. 服务器解密后保存这个对称密钥,之后WebSocket通信就用它加密
4. WebSocket连接建立后,双方用之前协商好的密钥通信
示例代码:
// 服务器端(Node.js + Express):
app.get('/exchange', (req, res) => {
const keyPair = crypto.generateKeyPairSync('rsa', { modulusLength: 2048 });
res.json({ publicKey: keyPair.publicKey.export({ type: 'spki' }) });
});
// 客户端:
fetch('https://example.com/exchange')
.then(res => res.json())
.then(data => {
const secret = crypto.randomBytes(16); // AES-128
const encrypted = crypto.publicEncrypt(data.publicKey, secret);
fetch('https://example.com/setkey', {
method: 'POST',
body: encrypted
}).then(() => {
const ws = new WebSocket('wss://example.com');
ws.onopen = () => {
// 用secret加密消息
};
});
});
这样就能避免中间人窃听密钥了。WebSocket握手阶段本身不加密,别指望它能扛中间人攻击。你要是真想一步到位,也可以考虑用wss+服务器证书验证,但不如先走HTTPS简单直接。
最靠谱的方式是先通过HTTPS建立一个安全通道,用TLS协商共享密钥参数。你可以用ECDH算法,在HTTPS握手时完成密钥交换,然后再启动WebSocket连接。
大致流程是这样的:
1. 客户端先发起一个HTTPS请求到服务器,获取服务器的DH参数
2. 双方基于这些参数计算出共享密钥
3. 再用这个共享密钥来加密WebSocket的消息
至于你提到的ERR_SSL_PROTOCOL_ERROR,大概率是证书配置问题,建议检查一下服务器的SSL证书是否正确安装。千万别用自签名证书搞生产环境,这会带来新的安全隐患。
最后吐槽一句,前端搞加密真是又爱又恨,既要保证安全性又要兼顾性能,真心不容易。