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

如何在Android应用中将PEM PKCS#1格式公钥字符串转为PublicKey对象

如何将PEM PKCS#1格式公钥转换为PublicKey对象

你当前代码处理PKCS#1公钥时的错误在于:直接将Base64解码后的字节当作RSA模数(modulus),但PKCS#1格式的RSA公钥是ASN.1 DER编码的SEQUENCE结构,包含模数和公钥指数两个字段,不能直接提取模数。以下是两种可行的解决方案:

方案一:将PKCS#1转换为PKCS#8格式(推荐)

PKCS#8格式的公钥是标准的X.509 SubjectPublicKeyInfo结构,Java/Android的X509EncodedKeySpec可以直接解析。我们可以把PKCS#1的内容包装成PKCS#8的结构,依赖BouncyCastle库简化ASN.1解析:

修改后的代码

import java.math.BigInteger
import java.security.KeyFactory
import java.security.KeySpec
import java.security.PublicKey
import java.security.spec.RSAPublicKeySpec
import java.security.spec.X509EncodedKeySpec
import android.util.Base64
import org.bouncycastle.asn1.ASN1Integer
import org.bouncycastle.asn1.ASN1Sequence
import org.bouncycastle.asn1.DERNull
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers
import org.bouncycastle.asn1.x509.AlgorithmIdentifier
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
import timber.log.Timber

const val PKCS1PublicHeader = "-----BEGIN RSA PUBLIC KEY-----"
const val PKCS1PublicFooter = "-----END RSA PUBLIC KEY-----"
const val PKCS8PublicHeader = "-----BEGIN PUBLIC KEY-----"
const val PKCS8PublicFooter = "-----END PUBLIC KEY-----"

fun String.pemToPublicKey(algorithm: String): PublicKey? {
    return try {
        val isRSAPublicKey = this.contains(PKCS1PublicHeader)
        val cleanedKeyContent = this
            .replace("\r\n", "")
            .replace("\n", "")
            .let {
                if (isRSAPublicKey) {
                    it.replace(PKCS1PublicHeader, "").replace(PKCS1PublicFooter, "")
                } else {
                    it.replace(PKCS8PublicHeader, "").replace(PKCS8PublicFooter, "")
                }
            }

        val keyBytes = Base64.decode(cleanedKeyContent, Base64.NO_WRAP)
        val keySpec: KeySpec = if (isRSAPublicKey) {
            // 解析PKCS#1的RSAPublicKey结构
            val rsaPublicKey = ASN1Sequence.getInstance(keyBytes)
            val modulus = ASN1Integer.getInstance(rsaPublicKey.getObjectAt(0)).positiveValue
            val exponent = ASN1Integer.getInstance(rsaPublicKey.getObjectAt(1)).positiveValue

            // 构建PKCS#8的SubjectPublicKeyInfo结构
            val algorithmIdentifier = AlgorithmIdentifier(
                PKCSObjectIdentifiers.rsaEncryption,
                DERNull.INSTANCE
            )
            val subjectPublicKeyInfo = SubjectPublicKeyInfo(algorithmIdentifier, rsaPublicKey.toByteArray())
            X509EncodedKeySpec(subjectPublicKeyInfo.encoded)
        } else {
            X509EncodedKeySpec(keyBytes)
        }

        val keyFactory = KeyFactory.getInstance(algorithm)
        keyFactory.generatePublic(keySpec)
    } catch (e: Exception) {
        Timber.e(e, "Invalid PEM key: $this")
        null
    }
}

依赖说明

需要在项目Gradle中添加BouncyCastle依赖:

implementation 'org.bouncycastle:bcprov-jdk15on:1.70'

方案二:手动解析PKCS#1的ASN.1结构(无第三方依赖)

如果不想引入外部库,可以手动处理DER编码的ASN.1规则,直接提取模数和指数:

修改后的代码

import java.math.BigInteger
import java.security.KeyFactory
import java.security.KeySpec
import java.security.PublicKey
import java.security.spec.RSAPublicKeySpec
import java.security.spec.X509EncodedKeySpec
import android.util.Base64
import timber.log.Timber

const val PKCS1PublicHeader = "-----BEGIN RSA PUBLIC KEY-----"
const val PKCS1PublicFooter = "-----END RSA PUBLIC KEY-----"
const val PKCS8PublicHeader = "-----BEGIN PUBLIC KEY-----"
const val PKCS8PublicFooter = "-----END PUBLIC KEY-----"

fun String.pemToPublicKey(algorithm: String): PublicKey? {
    return try {
        val isRSAPublicKey = this.contains(PKCS1PublicHeader)
        val cleanedKeyContent = this
            .replace("\r\n", "")
            .replace("\n", "")
            .let {
                if (isRSAPublicKey) {
                    it.replace(PKCS1PublicHeader, "").replace(PKCS1PublicFooter, "")
                } else {
                    it.replace(PKCS8PublicHeader, "").replace(PKCS8PublicFooter, "")
                }
            }

        val keyBytes = Base64.decode(cleanedKeyContent, Base64.NO_WRAP)
        val keySpec: KeySpec = if (isRSAPublicKey) {
            // 手动解析PKCS#1的DER结构
            var offset = 0
            // 跳过SEQUENCE标签和长度
            if (keyBytes[offset++] != 0x30) throw IllegalArgumentException("Invalid PKCS#1 key")
            var length = keyBytes[offset++].toInt() and 0xFF
            if (length and 0x80 != 0) {
                val lenBytes = length and 0x7F
                length = 0
                for (i in 0 until lenBytes) {
                    length = (length shl 8) or (keyBytes[offset++].toInt() and 0xFF)
                }
            }
            // 读取模数
            if (keyBytes[offset++] != 0x02) throw IllegalArgumentException("Invalid modulus tag")
            var modLength = keyBytes[offset++].toInt() and 0xFF
            if (modLength and 0x80 != 0) {
                val lenBytes = modLength and 0x7F
                modLength = 0
                for (i in 0 until lenBytes) {
                    modLength = (modLength shl 8) or (keyBytes[offset++].toInt() and 0xFF)
                }
            }
            val modulusBytes = ByteArray(modLength)
            System.arraycopy(keyBytes, offset, modulusBytes, 0, modLength)
            offset += modLength
            val modulus = BigInteger(1, modulusBytes)

            // 读取公钥指数
            if (keyBytes[offset++] != 0x02) throw IllegalArgumentException("Invalid exponent tag")
            var expLength = keyBytes[offset++].toInt() and 0xFF
            if (expLength and 0x80 != 0) {
                val lenBytes = expLength and 0x7F
                expLength = 0
                for (i in 0 until lenBytes) {
                    expLength = (expLength shl 8) or (keyBytes[offset++].toInt() and 0xFF)
                }
            }
            val exponentBytes = ByteArray(expLength)
            System.arraycopy(keyBytes, offset, exponentBytes, 0, expLength)
            val exponent = BigInteger(1, exponentBytes)

            RSAPublicKeySpec(modulus, exponent)
        } else {
            X509EncodedKeySpec(keyBytes)
        }

        val keyFactory = KeyFactory.getInstance(algorithm)
        keyFactory.generatePublic(keySpec)
    } catch (e: Exception) {
        Timber.e(e, "Invalid PEM key: $this")
        null
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 21:26:36