You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何保护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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 21:23:11