Android(Kotlin)与NodeJS加密通信公钥解析报错求助
问题根源
你遇到的错误核心原因是:NodeJS返回的公钥格式和Android端期望的格式不匹配。
NodeJS的crypto.createECDH().getPublicKey()默认返回的是裸EC公钥(仅包含公钥的X/Y坐标数据,无ASN.1/DER结构),而Android的X509EncodedKeySpec要求输入的是标准X.509格式的DER编码公钥(带算法标识、曲线参数等完整ASN.1结构),所以解析时直接抛出格式错误。
两种解决方案
方案一:修改NodeJS端,返回X.509格式公钥
在NodeJS端把裸公钥包装成符合X.509标准的DER结构,再Base64编码后返回给Android。
修正后的NodeJS代码:
const crypto = require('crypto'); const asn1 = require('asn1.js'); // 需先安装:npm install asn1.js // 定义EC公钥的ASN.1结构(对应X.509格式) const ECPublicKey = asn1.define('ECPublicKey', function() { this.seq().obj( this.key('algorithm').seq().obj( this.key('id').objid('1.2.840.10045.2.1'), // EC算法OID this.key('curve').objid('1.2.840.10045.3.1.7') // secp256r1/prime256v1曲线OID ), this.key('pub').bitstr() ); }); const publicKey = post.publicKey; // 接收Android端公钥 const keyPair = crypto.createECDH('prime256v1'); keyPair.generateKeys(); // 解析Android的X.509格式公钥(原逻辑保留) const publicKeyDer = Buffer.from(publicKey, 'base64'); const publicKeyParsed = ECPublicKey.decode(publicKeyDer, 'der'); const publicKeyBuffer = Buffer.from(publicKeyParsed.pub.data); const sharedSecret = keyPair.computeSecret(publicKeyBuffer); console.log("This is the sharedSecret:", sharedSecret.toString('base64')); // 将NodeJS裸公钥包装为X.509格式DER编码 const nodePubKeyRaw = keyPair.getPublicKey(); const nodePubKeyDer = ECPublicKey.encode({ algorithm: { id: '1.2.840.10045.2.1', curve: '1.2.840.10045.3.1.7' }, pub: { data: nodePubKeyRaw, unused: 0 } }, 'der'); // 转Base64后返回给Android const pubKeyBase = nodePubKeyDer.toString('base64'); // 回复pubKeyBase给Android端
修改后,Android端原有的X509EncodedKeySpec解析代码可直接正常工作,无需改动。
方案二:修改Android端,直接解析裸公钥
如果不想改动NodeJS端,可以在Android端处理裸公钥,通过ECPublicKeySpec生成公钥对象。
修正后的Android代码:
val publicKeyB: String = "PUBLICKEY-OF-NODEJS" val decKey: ByteArray = Base64.getDecoder().decode(publicKeyB) val keyFactory = KeyFactory.getInstance("EC") // 解析裸公钥:secp256r1曲线的非压缩裸公钥,首字节为0x04,后续32字节是X坐标,再32字节是Y坐标 val x = decKey.copyOfRange(1, 33) val y = decKey.copyOfRange(33, 65) // 获取secp256r1曲线参数 val ecParams = ECGenParameterSpec("secp256r1") val ecSpec = AlgorithmParameters.getInstance("EC").apply { init(ecParams) }.getParameterSpec(ECParameterSpec::class.java) // 生成EC公钥对象 val ecPublicKeySpec = ECPublicKeySpec(ECPoint(BigInteger(1, x), BigInteger(1, y)), ecSpec) val genPublicKey = keyFactory.generatePublic(ecPublicKeySpec) // 此处不再报错 val sharedSecret = getSharedSecret(pk.private, genPublicKey) val aesKey = getAESKey(sharedSecret)
注意:如果NodeJS端调用
getPublicKey('base64', 'compressed')生成了压缩格式公钥,需要额外处理压缩格式的坐标解析逻辑,建议默认使用非压缩格式(NodeJS默认输出非压缩格式)。
额外验证点
- secp256r1和prime256v1是同一条椭圆曲线的不同命名,双方曲线匹配,这部分无问题。
- 共享密钥生成后,两边用SHA-512哈希取前32字节生成AES-256密钥的逻辑一致,无需调整。
内容的提问来源于stack exchange,提问作者Riccoh
相关产品推荐
相关产品推荐

