如何在新的(未弃用的)SSLSocketFactory 中加载私钥密码?

How to load private key password in new (not deprecated) SSLSocketFactory?

我当前的 TCP 客户端正在使用 org.apache.http.conn.ssl.SSLSocketFactory
构建套接字 我要迁移到 import javax.net.ssl.SSLSocketFactory; 但是,我不确定如何加载存储在我已有的 .p12 文件中的客户端证书的私钥,作为新 SSLSocketFactory 的上下文?

这是我现有的代码,使用已弃用的 SSLSocketFactory:

import org.apache.http.ssl.SSLContexts;
import org.apache.http.conn.ssl.SSLSocketFactory;

String ksPassphrase = "myKSpass";
String pkPassphrase = "myPKpass";
// Get a keystore instance of type PKCS12:
KeyStore keystore = KeyStore.getInstance("PKCS12");
// Load keystore file and password:
keystore.load(new FileInputStream("myFile.p12"), ksPassphrase.toCharArray());
// Create HTTPS connection socket factory context:
SSLContext context = SSLContexts.custom()
    .loadKeyMaterial(keystore, pkPassphrase.toCharArray())
    .build();

SSLSocketFactory sslScktFactory = new SSLSocketFactory(context);

这是我可以从文档中管理的最佳代码:

import javax.net.ssl.*;

// not sure if this is the pass to KS or private key or, somehow both?
String passphrase = "myPassphrase" 

SSLContext context = SSLContext.getInstance("TLS");
KeyManagerFactory keyMngFactory = KeyManagerFactory.getInstance("SunX509");
KeyStore keyStore = KeyStore.getInstance("JKS");

keyStore.load(new FileInputStream("myFile.jks"), passphrase.toCharArray());
keyMngFactory.init(keyStore, passphrase);
context.init(keyMngFactory.getKeyManagers(), null, null);

SSLSocketFactory sslScktFactory = context.getSocketFactory();

您传递给KeyStore.load(InputStream,char[])的密码是商店密码;您传递给 KeyManagerFactory.init(KeyStore,char[]) 的密码是私钥密码('the' 单数;如果有多个密钥,则所有密钥必须相同)并注意它是 char[] 而不是 String——你的编译器(或IDE)应该已经告诉你了。如果您想继续使用现有(或任何 other/new)PKCS12 文件,则需要更改新代码以使用 KeyStore.getInstance("PKCS12")

请注意 API 允许您使用与 storepass 不同的密钥密码,但您可能希望与之交换或共享 PKCS12 文件的其他软件(如 Windows、MacOSX、OpenSSL、I think NSS) 支持这个,并且为了避免此类问题 Java 的命令行 keytool 不会将 keypass 设置为不同于 storepass 对于 PKCS12

What to use for the new SSLSocketFactory?

SSLContext.getSocketFactory() - 与您发布的完全一样。事实上,这实际上是您之前弃用的 org.apache.http.conn.ssl.SSLSocketFactory.SSLSocketFactory(SSLContext) 在内部所做的,只是它还默认了 HostNameVerifier。

Is it the case that JKS keystores don't have internal passwords for the private keys stored inside (I only used PKCS until now, not familiar with the JKS format) and that's why I can't see where the private key password is being referred to?

否; JKS 商店确实有单独的 storepass 和 keypass,如果按照我上面的描述进行更正,它们将在您发布的代码中使用——尽管私钥加密 JKS 使用 keypass for 比通常在 PKCS12 中使用的那个。由于 JKS 文件无论如何都不能与其他软件互操作,keytool 确实支持将 keypass 与 storepass 不同。事实上,keytool(通常是 JCA)支持在一个 JKS 存储中为不同的私钥设置不同的 keypass 值,但是 KeyManagerFactory(对于 SSL/TLS,即 JSSE) 支持,Apache 也不支持(除了 adding/wrapping 关键 choice 策略只是在下面使用 JSSE)。

(OP) 编辑:这里有一些经过测试的工作代码来自您的建议:

// Consider having both the same, for compatibility with Windows/MacOSX/OpenSSL/etc:
String ksPassphrase = "myKSpass";
String pkPassphrase = "myPKpass";

SSLContext context = SSLContext.getInstance("TLS");
KeyManagerFactory keyMngFactory = KeyManagerFactory.getInstance("SunX509");
KeyStore keyStore = KeyStore.getInstance("PKCS12");

keyStore.load(new FileInputStream("myFile.p12"), 
ksPassphrase.toCharArray());
keyMngFactory.init(keyStore, pkPassphrase.toCharArray());
context.init(keyMngFactory.getKeyManagers(), null, null);

SSLSocketFactory sslScktFactory = context.getSocketFactory();