为什么在 Java 中创建 SSL 套接字时会收到错误“无法存储非私有密钥”?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6656263/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why do I get the error "Cannot store non-PrivateKeys" when creating an SSL Socket in Java?
提问by Lawrence Dol
I am working on an older IBM iSeries (IBM-i, i5OS, AS/400, etc), with a Java 5 JVM (Classic, not ITJ J9) on O/S version V5R3M0.
我正在使用旧的 IBM iSeries(IBM-i、i5OS、AS/400 等),在 O/S 版本 V5R3M0 上使用 Java 5 JVM(经典,而不是 ITJ J9)。
Here is the scenario in a nutshell:
这是一个简而言之的场景:
- I created a key-store of type JKS using Portecle 1.7(Note: I did try converting my key-store to JCEKS but that was rejected as an unsupported format, so it appears that JKS is the only option with the iSeries machine (at least the version I am on).
- I then created a key-pair and CSR and sent the CSR to Thawte to be signed.
- I imported the signed certificate from Thawte successfully using the PKCS#7 format to import the entire certificate chain, which included my certificate, the Thawte intermediary and the Thawte server root.
- 我使用Portecle 1.7创建了一个 JKS 类型的密钥库(注意:我确实尝试将我的密钥库转换为 JCEKS,但由于不支持的格式而被拒绝,因此看来 JKS 是 iSeries 机器的唯一选择(至少我使用的版本)。
- 然后我创建了一个密钥对和 CSR 并将 CSR 发送给 Thawte 进行签名。
- 我使用 PKCS#7 格式成功地从 Thawte 导入了签名证书,以导入整个证书链,其中包括我的证书、Thawte 中介和 Thawte 服务器根。
This all worked as expected.
这一切都按预期进行。
However, when I ran up the JVM, configured properly to point to the store and supply it's password (which I have done in the past with self-signed certificates created in Portecle for testing), and try to start my web server on 443, I get the following security exception:
然而,当我运行 JVM,正确配置为指向商店并提供它的密码(我过去使用 Portecle 创建的自签名证书进行测试),并尝试在 443 上启动我的 Web 服务器,我收到以下安全异常:
java.security.KeyStoreException: Cannot store non-PrivateKeys
Can anyone tell me where I went wrong, or what I should check next?
谁能告诉我哪里出错了,或者我接下来应该检查什么?
采纳答案by Bruno
Instead of using an ephemeral keystore, you could handle everything within a single SSLContext
.
您无需使用临时密钥库,而是可以在单个SSLContext
.
You would need to initialise your SSLContext
using an custom X509KeyManager
instead of using the one given by the default KeyManagerFactory
. In this X509KeyManager
,chooseServerAlias(String keyType, Principal[] issuers, Socket socket)
should return a different alias depending on the local address obtained from the socket.
您需要SSLContext
使用自定义来初始化您,X509KeyManager
而不是使用默认给定的KeyManagerFactory
。在这种情况下X509KeyManager
,chooseServerAlias(String keyType, Principal[] issuers, Socket socket)
应该根据从套接字获得的本地地址返回不同的别名。
This way, you wouldn't have to worry about copying the private key from one keystore to another, and this would even work for keystore types from which you can't extract (and thus copy) but only use the private key, e.g. PKCS#11.
这样,您就不必担心将私钥从一个密钥库复制到另一个密钥库,这甚至适用于您无法从中提取(从而复制)而只能使用私钥的密钥库类型,例如 PKCS #11.
回答by Jared Pehrson
The "Cannot store non-PrivateKeys" error message usually indicates you are trying to use secret symmetric keys with a JKS keystore type. The JKS keystore type only supports asymmetric (public/private) keys. You would have to create a new keystore of type JCEKS to support secret keys.
“无法存储非私有密钥”错误消息通常表示您正在尝试使用 JKS 密钥库类型的秘密对称密钥。JKS 密钥库类型仅支持非对称(公共/私有)密钥。您必须创建一个新的 JCEKS 类型的密钥库来支持密钥。
回答by Lawrence Dol
As it turns out, this was a subtle problem, and it's worth giving the answer here in case someone else has something similar.
事实证明,这是一个微妙的问题,值得在这里给出答案,以防其他人有类似的问题。
The TLDR answer is that I did not check that my key and certificate were not null and as a result attempted to add a null key and certificate to a key-store. The longer answer follows.
TLDR 的答案是我没有检查我的密钥和证书是否为空,因此尝试将空密钥和证书添加到密钥库。更长的答案如下。
The way we have our web server set up to use SSL, specifically to support our user's typical configuration where the IP address is used to configure the web site listen address rather than a DNS name, is that it locates the certificate in the master key-store using the alias, and creates an ephemeral key-store containing just the certificate for that web site, using that key-store to configure an SSL context and an SSL socket factory, like so:
我们将 Web 服务器设置为使用 SSL 的方式,特别是为了支持我们用户的典型配置,其中 IP 地址用于配置网站侦听地址而不是 DNS 名称,是它将证书定位在主密钥中 -使用别名存储,并创建一个仅包含该网站证书的临时密钥库,使用该密钥库来配置 SSL 上下文和 SSL 套接字工厂,如下所示:
// CREATE EPHEMERAL KEYSTORE FOR THIS SOCKET USING THE DESIRED CERTIFICATE
try {
final char[] BLANK_PWD=new char[0];
SSLContext ctx=SSLContext.getInstance("TLS");
KeyManagerFactory kmf=KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
Key ctfkey=mstkst.getKey(svrctfals,BLANK_PWD);
Certificate[] ctfchn=mstkst.getCertificateChain(svrctfals);
KeyStore sktkst;
sktkst=KeyStore.getInstance("jks");
sktkst.load(null,BLANK_PWD);
sktkst.setKeyEntry(svrctfals,ctfkey,BLANK_PWD,ctfchn);
kmf.init(sktkst,BLANK_PWD);
ctx.init(kmf.getKeyManagers(),null,null);
ssf=ctx.getServerSocketFactory();
}
catch(java.security.GeneralSecurityException thr) {
throw new IOException("Cannot create server socket factory using ephemeral keystore ("+thr+")",thr);
}
Notice that it uses a blank password for extracting the private key and certificates from the master key-store. That was my problem - I had, out of habit from using keytool, created the private key-pair with a password (the same password as the key-store).
请注意,它使用空白密码从主密钥库中提取私钥和证书。那是我的问题 - 我出于使用 keytool 的习惯,使用密码(与密钥库相同的密码)创建了私钥对。
Because I had a password on the certificate, the key and certificate were not extracted, and null was passed to sktkst.setKeyEntry(svrctfals,ctfkey,BLANK_PWD,ctfchn);
However, setKeyEntry
checks the passed Key
using instanceof
and concludes (correctly) that null
is not an instanceof PrivateKey
, resulting in the misleading error I was seeing.
因为我在证书上有密码,所以没有提取密钥和证书,并将 null 传递给sktkst.setKeyEntry(svrctfals,ctfkey,BLANK_PWD,ctfchn);
但是,setKeyEntry
检查通过的Key
usinginstanceof
并(正确地)得出结论,这null
不是instanceof PrivateKey
,导致我看到的误导性错误。
The corrected code checks that a key and certificate are found and sends appropriate errors:
更正后的代码检查是否找到了密钥和证书并发送了适当的错误:
// CREATE EPHEMERAL KEYSTORE FOR THIS SOCKET USING THE DESIRED CERTIFICATE
try {
final char[] BLANK_PWD=new char[0];
SSLContext ctx=SSLContext.getInstance("TLS");
KeyManagerFactory kmf=KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
Key ctfkey=mstkst.getKey(svrctfals,BLANK_PWD);
Certificate[] ctfchn=mstkst.getCertificateChain(svrctfals);
KeyStore sktkst;
if(ctfkey==null) {
throw new IOException("Cannot create server socket factory: No key found for alias '"+svrctfals+"'");
}
if(ctfchn==null || ctfchn.length==0) {
throw new IOException("Cannot create server socket factory: No certificate found for alias '"+svrctfals+"'");
}
sktkst=KeyStore.getInstance("jks");
sktkst.load(null,BLANK_PWD);
sktkst.setKeyEntry(svrctfals,ctfkey,BLANK_PWD,ctfchn);
kmf.init(sktkst,BLANK_PWD);
ctx.init(kmf.getKeyManagers(),null,null);
ssf=ctx.getServerSocketFactory();
}
catch(java.security.GeneralSecurityException thr) {
throw new IOException("Cannot create server socket factory using ephemeral keystore ("+thr+")",thr);
}