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