Java 7并且无法生成DH密钥对

我读了一篇关于当服务器发出长度超过1024位的密钥时出现错误’无法生成DH密钥对’的post。 下载JCE无限量jar子应该可以解决这个问题。 在测试环境中,我遇到了以下内容,对于同一个Web服务器,如果我使用Java 6,在执行https查询时没有出现任何错误,但是如果我使用Java 7则会得到“无法生成DH密钥对”。

我尝试更换JCE无效的jar文件,但仍然得到相同的错误。 自2007年以来报告了该错误,但为什么它运行Java 6而不是Java 7? 要下载的文件不是正确的吗? 我从以前的postJava获得了链接:为什么SSL握手会给出“无法生成DH密钥对”的exception? 。

此时我不知道该怎么做。 如果我尝试加载BouncyCastle提供程序,我会得到一个ArrayOutOfIndexexception。 我的服务器只允许DH算法,所以我不能使用上面post中建议的其他算法。

一些补充或澄清:

(Suncle)Java 7自7u09以来默认使用密码组的更合理的一致顺序,与7u04中看似随机的顺序不同。 (我在04和09之间没有测试。)此命令在DHE之前放置ECDHE和普通RSA(也称为akRSA),因此只有在服务器支持ECDHE或RSA并且同意客户端偏好时才能避免此问题。 (或ECDH固定,但实际上没有人使用它。)如果服务器坚持DHE(无论出于何种原因)并使用DH> 1024位,你仍然有问题。

如果提问者(或其他任何人)连接到真正需要整数DH(而不是ECDH或RSA)的服务器,那么在8之前使用Java的唯一方法是让服务器使用DH 1024位。 哪种AFAWK在技术上可以安全使用多年,但由于NIST等重要机构禁止使用AFAWK(请参阅csrc.nist.gov上的Special Pub 800-57)。 (即使RSA 1024实际上还没有被破坏,但它可能很快就会被破坏,因此被禁止。)

“无限强度政策”与这个问题无关,或者至少不是直接关系,#6851461的好答案并没有说明。 它不会改变SunJCE中对DH参数的限制,这被(错误地)视为标准问题而非强度问题。 (具体来说,它采用了以前对DSA正确的限制,并将它们应用于DH。)它确实启用了AES-256和SHA-2(仅适用于TLSv1.2)套件,并且给出了一个足够奇怪的首选项列表,将选择结果从DHE(失败)更改为非DHE(工作)。

您不需要完全返回到Java 6列表,只需要通过DHE优先处理其他密钥交换,或者对于一个顽固的服务器完全丢弃DHE。 除非传统服务器绝对需要,否则你绝对不应该回到启用任何EXPORT或单DES套件; 他们几年来一直没有安全,并且在默认情况下仍然保持启用状态的时间比他们应该的要长得多。

我偶然发现了与SSLScokets相同的问题,我想我已经确定了使用Java 7进行回归的原因。原因是客户端和服务器之间协商的密码。

默认情况下,Java 6为TLS连接启用这些密码(按优先级顺序):

SSL_RSA_WITH_RC4_128_MD5 SSL_RSA_WITH_RC4_128_SHA TLS_RSA_WITH_AES_128_CBC_SHA TLS_DHE_RSA_WITH_AES_128_CBC_SHA TLS_DHE_DSS_WITH_AES_128_CBC_SHA SSL_RSA_WITH_3DES_EDE_CBC_SHA SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA SSL_RSA_WITH_DES_CBC_SHA SSL_DHE_RSA_WITH_DES_CBC_SHA SSL_DHE_DSS_WITH_DES_CBC_SHA SSL_RSA_EXPORT_WITH_RC4_40_MD5 SSL_RSA_EXPORT_WITH_DES40_CBC_SHA SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA TLS_EMPTY_RENEGOTIATION_INFO_SCSV 

Java 7支持以下密码:

 TLS_DHE_RSA_WITH_AES_128_CBC_SHA TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA SSL_RSA_WITH_RC4_128_SHA TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA TLS_ECDHE_RSA_WITH_RC4_128_SHA TLS_ECDH_ECDSA_WITH_RC4_128_SHA TLS_ECDHE_ECDSA_WITH_RC4_128_SHA TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA TLS_ECDH_RSA_WITH_RC4_128_SHA TLS_EMPTY_RENEGOTIATION_INFO_SCSV TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA TLS_ECDH_RSA_WITH_AES_128_CBC_SHA TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA TLS_RSA_WITH_AES_128_CBC_SHA TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA SSL_RSA_WITH_RC4_128_MD5 TLS_DHE_DSS_WITH_AES_128_CBC_SHA SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA SSL_RSA_WITH_3DES_EDE_CBC_SHA 

使用Diffie-Hellman的密码在Java 7上具有更高的优先级,但它们似乎不支持长度超过1024位的密钥,除非安装了强加密包。

我使用的解决方法是在SSLSocket上指定Java 6启用的密码:

 SSLSocketFactory socketFactory = SSLContext.getInstance("TLS").getSocketFactory(); SSLSocket socket = (SSLSocket) socketFactory.createSocket(InetAddress.getByName(hostname), port); socket.setEnabledCipherSuites(new String[] { "SSL_RSA_WITH_RC4_128_MD5", "SSL_RSA_WITH_RC4_128_SHA", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_RSA_WITH_AES_128_CBC_SHA", "TLS_DHE_DSS_WITH_AES_128_CBC_SHA", "SSL_RSA_WITH_3DES_EDE_CBC_SHA", "SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA", "SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA", "SSL_RSA_WITH_DES_CBC_SHA", "SSL_DHE_RSA_WITH_DES_CBC_SHA", "SSL_DHE_DSS_WITH_DES_CBC_SHA", "SSL_RSA_EXPORT_WITH_RC4_40_MD5", "SSL_RSA_EXPORT_WITH_DES40_CBC_SHA", "SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA", "SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA", "TLS_EMPTY_RENEGOTIATION_INFO_SCSV"}); socket.startHandshake(); 

鉴于您使用的是最新的java版本并仍然出现错误,您可以更改java.security中的设置(例如,在文件夹C:\ Program Files \ Java \ jre1.8.0_xx \ lib \ security中)

 # Example: # jdk.tls.disabledAlgorithms=MD5, SSLv3, DSA, RSA keySize < 2048 jdk.tls.disabledAlgorithms=SSLv3, RC4 

在jdk.tls.disabledAlgorithms中将DH添加为禁用算法

  jdk.tls.disabledAlgorithms=SSLv3, RC4, DH 

重启tomcat或重新运行程序。

我们也遇到了Java7和Java8的这个问题。 我们还使用了类似于Emanual Borg的建议的解决方法。 但我们的目标是避免硬编码固定的CipherSuite列表。 所以我们尝试删除导致问题的条目(通过反复试验……)。

 String[] enabledCipherSuites = socket.getEnabledCipherSuites(); // avoid hardcoding a new list, we just remove the entries // which cause the exception List asList = new ArrayList(Arrays.asList(enabledCipherSuites)); // we identified the following entries causeing the problems // "Could not generate DH keypair" // and "Caused by: java.security.InvalidAlgorithmParameterException: Prime size must be multiple of 64, and can only range from 512 to 1024 (inclusive)" asList.remove("TLS_DHE_RSA_WITH_AES_128_CBC_SHA"); asList.remove("SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA"); asList.remove("TLS_DHE_RSA_WITH_AES_256_CBC_SHA"); String[] array = asList.toArray(new String[0]); socket.setEnabledCipherSuites(array); 

问题:有人看到这种方法有问题吗?

顺便说一句:如果您使用的是Apache HTTPClient,那么https://issues.apache.org/jira/browse/HTTPCLIENT-1111很有趣,它展示了如何通过该方法设置CipherSuites(从HTTPClient v4.2开始)

 SSLConnectionSocketFactory() {...}.prepareSocket(SSLSocket) 

更新2015/10/31:为了更好地理解使用它的上下文,这里作为完整的伪代码示例,您将看到如何挂钩以覆盖prepareSocket()方法:

 HttpClientBuilder builder = HttpClients.custom(); SSLContextBuilder sslContextBuilder = SSLContexts.custom(); SSLContext sslContext = sslContextBuilder.build(); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, hostNameVerfier) { protected void prepareSocket(SSLSocket socket) throws IOException { // Workaround to use different order of CipherSuites used by Java6 in order // to avoid the the problem of java7 "Could not generate DH keypair" String[] enabledCipherSuites = socket.getEnabledCipherSuites(); // but to avoid hardcoding a new list, we just remove the entries // which cause the exception (via TrialAndError) List asList = new ArrayList(Arrays.asList(enabledCipherSuites)); // we identified the following entries causeing the problems // "Could not generate DH keypair" // and "Caused by: java.security.InvalidAlgorithmParameterException: Prime size must be multiple of 64, and can only range from 512 to 1024 (inclusive)" asList.remove("TLS_DHE_RSA_WITH_AES_128_CBC_SHA"); asList.remove("SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA"); asList.remove("TLS_DHE_RSA_WITH_AES_256_CBC_SHA"); String[] array = asList.toArray(new String[0]); socket.setEnabledCipherSuites(array); }; }; Registry socketFactoryRegistry = RegistryBuilder. create().register("https", sslsf).build(); PoolingHttpClientConnectionManager conman = new PoolingHttpClientConnectionManager(socketFactoryRegistry); builder.setConnectionManager(conman); CloseableHttpClient httpClient = builder.build(); 

注意我们仅在用户明确允许信任自签名证书的上下文中使用这段代码(例如,对于测试环境等)。 如果你不想这样做,那么最好不要乱用SSL。

如果您使用的是jdk1.7.0_04,请升级到jdk1.7.0_21。 该更新已解决该问题。