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

Android Kotlin中如何用P-256(secp256k1)ECDSA私钥签名并转PKCS#8

Android下SEC1格式EC私钥转PKCS#8并签名解决方案

错误根源

你当前的PEM私钥是SEC1格式(标识为-----BEGIN EC PRIVATE KEY-----),但代码中错误地使用PKCS8EncodedKeySpec解析它——PKCS#8格式的私钥DER编码结构与SEC1完全不同,这直接导致了解析失败。

可行方案(无需引入外部Bouncy Castle库)

Android API 29+(对应targetSdk33完全满足)的系统内置加密组件支持直接处理SEC1格式私钥,以下提供两种无需额外依赖的实现方式:

方案1:直接解析SEC1私钥生成签名

通过解析SEC1的ASN.1结构提取私钥参数,结合固定的secp256k1曲线参数生成私钥:

import java.math.BigInteger
import java.security.KeyFactory
import java.security.spec.ECParameterSpec
import java.security.spec.ECPrivateKeySpec
import java.security.spec.EllipticCurve
import android.util.Base64
import org.bouncycastle.asn1.sec.ECPrivateKey
import org.bouncycastle.asn1.x9.X9NamedCurves

fun parseSec1EcPrivateKey(pem: String): java.security.PrivateKey {
    // 清理PEM格式并解码Base64
    val cleanPem = pem
        .replace("-----BEGIN EC PRIVATE KEY-----", "")
        .replace("-----END EC PRIVATE KEY-----", "")
        .replace("\\s".toRegex(), "")
    val derBytes = Base64.decode(cleanPem, Base64.DEFAULT)

    // 解析SEC1格式的ASN.1结构
    val ecPrivKey = ECPrivateKey.getInstance(derBytes)
    val privateKeyBigInt = ecPrivKey.key

    // 获取secp256k1曲线的标准参数
    val x9Params = X9NamedCurves.getByName("secp256k1")
    val ecSpec = ECParameterSpec(
        EllipticCurve(
            x9Params.curve.field.characteristic,
            x9Params.curve.a.toBigInteger(),
            x9Params.curve.b.toBigInteger()
        ),
        java.security.spec.ECPoint(
            x9Params.g.xCoord.toBigInteger(),
            x9Params.g.yCoord.toBigInteger()
        ),
        x9Params.n,
        x9Params.h.toInt()
    )

    // 生成私钥对象
    val keyFactory = KeyFactory.getInstance("EC")
    return keyFactory.generatePrivate(ECPrivateKeySpec(privateKeyBigInt, ecSpec))
}

fun signData(privateKey: java.security.PrivateKey, dataHex: String): ByteArray {
    val data = hexStringToByteArray(dataHex)
    val signature = java.security.Signature.getInstance("SHA256withECDSA")
    signature.initSign(privateKey)
    signature.update(data)
    return signature.sign()
}

fun hexStringToByteArray(hex: String): ByteArray {
    val len = hex.length
    val data = ByteArray(len / 2)
    for (i in 0 until len step 2) {
        data[i / 2] = ((Character.digit(hex[i], 16) shl 4)
                + Character.digit(hex[i + 1], 16)).toByte()
    }
    return data
}

// 测试代码
fun main() {
    val privateKeyPem = """
        -----BEGIN EC PRIVATE KEY-----
        MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg0m4yLz+sdzZtBG9Q
        HQ9++wcfq1O4hOWgSBMb/A6eijyhRANCAAQeB0fBl2D7HZOKVBjpPiU2jabzNxQU
        ZYrJ+MSA3LpzZxmRk2JaFHNujjkJghQT19HHjg3Fnkb8Y9oIhB9neXBI
        -----END EC PRIVATE KEY-----
    """.trimIndent()

    val dataHex = "48656c6c6f2c20576f726c6421"

    try {
        val privateKey = parseSec1EcPrivateKey(privateKeyPem)
        val signatureBytes = signData(privateKey, dataHex)
        println("签名结果: ${Base64.encodeToString(signatureBytes, Base64.DEFAULT)}")
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

方案2:将SEC1格式转换为PKCS#8格式

手动构造PKCS#8的ASN.1结构,将SEC1私钥包装为标准PKCS#8格式后再解析:

import java.security.KeyFactory
import java.security.spec.PKCS8EncodedKeySpec
import android.util.Base64
import org.bouncycastle.asn1.ASN1EncodableVector
import org.bouncycastle.asn1.ASN1ObjectIdentifier
import org.bouncycastle.asn1.DERSequence
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo

fun convertSec1ToPkcs8(pem: String): ByteArray {
    // 清理PEM并解码SEC1格式DER
    val cleanPem = pem
        .replace("-----BEGIN EC PRIVATE KEY-----", "")
        .replace("-----END EC PRIVATE KEY-----", "")
        .replace("\\s".toRegex(), "")
    val sec1Der = Base64.decode(cleanPem, Base64.DEFAULT)

    // secp256k1的标准OID
    val secp256k1Oid = ASN1ObjectIdentifier("1.3.132.0.10")

    // 构造PKCS#8的算法标识符
    val algId = DERSequence(
        ASN1EncodableVector().apply {
            add(secp256k1Oid)
            add(DERSequence(ASN1EncodableVector().apply { add(secp256k1Oid) }))
        }
    )

    // 生成PKCS#8格式的DER编码
    val privateKeyInfo = PrivateKeyInfo(algId, sec1Der)
    return privateKeyInfo.encoded
}

fun parsePkcs8PrivateKey(pkcs8Der: ByteArray): java.security.PrivateKey {
    val keyFactory = KeyFactory.getInstance("EC")
    return keyFactory.generatePrivate(PKCS8EncodedKeySpec(pkcs8Der))
}

// 测试代码
fun main() {
    val privateKeyPem = """
        -----BEGIN EC PRIVATE KEY-----
        MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg0m4yLz+sdzZtBG9Q
        HQ9++wcfq1O4hOWgSBMb/A6eijyhRANCAAQeB0fBl2D7HZOKVBjpPiU2jabzNxQU
        ZYrJ+MSA3LpzZxmRk2JaFHNujjkJghQT19HHjg3Fnkb8Y9oIhB9neXBI
        -----END EC PRIVATE KEY-----
    """.trimIndent()

    val dataHex = "48656c6c6f2c20576f726c6421"

    try {
        val pkcs8Der = convertSec1ToPkcs8(privateKeyPem)
        val privateKey = parsePkcs8PrivateKey(pkcs8Der)
        val signatureBytes = signData(privateKey, dataHex)
        println("签名结果: ${Base64.encodeToString(signatureBytes, Base64.DEFAULT)}")
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

关键说明

  • 方案中使用的org.bouncycastle类是Android系统内置的核心加密组件,无需引入任何第三方依赖,完全符合targetSdk33的项目限制。
  • Android 10及以上默认使用Conscrypt作为加密provider,对上述逻辑提供完整支持;如需兼容更低版本,可显式指定provider:KeyFactory.getInstance("EC", "Conscrypt")。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 13:25:54