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

Python可正常读取的EC公钥,在Kotlin(Android)加载时报错求助

Python转Android Kotlin时EC公钥加载问题及解决

问题概述

将代码从Python迁移到Android平台Kotlin时,遇到EC公钥加载失败的问题。目标公钥为:

MDkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDIgAC2X07fCab+nIPAWBb5eRlhdOfR0Bkrhx7TgM3cGbR31g=

Python端可行实现

该公钥可通过以下Python代码成功读取:

key = bytearray.fromhex(
   "3039301306072a8648ce3d020106082a8648ce3d030107032200"
) + bytearray([0x03]) + bytearray.fromhex("d97d3b7c269bfa720f01605be5e46585d39f474064ae1c7b4e03377066d1df58")
device_public_key = load_der_public_key(
                key
            )

打印结果为:

<cryptography.hazmat.backends.openssl.ec._EllipticCurvePublicKey object at 0x102372cd0>

Kotlin端初始失败实现

尝试用Kotlin实现相同功能时抛出异常,代码如下:

fun loadKey() {
   
    val key  = Hex.decode("3039301306072a8648ce3d020106082a8648ce3d03010703220003d97d3b7c269bfa720f01605be5e46585d39f474064ae1c7b4e03377066d1df58")
    try {
        read(key)
    } catch (e: Exception) {
        println("Failed to load for sign $sign: $e")
    }
}

private fun read(publicKey: ByteArray) {
    val spec: ECNamedCurveParameterSpec = ECNamedCurveTable.getParameterSpec("prime256v1")
    val kf = KeyFactory.getInstance("ECDSA", BouncyCastleProvider())
    val params = ECNamedCurveSpec("prime256v1", spec.curve, spec.g, spec.n)
    val point: ECPoint = ECPointUtil.decodePoint(params.curve, publicKey)
    val pubKeySpec = ECPublicKeySpec(point, params)
    val t =  kf.generatePublic(pubKeySpec) as ECPublicKey
    println(t)
}

抛出异常:

java.lang.IllegalArgumentException: Invalid point encoding 0x30

该公钥由固定头部、标识字节和32字节X坐标拼接而成:

Compressed ephemeral public key used for ECDH w/ reader private key, 32 byte X coordinate. Note: when uncompressing, the Y coordinate is always even

解决方案(Android平台JVM兼容Kotlin)

方案1:从私钥关联曲线加载压缩公钥

仅使用标识字节(0x02)+ X坐标的压缩公钥,通过私钥关联的曲线解析:

fun load_compressed_public_key(privateKey: ECPrivateKey, compressedKey: ByteArray): ECPublicKey {
            val decodePoint = org.bouncycastle.jce.ECPointUtil.decodePoint(privateKey.params.curve, compressedKey)
            val spec = ECNamedCurveTable.getParameterSpec("secp256r1")
            val kf = java.security.KeyFactory.getInstance("ECDSA", BouncyCastleProvider())
            val params = ECNamedCurveSpec("secp256r1", spec.curve, spec.g, spec.n)
            val pubKeySpec = java.security.spec.ECPublicKeySpec(decodePoint, params)
            val uncompressed = kf.generatePublic(pubKeySpec) as ECPublicKey
            return uncompressed
        }

方案2:直接加载完整X.509格式公钥

最初尝试失败的原因是创建KeyFactory时未指定BouncyCastleProvider,添加后即可正常加载完整的X.509格式公钥:

@Test
fun `test load ephemeral x509`(){
    val spki: ByteArray = Hex.decode(
        "3039301306072a8648ce3d020106082a8648ce3d030107032200"
                + "02d97d3b7c269bfa720f01605be5e46585d39f474064ae1c7b4e03377066d1df58"
    )
    val kf1: KeyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider())
    val pub1: PublicKey = kf1.generatePublic(X509EncodedKeySpec(spki))
    println(pub1)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.11 11:12:13