如何在Kotlin中加载RSA公钥?无需第三方库解决PKCS1格式公钥解析异常问题
Hey there! Let's figure out how to convert that PKCS#1 RSA public key (the one starting with -----BEGIN RSA PUBLIC KEY-----) to the PKCS#8/X.509 format your code expects—no third-party libraries needed.
First, let's quickly clarify the difference between the two formats:
- PKCS#1: The format you have, which directly encodes the RSA public key components (modulus and exponent) in an ASN.1
RSAPublicKeysequence. - PKCS#8/X.509: The format your
loadPublicKeyfunction uses, wrapped in an ASN.1SubjectPublicKeyInfosequence that includes the RSA algorithm identifier alongside the key data.
Solution 1: Use Android's Native ASN.1 Parsing Classes
Android includes a built-in ASN.1 parsing library under org.apache.harmony.security.asn1 that we can leverage to convert the key structure:
import android.util.Base64 import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.apache.harmony.security.asn1.ASN1Integer import org.apache.harmony.security.asn1.ASN1Null import org.apache.harmony.security.asn1.ASN1ObjectIdentifier import org.apache.harmony.security.asn1.ASN1Sequence import org.apache.harmony.security.asn1.BEROutputStream import java.security.KeyFactory import java.security.spec.X509EncodedKeySpec private fun convertPkcs1ToX509(pkcs1Key: String): ByteArray { // Clean up the PKCS#1 key string and decode from Base64 val cleanedKey = pkcs1Key .replace("-----BEGIN RSA PUBLIC KEY-----", "") .replace("-----END RSA PUBLIC KEY-----", "") .replace("\\s+".toRegex(), "") val pkcs1Der = Base64.decode(cleanedKey, Base64.DEFAULT) // Parse the PKCS#1 RSAPublicKey sequence to get modulus and exponent val asn1Sequence = ASN1Sequence.getInstance(pkcs1Der) val modulus = ASN1Integer.getInstance(asn1Sequence.getObjectAt(0)).value val publicExponent = ASN1Integer.getInstance(asn1Sequence.getObjectAt(1)).value // Build the RSA AlgorithmIdentifier (OID + NULL parameter) val algorithmOid = ASN1ObjectIdentifier("1.2.840.113549.1.1.1") val algorithmParams = ASN1Null.getInstance() val algorithmSequence = ASN1Sequence(arrayOf(algorithmOid, algorithmParams)) // Wrap the PKCS#1 key in a BIT STRING (first byte = 0 unused bits) val publicKeyBitString = byteArrayOf(0) + pkcs1Der // Build the full SubjectPublicKeyInfo sequence val subjectPublicKeyInfo = ASN1Sequence(arrayOf(algorithmSequence, publicKeyBitString)) // Encode the sequence to DER format val outputStream = BEROutputStream() subjectPublicKeyInfo.encode(subjectPublicKeyInfo, outputStream) return outputStream.byteArray } // Load PKCS#1 format public key using your existing logic suspend fun loadPkcs1PublicKey(key: String): java.security.PublicKey { return withContext(Dispatchers.IO) { val x509Der = convertPkcs1ToX509(key) KeyFactory.getInstance("RSA") .generatePublic(X509EncodedKeySpec(x509Der)) } }
Solution 2: Manual DER Byte Construction (Max Compatibility)
If you need broader compatibility across older Android versions where the Harmony ASN.1 classes might not be available, you can manually construct the DER-encoded SubjectPublicKeyInfo byte array (since the ASN.1 structure for RSA is fixed):
import android.util.Base64 import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import java.security.KeyFactory import java.security.spec.X509EncodedKeySpec private fun convertPkcs1ToX509Manual(pkcs1Key: String): ByteArray { val cleanedKey = pkcs1Key .replace("-----BEGIN RSA PUBLIC KEY-----", "") .replace("-----END RSA PUBLIC KEY-----", "") .replace("\\s+".toRegex(), "") val pkcs1Der = Base64.decode(cleanedKey, Base64.DEFAULT) // Predefined DER components for RSA AlgorithmIdentifier val oidBytes = byteArrayOf(0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01) val nullBytes = byteArrayOf(0x05, 0x00) val algorithmSequence = byteArrayOf(0x30, (oidBytes.size + nullBytes.size).toByte()) + oidBytes + nullBytes // Build BIT STRING containing the PKCS#1 key val bitStringLength = 1 + pkcs1Der.size val bitString = byteArrayOf(0x03, bitStringLength.toByte(), 0x00) + pkcs1Der // Build full SubjectPublicKeyInfo sequence val spkiLength = algorithmSequence.size + bitString.size return byteArrayOf(0x30, spkiLength.toByte()) + algorithmSequence + bitString } // Alternative load function using manual conversion suspend fun loadPkcs1PublicKeyManual(key: String): java.security.PublicKey { return withContext(Dispatchers.IO) { val x509Der = convertPkcs1ToX509Manual(key) KeyFactory.getInstance("RSA") .generatePublic(X509EncodedKeySpec(x509Der)) } }
Use With Your Existing loadPublicKey Function
If you prefer to reuse your current loadPublicKey function, you can convert the PKCS#1 key to a PKCS#8-formatted string first:
fun convertPkcs1ToX509String(pkcs1Key: String): String { val x509Der = convertPkcs1ToX509Manual(pkcs1Key) val base64 = Base64.encodeToString(x509Der, Base64.DEFAULT) return "-----BEGIN PUBLIC KEY-----\n$base64\n-----END PUBLIC KEY-----" } // Usage example: // val pkcs1Key = "-----BEGIN RSA PUBLIC KEY-----..." // val x509Key = convertPkcs1ToX509String(pkcs1Key) // val publicKey = loadPublicKey(x509Key)
Both solutions work without any external libraries—they either use Android's built-in ASN.1 tools or directly construct the required DER byte structure. Either way, you'll be able to load your RSA public key without the parsing error you were seeing.
内容的提问来源于stack exchange,提问作者Anath43ema

