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
相关产品推荐
相关产品推荐

