【问题标题】:Java 7 and Could not generate DH keypairJava 7 无法生成 DH 密钥对
【发布时间】:2012-05-28 01:38:48
【问题描述】:

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

我尝试为 JCE unlimited 替换 jar 文件,但仍然出现相同的错误。自 2007 年以来报告了该错误,但为什么它在 Java 6 上运行而不在 Java 7 上运行?要下载的文件不是正确的吗?我从以前的帖子Java: Why does SSL handshake give 'Could not generate DH keypair' exception? 中获得了链接。

此时我不知道该怎么办。如果我尝试加载 BouncyCastle 提供程序,则会收到 ArrayOutOfIndex 异常。我的服务器只允许 DH 算法,所以我不能使用上面帖子中建议的其他算法。

【问题讨论】:

    标签: ssl java jce


    【解决方案1】:

    一些补充或说明:

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

    如果询问者(或其他任何人)连接到真正需要整数 DH(而不是 ECDH 或 RSA)的服务器,则在 8 之前使用 Java 的唯一方法是让服务器使用 DH 1024 位。哪个 AFAWK 在技术上可以再安全几年,但其利润微薄,被 NIST 等重要权威机构禁止(参见 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 套件;它们已经不安全好几年了,并且在默认情况下保持启用的时间比它们应有的时间要长得多。

    【讨论】:

      【解决方案2】:

      我在 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();
      

      【讨论】:

      • 非常感谢!我在使用 Webscarab(SSL MITM 代理)时遇到了一个奇怪的 java.security.ProviderException: sun.security.pkcs11.wrapper.PKCS11Exception: CKR_DOMAIN_PARAMS_INVALID 错误。明确指定这些密码套件使其再次工作。
      • 我是 Java 证券新手。我应该在哪里写这段代码?
      【解决方案3】:

      鉴于您使用的是最新的 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 或重新运行您的程序。

      【讨论】:

        【解决方案4】:

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

        String[] enabledCipherSuites = socket.getEnabledCipherSuites();
        
        // avoid hardcoding a new list, we just remove the entries
        // which cause the exception
        List<String> 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<String> 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<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory> create().register("https", sslsf).build();
        
        PoolingHttpClientConnectionManager conman = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        builder.setConnectionManager(conman);
        
        CloseableHttpClient httpClient =  builder.build();
        

        小心 我们仅在用户明确启用信任自签名证书的上下文中使用这段代码(例如,用于测试环境等)。如果您不想这样做,那么最好不要弄乱 SSL 的东西。

        【讨论】:

        • 我正在使用 apache httpclient,但不明白如何使用 prepareSocket
        【解决方案5】:

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

        【讨论】:

        • 酷。这已在较新版本的 Java 中得到修复。但我的问题是关于使用旧版本..当我使用旧版本时,有时它可以工作,有时它会给出上述异常..为什么这么随机的行为?如果它是java中的一个错误,那么我想它应该永远不会工作?
        • 不幸的是,我仍然在7u21-2.3.9-1ubuntu1 中收到此错误。
        • 我在1.7.0_55-b13得到它。
        • 我在 amazon linux 中使用 jdk 7 时遇到了同样的问题,升级到 oracle jdk8 解决了这个问题
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-11-20
        • 1970-01-01
        • 2014-06-26
        • 2014-02-21
        • 2015-08-25
        • 2011-01-28
        • 2012-12-24
        相关资源
        最近更新 更多