将PEM导入Java密钥库

194

我试图连接到一个需要身份验证的SSL服务器。为了在Apache MINA上使用SSL,我需要一个合适的JKS文件。然而,我只收到了一个.PEM文件。

我该如何从PEM文件创建JKS文件?


1
也许这个链接会有帮助:http://www.agentbob.info/agentbob/79-AB.html - Laurent K
13个回答

2
虽然这个问题已经很老了,而且已经有很多答案了,但我认为提供一种替代方案是值得的。使用本地Java类使得仅使用pem文件变得非常冗长,几乎迫使您想要将pem文件转换为p12或jks文件,因为使用p12或jks文件更容易。我想给那些想要替代已提供答案的人一个选择。 GitHub - SSLContext Kickstart
var keyManager = PemUtils.loadIdentityMaterial("certificate-chain.pem", "private-key.pem");
var trustManager = PemUtils.loadTrustMaterial("some-trusted-certificate.pem");

var sslFactory = SSLFactory.builder()
          .withIdentityMaterial(keyManager)
          .withTrustMaterial(trustManager)
          .build();

var sslContext = sslFactory.getSslContext();

在这里,我需要提供一些免责声明,我是该库的维护者。


1
如果您想在Android上完成此操作,可以对PEMImporter类进行一些微小的调整,这些调整来自于这个很棒的答案。总结如下:
  • 首先我使用了Android Studio将其翻译成Kotlin(这并非必要,我只是更喜欢它)。原始类包含所有静态方法,因此结果是一个命名对象。

  • javax.xml.bind.DatatypeConverter在11版本中已从java核心中删除。虽然您仍然可以导入它(在gradle中:implementation("javax.xml.bind:jaxb-api:2.4.0-b180830.0359"),但这在Android上不起作用,因此最好使用 java.util.Base64 来执行它执行的任务(即将base64转换为字节)。输出相同(尽管您需要修剪原始PEM数据中的行尾)。

  • SunX509JKS替换为PKIX。仅在第一种情况下是必要的,在第二种情况下可能是无关紧要的;如果您正在使用已初始化的PrivateKey等对象填充KeyStore,则我认为它没有任何意义。实际上,我在createKeyStore中使用了getDefaultAlgorithm()来代替"JKS",尽管该默认值目前为"jks",但使用PKIX作为算法创建的KeyManagerFactory中的密钥库工作正常。

我还应该注意,我没有使用createSSLFactory方法,而是使用createKeyStore()的输出来初始化KeyManagerFactory并提取用于初始化SSLContextKeyManagers

val context = SSLContext.getInstance(contextProtocol)
val password = String(...)
val ks : KeyStore = try {
            PEMImporter.createKeyStore(
                File(keyPath),
                File(certPath),
                password
            )
        } catch (ex : Throwable) { ... }

val kmf = KeyManagerFactory.getInstance("PKIX")
        try { kmf.init(ks, password.toCharArray()) }

这里密码的内容可能并不重要,因为PEMImporter使用的是已经解密的密钥数据,除非您想将PrivateKey写回文件(我假设getEncoded()是朝这个方向迈出的一步,但我从未需要过这样做)。它只需要在上述两个用途中匹配即可。
我还添加了一个捕获RSA私钥的处理,因为它们与第一行没有“RSA”的PEM密钥不同;这是我之前不知道的微妙之处。前者是PKCS#1,后者是PKCS#8;您应该能够使用您通常用于处理这些密钥的任何工具(例如,在使用certtool创建密钥时,请使用--pkcs8)。请注意,这并不意味着PKCS#8密钥不能潜在地基于RSA,而是关于用于存储和提取密钥数据的协议。
下面是我的Kotlin版Android PEMImporter
import java.io.*
import java.security.*
import java.security.cert.CertificateException
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.security.interfaces.RSAPrivateKey
import java.security.spec.InvalidKeySpecException
import java.security.spec.PKCS8EncodedKeySpec
import java.util.*
import javax.net.ssl.KeyManagerFactory
import javax.net.ssl.SSLContext
import javax.net.ssl.SSLServerSocketFactory

object PEMImporter {

    @Throws(Exception::class)
    fun createSSLFactory(
        privateKeyPem: File,
        certificatePem: File?,
        password: String
    ): SSLServerSocketFactory {
        val context = SSLContext.getInstance("TLS")
        val keystore = createKeyStore(privateKeyPem, certificatePem, password)
        val kmf = KeyManagerFactory.getInstance("PKIX")
        kmf.init(keystore, password.toCharArray())
        val km = kmf.keyManagers
        context.init(km, null, null)
        return context.serverSocketFactory
    }

    /**
     * Create a KeyStore from standard PEM files
     *
     * @param privateKeyPem the private key PEM file
     * @param certificatePem the certificate(s) PEM file
     * @param password the password to set to protect the private key
     */
    @Throws(
        Exception::class,
        KeyStoreException::class,
        IOException::class,
        NoSuchAlgorithmException::class,
        CertificateException::class
    )
    fun createKeyStore(privateKeyPem: File, certificatePem: File?, password: String): KeyStore {
        val cert = createCertificates(certificatePem)
        val keystore = KeyStore.getInstance(KeyStore.getDefaultType())
        keystore.load(null)
        // Import private key
        val key = createPrivateKey(privateKeyPem)
        keystore.setKeyEntry(privateKeyPem.name, key, password.toCharArray(), cert)
        return keystore
    }

    @Throws(Exception::class)
    private fun createPrivateKey(privateKeyPem: File): PrivateKey {
        val r = BufferedReader(FileReader(privateKeyPem))
        var s = r.readLine()
        if (s.contains("BEGIN RSA PRIVATE KEY")) {
            r.close()
            throw IllegalArgumentException(privateKeyPem.name +
                " is a PKCS #1 key, not a PKCS #8.")
        }
        if (s == null || (!s.contains("BEGIN PRIVATE KEY"))) {
            r.close()
            throw IllegalArgumentException("Bad private key header (${privateKeyPem.name}): $s")
        }
        val b = StringBuilder()
        s = ""
        while (s != null) {
            if (s.contains("END PRIVATE KEY")) {
                break
            }
            b.append(s.trimEnd())
            s = r.readLine()
        }
        r.close()
        val hexString = b.toString()
        // Base64 is in java.util.
        val bytes = Base64.getDecoder().decode(hexString)
        return generatePrivateKeyFromDER(bytes)
    }

    @Throws(Exception::class)
    private fun createCertificates(certificatePem: File?): Array<X509Certificate> {
        val result = mutableListOf<X509Certificate>()
        val r = BufferedReader(FileReader(certificatePem))
        var s = r.readLine()
        if (s == null || !s.contains("BEGIN CERTIFICATE")) {
            r.close()
            throw IllegalArgumentException("No CERTIFICATE found")
        }
        var b = StringBuilder()
        while (s != null) {
            if (s.contains("END CERTIFICATE")) {
                val hexString = b.toString()
                val bytes = Base64.getDecoder().decode(hexString.trimEnd())
                val cert = generateCertificateFromDER(bytes)
                result.add(cert)
                b = StringBuilder()
            } else {
                if (!s.startsWith("----")) {
                    b.append(s)
                }
            }
            s = r.readLine()
        }
        r.close()
        return result.toTypedArray()
    }

    @Throws(InvalidKeySpecException::class, NoSuchAlgorithmException::class)
    private fun generatePrivateKeyFromDER(keyBytes: ByteArray): RSAPrivateKey {
        val spec = PKCS8EncodedKeySpec(keyBytes)
        val factory = KeyFactory.getInstance("RSA")
        return factory.generatePrivate(spec) as RSAPrivateKey
    }

    @Throws(CertificateException::class)
    private fun generateCertificateFromDER(certBytes: ByteArray): X509Certificate {
        val factory = CertificateFactory.getInstance("X.509")
        return factory.generateCertificate(ByteArrayInputStream(certBytes)) as X509Certificate
    }
}

0
如果您有一个包含多个证书的pem文件,您可以使用脚本来提取它们并逐个添加,例如:
    index=0
    while read -r line; do
        if [ "$line" = "-----BEGIN CERTIFICATE-----" ]; then
            echo "$line" > temp_cert.pem
        elif [ "$line" = "-----END CERTIFICATE-----" ]; then
            echo "$line" >> temp_cert.pem
            let "index++"
            keytool -importcert -alias "your-alias_$index" -keystore cacerts -file temp_cert.pem
            rm temp_cert.pem
        else
            echo "$line" >> temp_cert.pem
        fi
    done < multi_certs.pem

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接