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

Android平台EC Diffie Hellman密钥交换验证失败求助

Android端ECDH密钥交换失败:服务器无法识别公钥(iOS正常)

问题核心

你遇到的问题本质是Android生成的EC公钥格式与服务器/ iOS预期不匹配:

  • iOS端默认输出原始未压缩EC公钥(65字节:前缀0x04 + 32字节X坐标 + 32字节Y坐标)
  • 而Android通过Key.getEncoded()获取的公钥是X.509 SubjectPublicKeyInfo格式(带ASN.1结构包装,长度约91字节),服务器无法直接解析这种格式。
    即使切换到BouncyCastle,默认输出的公钥编码同样是X.509格式,所以问题会重复出现。

解决方案:提取原始未压缩EC公钥

修改公钥生成逻辑,从AndroidKeyStore的EC公钥中提取出原始未压缩格式,再编码发送:

修改后的代码

fun getPublicKey(): String? {
    val keyPair = getKeyPair() ?: return null
    val publicKey = keyPair.public as ECPublicKey
    
    // 提取椭圆曲线坐标点
    val point = publicKey.w
    // 处理BigInteger转字节数组时的前导零缺失问题,补全到32字节(secp256r1坐标长度)
    val x = point.affineX.toByteArray().let {
        if (it.size < 32) ByteArray(32 - it.size) + it else it.takeLast(32).toByteArray()
    }
    val y = point.affineY.toByteArray().let {
        if (it.size < 32) ByteArray(32 - it.size) + it else it.takeLast(32).toByteArray()
    }
    
    // 拼接原始未压缩公钥:0x04标识 + X坐标 + Y坐标
    val rawPublicKey = ByteArray(65).apply {
        set(0, 0x04.toByte())
        System.arraycopy(x, 0, this, 1, 32)
        System.arraycopy(y, 0, this, 33, 32)
    }
    
    return Base64.encodeToString(rawPublicKey, Base64.NO_WRAP)
}

关键细节

  1. 类型强转:将公钥转为ECPublicKey,才能直接获取椭圆曲线的坐标点信息
  2. 坐标补全:BigInteger转字节数组时,正数的前导零会被省略,必须补全到32字节才能匹配secp256r1的标准长度
  3. 未压缩标识:前缀0x04是EC公钥未压缩格式的标准标识,服务器通常依赖这个标识解析坐标

额外排查项

  • 检查Base64编码参数:如果服务器要求URL安全的Base64,替换Base64.NO_WRAP为Base64.URL_SAFE or Base64.NO_WRAP
  • 确认曲线一致性:确保服务器、iOS、Android三方都使用secp256r1(又称P-256)曲线,避免曲线不匹配
  • 复用密钥对:getKeyPair()每次调用都会生成新密钥对,如果需要复用,需先从AndroidKeyStore查询是否已存在目标别名的密钥,再决定是否生成新的

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 06:42:37