如何保护Java P2P协议中Diffie-Hellman密钥交换免受MITM攻击
如何保护Diffie-Hellman密钥交换免受中间人攻击?
你的DH密钥交换实现目前缺少身份验证环节——中间人可以拦截双方发送的公钥,替换成自己的DH公钥,分别和你、对方生成独立的共享密钥,从而完全窃听、篡改通信。以下是几种适配P2P场景的解决方案:
一、预共享密钥(PSK)验证法
如果双方预先通过可信渠道共享过一个密钥(比如线下约定的密码、设备配对码),可以用这个密钥对DH公钥做签名验证:
修改发送公钥逻辑
private void sendPublicKey() { try { // 假设preSharedKey是预先共享的SecretKey(可由约定密码生成) Mac hmac = Mac.getInstance("HmacSHA256"); hmac.init(preSharedKey); // 对DH公钥生成HMAC签名 byte[] signature = hmac.doFinal(publicKey.getEncoded()); // 把公钥和签名一起序列化发送 Message message = new Message(null, null, Base64.getEncoder().encodeToString(publicKey.getEncoded()), Base64.getEncoder().encodeToString(signature)); String json = JsonUtils.serializeData(new Data(Data.DataType.PUBLIC_KEY, message, null)); out.println(json); } catch (Exception e) { e.printStackTrace(); } }
修改接收公钥逻辑
public void receivePublicKey(String encodedPublicKey, String encodedSignature) { try { byte[] decodedKey = Base64.getDecoder().decode(encodedPublicKey); byte[] decodedSignature = Base64.getDecoder().decode(encodedSignature); // 用预共享密钥验证签名 Mac hmac = Mac.getInstance("HmacSHA256"); hmac.init(preSharedKey); byte[] computedSignature = hmac.doFinal(decodedKey); if (!Arrays.equals(computedSignature, decodedSignature)) { Logger.logMessage("公钥签名验证失败,可能存在中间人攻击", Logger.LogType.error); return; } // 验证通过后再处理公钥 X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey); KeyFactory keyFactory = KeyFactory.getInstance("DH"); receivedPublicKey = keyFactory.generatePublic(keySpec); generateSharedSecret(); } catch (Exception e) { e.printStackTrace(); } }
二、数字证书签名验证
引入轻量PKI体系,双方持有可信CA签发的身份证书,发送DH公钥时用自己的证书私钥签名,对方通过证书验证公钥归属:
修改发送逻辑
private void sendPublicKey() { try { // 假设已有自己的证书私钥(privateCertKey)和证书(certificate) Signature sig = Signature.getInstance("SHA256withRSA"); sig.initSign(privateCertKey); sig.update(publicKey.getEncoded()); byte[] signature = sig.sign(); // 发送DH公钥、证书、签名 Message message = new Message(null, null, Base64.getEncoder().encodeToString(publicKey.getEncoded()), Base64.getEncoder().encodeToString(certificate.getEncoded()), Base64.getEncoder().encodeToString(signature)); String json = JsonUtils.serializeData(new Data(Data.DataType.PUBLIC_KEY, message, null)); out.println(json); } catch (Exception e) { e.printStackTrace(); } }
修改接收逻辑
public void receivePublicKey(String encodedPublicKey, String encodedCert, String encodedSignature) { try { byte[] decodedKey = Base64.getDecoder().decode(encodedPublicKey); byte[] decodedCert = Base64.getDecoder().decode(encodedCert); byte[] decodedSignature = Base64.getDecoder().decode(encodedSignature); // 1. 解析并验证证书(验证CA签名、有效期、身份匹配) CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); X509Certificate certificate = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(decodedCert)); certificate.verify(caPublicKey); // caPublicKey是可信CA的公钥 certificate.checkValidity(); // 2. 用证书公钥验证DH公钥的签名 Signature sig = Signature.getInstance("SHA256withRSA"); sig.initVerify(certificate.getPublicKey()); sig.update(decodedKey); if (!sig.verify(decodedSignature)) { Logger.logMessage("公钥签名验证失败", Logger.LogType.error); return; } // 3. 验证通过后处理公钥 X509EncodedKeySpec keySpec = new X509EncodedKeySpec(decodedKey); KeyFactory keyFactory = KeyFactory.getInstance("DH"); receivedPublicKey = keyFactory.generatePublic(keySpec); generateSharedSecret(); } catch (Exception e) { e.printStackTrace(); } }
三、短身份验证字符串(SAS)
适合无预共享密钥、无CA的P2P场景,双方基于共享密钥生成一个短字符串,通过线下/其他可信渠道(比如语音、二维码)确认一致:
修改共享密钥生成逻辑
private void generateSharedSecret() { try { KeyAgreement keyAgreement = KeyAgreement.getInstance("DH"); keyAgreement.init(privateKey); keyAgreement.doPhase(receivedPublicKey, true); byte[] sharedSecret = keyAgreement.generateSecret(); secretKey = new SecretKeySpec(sharedSecret, 0, 32, "AES"); // 生成SAS验证字符串 Mac hmac = Mac.getInstance("HmacSHA256"); hmac.init(secretKey); byte[] sasBytes = hmac.doFinal("P2P_SAS_VERIFY".getBytes(StandardCharsets.UTF_8)); String sas = new BigInteger(1, sasBytes).toString().substring(0, 6); // 取前6位数字 System.out.println("请对方确认SAS:" + sas); // 等待对方发送其生成的SAS并对比(需补充接收逻辑) if (!receivedSAS.equals(sas)) { Logger.logMessage("SAS验证失败,存在中间人攻击", Logger.LogType.error); return; } Logger.logMessage("SAS验证通过,共享密钥生成成功", Logger.LogType.info); _HCP = new HaritaCP(); validateSecretKey(); } catch (Exception e) { e.printStackTrace(); } }
关键注意事项
- 所有方案的核心都是验证对方公钥的真实身份,确保你拿到的公钥确实来自预期的通信方。
- 优先选择ECDH(椭圆曲线DH)替代传统DH,相同安全强度下密钥更短、计算更快,结合ECDSA签名更适合P2P场景。
内容的提问来源于stack exchange,提问作者Fanififfs
相关产品推荐
相关产品推荐

