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
相关产品推荐
相关产品推荐

