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

从字符串加载RSA密钥对生成SSLSocketFactory时遇异常求排查

问题根源与解决方案

核心问题

你犯了一个关键错误:直接把PEM格式的私钥字符串转成字节数组存入KeyStore,但KeyStore的setKeyEntry方法需要的是已解析的PrivateKey对象,而不是原始的PEM字节流。BouncyCastle的KeyStore实现无法识别这种未处理的字节,因此抛出了"forget something!"的异常。

另外,你的私钥是-----BEGIN RSA PRIVATE KEY-----开头的,属于PKCS#1格式,需要专门解析成RSAPrivateKey对象,不能直接转字节存入。

修正步骤

  1. 解析PEM私钥为PrivateKey

    • 去掉私钥字符串的头尾标记,Base64解码得到DER格式字节
    • 将PKCS#1格式的私钥转换为PKCS#8格式,再用KeyFactory解析成私钥对象
  2. 修正KeyStore存入逻辑

    • 将解析后的PrivateKey传入setKeyEntry,而不是原始字节数组
  3. 优化KeyManagerFactory算法

    • 尽量用默认算法KeyManagerFactory.getDefaultAlgorithm(),避免硬编码"PKIX"带来的兼容性问题

修正后的完整代码

import java.security.KeyFactory
import java.security.KeyStore
import java.security.PrivateKey
import java.security.spec.PKCS8EncodedKeySpec
import java.security.cert.CertificateFactory
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import android.util.Base64

fun loadCertificates(privateCertificate: String, publicCertificate: String): SSLContext.SocketFactory? {
    return try {
        // 1. 清理并解码PEM格式的RSA私钥
        val cleanedPrivateKey = privateCertificate
            .replace("-----BEGIN RSA PRIVATE KEY-----", "")
            .replace("-----END RSA PRIVATE KEY-----", "")
            .replace("\n", "")
            .replace("\r", "")
        val pkcs1Bytes = Base64.decode(cleanedPrivateKey, Base64.DEFAULT)
        
        // 将PKCS#1格式转换为PKCS#8,适配KeyFactory解析
        val pkcs8Bytes = convertPkcs1ToPkcs8(pkcs1Bytes)
        val keySpec = PKCS8EncodedKeySpec(pkcs8Bytes)
        val keyFactory = KeyFactory.getInstance("RSA")
        val privateKey: PrivateKey = keyFactory.generatePrivate(keySpec)

        // 2. 解析X.509公钥证书
        val certificateFactory = CertificateFactory.getInstance("X.509")
        val publicCert = certificateFactory.generateCertificate(
            publicCertificate.byteInputStream()
        )

        // 3. 初始化KeyStore并存入密钥对
        val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())
        keyStore.load(null)
        keyStore.setKeyEntry(
            "agragps",
            privateKey, // 传入解析后的PrivateKey对象
            charArrayOf(), // 私钥无密码则传空数组,有密码则填对应密码
            arrayOf(publicCert)
        )

        // 4. 初始化KeyManagerFactory
        val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
        keyManagerFactory.init(keyStore, charArrayOf())

        // 5. 初始化SSLContext并返回SocketFactory
        val sslContext = SSLContext.getInstance("TLS")
        sslContext.init(keyManagerFactory.keyManagers, null, null)
        
        sslContext.socketFactory
    } catch (e: Exception) {
        e.printStackTrace()
        null
    }
}

// 辅助方法:PKCS#1转PKCS#8格式
private fun convertPkcs1ToPkcs8(pkcs1Bytes: ByteArray): ByteArray {
    val algorithmIdentifier = byteArrayOf(
        0x30, 0x0D,
        0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01,
        0x05, 0x00
    )
    val privateKeyOctet = byteArrayOf(0x04, pkcs1Bytes.size.toByte()) + pkcs1Bytes
    val privateKeyInfo = algorithmIdentifier + privateKeyOctet
    val version = byteArrayOf(0x02, 0x01, 0x00)
    return byteArrayOf(0x30, (version.size + privateKeyInfo.size).toByte()) + version + privateKeyInfo
}

补充说明

  • 如果你的私钥是PKCS#8格式(开头为-----BEGIN PRIVATE KEY-----),可以跳过格式转换步骤,直接用PKCS8EncodedKeySpec解析。
  • 若私钥设置了密码,keyManagerFactory.init的第二个参数要传入对应的密码char数组,不能传空数组。
  • 使用SSLContext.getInstance("TLS")替代getDefault(),避免依赖系统默认SSL版本,提升跨设备兼容性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 04:57:00