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

如何在Kotlin中加载RSA公钥?无需第三方库解决PKCS1格式公钥解析异常问题

Convert PKCS#1 RSA Public Key to PKCS#8/X.509 Without Third-Party Libraries

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 RSAPublicKey sequence.
  • PKCS#8/X.509: The format your loadPublicKey function uses, wrapped in an ASN.1 SubjectPublicKeyInfo sequence 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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 13:02:40