【问题标题】:Importing PEM certificate into Java KeyStore programmatically以编程方式将 PEM 证书导入 Java KeyStore
【发布时间】:2018-12-23 11:11:34
【问题描述】:

我有一个由两个文件(.crt 和 .key)组成的客户端证书,我希望将其导入到 java KeyStore 中,然后在 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: 无效的密钥格式 在 me.failedshack.ssltest.SSLTest.createSSLContext(SSLTest.java:80) 在 me.failedshack.ssltest.SSLTest.main(SSLTest.java:31)

原因:java.security.spec.InvalidKeySpecException:java.security.InvalidKeyException:无效的密钥格式 在 java.base/sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:216) 在 java.base/java.security.KeyFactory.generatePrivate(KeyFactory.java:390) 在 me.failedshack.ssltest.SSLTest.createSSLContext(SSLTest.java:62) ... 1 更多

原因: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.engineGeneratePrivate(RSAKeyFactory.java:212) ... 3 更多

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

【问题讨论】:

  • 请跟踪堆栈。注意你不应该打电话给setCertificateEntry(),只能打电话给setKeyEntry()
  • 您还需要用KeyManager 来初始化SSLContext,而不是TrustManager。否则永远找不到你的私钥。
  • 而且您无法将私钥转换为公钥。这没什么意义。
  • @EJP 对不起,PublicKey 演员,这是我玩弄的东西,不小心复制到了问题中。您对 KeyManager 的看法是正确的。我已经修改了问题。
  • 这个异常只能说明它所说的:密钥文件有问题。你能用 OpenSSL 阅读它吗?

标签: java ssl https apache-httpclient-4.x client-certificates


【解决方案1】:

RSA PRIVATE KEY 类型的 PEM 文件是 base64 而非二进制,更重要的是,它是 PKCS1 格式而不是 PKCS8,因此不能作为 PKCS8EncodedKeySpec 处理。

您的选择是:

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

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

  • 如果 PKCS1 PEM 未加密,则按上述方式读取并解码为 PKCS1 DER,然后手动构建 PKCS8(未加密)编码,并使用该编码

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

查看以下一个或多个骗子:
Load certificate to KeyStore (JAVA)(Q 使用 BouncyCastle 构造 PKCS8)
Java: Convert DKIM private key from RSA to DER for JavaMail(我的答案是“手动”构造 PKCS8)
How to Load RSA Private Key From File(使用 BouncyCastle 读取)
Read RSA private key of format PKCS1 in JAVA(使用 BouncyCastle 读取)
Get a PrivateKey from a RSA .pem file(使用 BC 解密)
Decrypting an OpenSSL PEM Encoded RSA private key with Java?(手动解密)
也许PKCS#1 and PKCS#8 format for RSA private key(背景)
Differences between "BEGIN RSA PRIVATE KEY" and "BEGIN PRIVATE KEY"(背景)

【讨论】:

  • 非常感谢您的澄清,我选择使用 BouncyCastle 来读取私钥,并且似乎正在做它的工作。但是,我无法通过请求,我收到以下异常:“PKIX 路径构建失败:sun.security.provider.certpath.SunCertPathBuilderException:无法找到请求目标的有效证书路径”。我读过这意味着证书不受信任。我使用的代码与问题中的代码相同,除了我更改为使用 BouncyCastle 加载私钥的几行代码。
  • 这是一个不同的问题;您在 KeyManager 中加载以验证自己的 privatekey+cert 与 TrustManager 验证任何其他方(反之亦然)完全分开,并且完全没有关系。但是您发布的代码似乎使用了 TMF 而没有对其进行初始化,这根本行不通,并且会引发与您说的完全不同的异常。请检查您的代码,并(如果您仍然有问题)将您用于尝试连接的信任库添加到您的 Q 中。
  • 我以为我已经更新了问题中的代码,但显然没有。我正在尝试完成与此处相同的操作:github.com/Plailect/PlaiCDN/blob/master/PlaiCDN.py#L123
  • 现在我尝试使用 OpenSSL 手动将证书和密钥导入 PKCS12 密钥库并按照此处的说明加载它:stackoverflow.com/questions/21223084/… 但是连接时不断收到相同的消息,我觉得我应该此时打开一个新问题。
  • (1) 好的,您的代码现在有效地使用(或者更确切地说让 JSSE 使用)默认信任库 (2) 该 python 中引用的两个主机提供由“Nintendo CA - G3”颁发的叶证书.该 CA 肯定不在 Java 默认 CA 列表中,尽管我不知道 python 使用哪个商店(您的?)如果该 CA 在任何标准分布式 CA 列表中未经修改,我会感到非常惊讶。您确定您没有(按照一些说明或设置程序)将该证书添加到系统上的存储区吗?
猜你喜欢
  • 1970-01-01
  • 2012-09-12
  • 2011-05-28
  • 2011-03-07
  • 1970-01-01
  • 2011-09-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多