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

Android Java客户端与Node.js服务器DH密钥交换遇InvalidKeySpecException

问题原因

你遇到的错误是因为Java端的X509EncodedKeySpec期望的是带完整ASN.1结构的X.509格式DER编码公钥,而Node.js端发送的只是DH公钥的裸字节(仅公钥值y),缺少DH算法必需的素数p和生成元g参数信息,导致Java无法解析这个裸字节数组为合法的公钥实例。

同时原Java代码的TCP读取逻辑存在隐患:inputStream.read(buffer)无法保证一次性读取完整的公钥字符串,可能导致截断。

解决方案

需要让Node.js端同步发送DH的核心参数(素数p、生成元g、公钥y),Java端解析这些参数后手动构造DH公钥,而不是依赖X.509格式的自动解析。

1. 修改Node.js服务器代码

将DH参数和公钥打包成JSON发送,确保客户端能获取完整的算法参数:

const net = require("net");
const crypto = require("crypto");
const dh = crypto.createDiffieHellman(512);
const serverDiffieHellman = crypto.generateDiffieHellman(dh.getPrime());

net.createServer((socket) => {
    // 打包DH参数与公钥为JSON,换行符作为结束标记
    const dhData = {
        prime: dh.getPrime().toString("base64"),
        generator: dh.getGenerator().toString("base64"),
        publicKey: serverDiffieHellman.getPublicKey().toString("base64")
    };
    socket.write(JSON.stringify(dhData) + "\n");
}).listen(1211, "185.255.95.248", () => console.log("TCP server is running..."));

2. 修改Android客户端Java代码

解析服务器发送的JSON参数,手动构造DH公钥:

import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.net.Socket;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.DHParameterSpec;
import java.security.spec.DHPublicKeySpec;
import android.util.Base64;
import android.util.Log;

public boolean generateSharedSecret() throws Exception {
    // 读取完整的DH参数JSON
    String dhParamsJson = receiveDhParams();
    JSONObject json = new JSONObject(dhParamsJson);

    // 解析参数字节数组
    byte[] primeBytes = Base64.decode(json.getString("prime"), Base64.DEFAULT);
    byte[] generatorBytes = Base64.decode(json.getString("generator"), Base64.DEFAULT);
    byte[] serverPubKeyBytes = Base64.decode(json.getString("publicKey"), Base64.DEFAULT);

    // 构建DH参数规格
    BigInteger p = new BigInteger(1, primeBytes);
    BigInteger g = new BigInteger(1, generatorBytes);
    DHParameterSpec dhParamSpec = new DHParameterSpec(p, g);

    // 构建服务器公钥规格
    BigInteger serverPubKeyY = new BigInteger(1, serverPubKeyBytes);
    DHPublicKeySpec dhPubKeySpec = new DHPublicKeySpec(serverPubKeyY, p, g);

    // 生成服务器公钥实例
    KeyFactory keyFactory = KeyFactory.getInstance("DH");
    PublicKey dhPublicKeyFromServer = keyFactory.generatePublic(dhPubKeySpec);

    // 后续生成共享密钥逻辑(示例:用本地DH私钥计算)
    // KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("DH");
    // keyPairGen.initialize(dhParamSpec);
    // KeyPair clientKeyPair = keyPairGen.generateKeyPair();
    // KeyAgreement keyAgreement = KeyAgreement.getInstance("DH");
    // keyAgreement.init(clientKeyPair.getPrivate());
    // keyAgreement.doPhase(dhPublicKeyFromServer, true);
    // byte[] sharedSecret = keyAgreement.generateSecret();

    return true;
}

private String receiveDhParams() throws Exception {
    Socket socket = new Socket("185.255.95.248", 1211);
    BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
    // 读取完整一行(服务器发送时添加了换行符作为结束标记)
    String jsonStr = reader.readLine();
    reader.close();
    socket.close();
    return jsonStr;
}
关键说明
  • DH算法要求通信双方必须使用相同的素数p和生成元g,因此必须同步这些参数,不能仅发送裸公钥。
  • 改用JSON打包参数是最直观的方式,避免了手动构造ASN.1/DER编码的复杂操作。
  • 用BufferedReader.readLine()读取TCP数据,能确保获取完整的参数内容,解决了原代码可能的截断问题。

内容的提问来源于stack exchange,提问作者user26341567

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 13:27:35