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

