以编程方式将PEM证书导入Java密钥库

以编程方式将PEM证书导入Java密钥库,java,ssl,https,apache-httpclient-4.x,client-certificates,Java,Ssl,Https,Apache Httpclient 4.x,Client Certificates,我有一个由两个文件(.crt和.key)组成的客户机证书,我希望将它们导入java密钥库,然后在SSLContext中使用,以使用Apache的HTTPClient发送HTTP请求。然而,我似乎找不到一种以编程方式实现这一点的方法,我发现的大多数其他问题要么指向外部工具,要么不适合我的情况 我的证书使用典型的“BEGIN certificate”编码,后跟一个Base64编码字符串,密钥使用“BEGIN RSA PRIVATE key”,然后是另一个Base64编码字符串 到目前为止,我得到的是

我有一个由两个文件(.crt和.key)组成的客户机证书,我希望将它们导入java密钥库,然后在SSLContext中使用,以使用Apache的HTTPClient发送HTTP请求。然而,我似乎找不到一种以编程方式实现这一点的方法,我发现的大多数其他问题要么指向外部工具,要么不适合我的情况

我的证书使用典型的“BEGIN certificate”编码,后跟一个Base64编码字符串,密钥使用“BEGIN RSA PRIVATE key”,然后是另一个Base64编码字符串

到目前为止,我得到的是:

private static SSLContext createSSLContext(File certFile, File keyFile) throws IOException {
    try {
        PEMParser pemParser = new PEMParser(new FileReader(keyFile));
        JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(new BouncyCastleProvider());
        Object object = pemParser.readObject();
        KeyPair kp = converter.getKeyPair((PEMKeyPair) object);
        PrivateKey privateKey = kp.getPrivate();

        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
        FileInputStream stream = new FileInputStream(certFile);
        X509Certificate cert = (X509Certificate) certFactory.generateCertificate(stream);

        KeyStore store = KeyStore.getInstance("JKS");
        store.load(null);
        store.setCertificateEntry("certificate", cert);
        store.setKeyEntry("private-key", privateKey, "changeit".toCharArray(), new Certificate[] { cert });

        SSLContext sslContext = SSLContexts.custom()
                .loadKeyMaterial(store, "changeit".toCharArray())
                .build();
        return sslContext;
    } catch (IOException | NoSuchAlgorithmException | CertificateException | KeyStoreException | KeyManagementException | UnrecoverableKeyException e) {
        throw new IOException(e);
    }
}
堆栈跟踪:

java.io.IOException:java.security.spec.InvalidKeySpecException:java.security.InvalidKeyException:无效密钥格式 at me.failedshack.ssltest.ssltest.createSSLContext(ssltest.java:80) at me.failedshack.ssltest.ssltest.main(ssltest.java:31)

原因:java.security.spec.InvalidKeySpecException:java.security.InvalidKeyException:密钥格式无效 位于java.base/sun.security.rsa.RSAKeyFactory.EngineeGeneratePrivate(RSAKeyFactory.java:216) 位于java.base/java.security.KeyFactory.generatePrivate(KeyFactory.java:390) at me.failedshack.ssltest.ssltest.createSSLContext(ssltest.java:62) ... 还有一个

原因:java.security.InvalidKeyException:密钥格式无效 位于java.base/sun.security.pkcs.PKCS8Key.decode(PKCS8Key.java:330) 位于java.base/sun.security.pkcs.PKCS8Key.decode(PKCS8Key.java:355) 位于java.base/sun.security.rsa.RSAPrivateCrtKeyImpl。(RSAPrivateCrtKeyImpl.java:91) 位于java.base/sun.security.rsa.RSAPrivateCrtKeyImpl.newKey(RSAPrivateCrtKeyImpl.java:75) 位于java.base/sun.security.rsa.RSAKeyFactory.generatePrivate(RSAKeyFactory.java:315) 位于java.base/sun.security.rsa.RSAKeyFactory.EngineeGeneratePrivate(RSAKeyFactory.java:212) ... 3个以上


遗憾的是,在从文件生成私钥时,我一直收到InvalidKeyException。

类型为
RSA私钥的PEM文件是base64而不是二进制文件,更重要的是,它是PKCS1格式而不是PKCS8格式,因此无法作为
PKCS8EncodedKeySpec
处理

你的选择是:

  • 将PKCS1 PEM格式转换为PKCS8(未加密)PEM格式;阅读后,删除标题行和尾行,将base64解码为二进制,并将其放入
    PKCS8EncodedKeySpec
    ——但您说您不需要外部工具,另外,将privatekey plus证书(或链)转换为已经是Java密钥库的PKCS12(DER)也同样容易,并且可以避免此问题

  • 将PKCS1 PEM格式转换为PKCS8(未加密)DER格式,您可以将其读取为二进制并放入
    PKCS8EncodedKeySpec
    ——同上

  • 如果PKCS1 PEM未加密,则如上所述将其读取并解码为PKCS1 DER,然后手动构造PKCS8(未加密)编码,并使用该编码

  • 如果PKCS1 PEM是加密的,您可以检测到它,因为它的主体除了base64之外还包含两个822样式的头行,那么您必须复制OpenSSL的“遗留”密钥文件解密,并构造PKCS8(未加密)编码

  • 如果您可以专门使用BouncyCastle
    bcpkix
    ,它可以直接读取和解析OpenSSL用于私钥的所有PEM变体,包括解密加密的PEM变体;但是,如果您还没有使用它,那么需要安装和/或部署一个额外的jar

查看一个或多个复制:
(Q使用BouncyCastle构建PKCS8)
(我的答案是“手工”构建PKCS8)
(使用BouncyCastle读取)
(使用BouncyCastle读取)
(使用BC解密)
(手动解密)
可能(背景)

和(背景)

请进行堆栈跟踪。注意:您不应该调用
setCertificateEntry()
,只调用
setKeyEntry()
。您还需要使用
KeyManager
而不是
信任管理器来初始化
SSLContext
。否则您的私钥将永远找不到。而且您也不能将私钥强制转换为公钥。这没有多大意义。@EJP对PublicKey的演员阵容表示抱歉,这是我一直在玩弄的东西,不小心复制到了问题上。关于钥匙管理器的事你是对的。我已经修改了这个问题。这个异常只能表示它所说的:密钥文件有问题。您能用OpenSSL阅读它吗?非常感谢您的澄清,我选择使用BouncyCastle来阅读私钥,并且似乎正在完成它的工作。但是,我无法获得要通过的请求,我得到以下异常:“PKIX路径生成失败:sun.security.provider.certpath.SunCertPathBuilderException:无法找到请求目标的有效证书路径”。我听说这意味着证书不可信。我使用的代码与问题中的代码相同,只是我更改了几行代码,用BouncyCastle加载私钥;您加载到KeyManager中进行身份验证的privatekey+证书与TrustManager验证任何其他方完全无关(反之亦然)。但是您发布的代码似乎使用了一个TMF而没有初始化它,这根本不起作用,并且抛出了一个与您所说的完全不同的异常。请检查您的代码,并(如果您仍然有问题)将您用于尝试连接的信任库添加到您的Q中。我以为我已经更新了问题中的代码,但显然没有。我正在尝试完成与这里相同的任务:现在我已经尝试使用OpenSSL手动将证书和密钥导入PKCS12密钥存储,并按照这里的说明加载它:但是在连接时,我会不断收到相同的消息,我觉得我应该在这一点上提出一个新问题。(1)好的,您的代码现在有效地使用(或者更确切地说让JSSE使用)默认信任存储(2)两个ho