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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 10:25:35