为什么SSL握手会给出"无法生成DH密钥对"的异常?

sam*_*sam 139 java ssl cryptography diffie-hellman

当我与某些IRC服务器建立SSL连接(但不是其他服务器 - 可能是由于服务器的首选加密方法),我得到以下异常:

Caused by: java.lang.RuntimeException: Could not generate DH keypair
    at com.sun.net.ssl.internal.ssl.DHCrypt.<init>(DHCrypt.java:106)
    at com.sun.net.ssl.internal.ssl.ClientHandshaker.serverKeyExchange(ClientHandshaker.java:556)
    at com.sun.net.ssl.internal.ssl.ClientHandshaker.processMessage(ClientHandshaker.java:183)
    at com.sun.net.ssl.internal.ssl.Handshaker.processLoop(Handshaker.java:593)
    at com.sun.net.ssl.internal.ssl.Handshaker.process_record(Handshaker.java:529)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:893)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1138)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1165)
    ... 3 more
Run Code Online (Sandbox Code Playgroud)

最终原因:

Caused by: java.security.InvalidAlgorithmParameterException: Prime size must be multiple of 64, and can only range from 512 to 1024 (inclusive)
    at com.sun.crypto.provider.DHKeyPairGenerator.initialize(DashoA13*..)
    at java.security.KeyPairGenerator$Delegate.initialize(KeyPairGenerator.java:627)
    at com.sun.net.ssl.internal.ssl.DHCrypt.<init>(DHCrypt.java:100)
    ... 10 more
Run Code Online (Sandbox Code Playgroud)

演示此问题的服务器示例是aperture.esper.net:6697(这是一个IRC服务器).没有证明问题的服务器示例是kornbluth.freenode.net:6697.[毫不奇怪,每个网络上的所有服务器共享相同的行为.]

我的代码(如上所述,在连接到某些SSL服务器时有效)是:

    SSLContext sslContext = SSLContext.getInstance("SSL");
    sslContext.init(null, trustAllCerts, new SecureRandom());
    s = (SSLSocket)sslContext.getSocketFactory().createSocket();
    s.connect(new InetSocketAddress(host, port), timeout);
    s.setSoTimeout(0);
    ((SSLSocket)s).startHandshake();
Run Code Online (Sandbox Code Playgroud)

这是抛出异常的最后一个startHandshake.是的,'trustAllCerts'有一些魔力; 该代码强制SSL系统不验证证书.(所以...不是证书问题.)

显然有一种可能性是esper的服务器配置错误,但我搜索并没有找到任何其他对esper的SSL端口出现问题的人的引用,并且'openssl'连接到它(见下文).所以我想知道这是否是Java默认SSL支持的限制,或者其他什么.有什么建议?

这是当我使用命令行中的'openssl'连接到aperture.esper.net 6697时发生的情况:

~ $ openssl s_client -connect aperture.esper.net:6697
CONNECTED(00000003)
depth=0 /C=GB/ST=England/L=London/O=EsperNet/OU=aperture.esper.net/CN=*.esper.net/emailAddress=support@esper.net
verify error:num=18:self signed certificate
verify return:1
depth=0 /C=GB/ST=England/L=London/O=EsperNet/OU=aperture.esper.net/CN=*.esper.net/emailAddress=support@esper.net
verify return:1
---
Certificate chain
 0 s:/C=GB/ST=England/L=London/O=EsperNet/OU=aperture.esper.net/CN=*.esper.net/emailAddress=support@esper.net
   i:/C=GB/ST=England/L=London/O=EsperNet/OU=aperture.esper.net/CN=*.esper.net/emailAddress=support@esper.net
---
Server certificate
-----BEGIN CERTIFICATE-----
[There was a certificate here, but I deleted it to save space]
-----END CERTIFICATE-----
subject=/C=GB/ST=England/L=London/O=EsperNet/OU=aperture.esper.net/CN=*.esper.net/emailAddress=support@esper.net
issuer=/C=GB/ST=England/L=London/O=EsperNet/OU=aperture.esper.net/CN=*.esper.net/emailAddress=support@esper.net
---
No client certificate CA names sent
---
SSL handshake has read 2178 bytes and written 468 bytes
---
New, TLSv1/SSLv3, Cipher is DHE-RSA-AES256-SHA
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
SSL-Session:
    Protocol  : TLSv1
    Cipher    : DHE-RSA-AES256-SHA
    Session-ID: 51F1D40A1B044700365D3BD1C61ABC745FB0C347A334E1410946DCB5EFE37AFD
    Session-ID-ctx: 
    Master-Key: DF8194F6A60B073E049C87284856B5561476315145B55E35811028C4D97F77696F676DB019BB6E271E9965F289A99083
    Key-Arg   : None
    Start Time: 1311801833
    Timeout   : 300 (sec)
    Verify return code: 18 (self signed certificate)
---
Run Code Online (Sandbox Code Playgroud)

如前所述,它确实成功连接,这比我对Java应用程序的说法更多.

它是否相关,我使用的是OS X 10.6.8,Java版本1.6.0_26.

Viv*_*ath 113

问题是最大的尺寸.Java接受的最大可接受大小是1024位.这是一个已知问题(参见JDK-6521495).

我链接的错误报告提到了使用BouncyCastle的JCE实现的解决方法.希望这对你有用.

UPDATE

这被报告为错误JDK-7044060并且最近修复了.

但请注意,限制仅提高到2048位.对于大小> 2048位,有JDK-8072452 - 删除DH键的最大主要大小 ; 修复似乎是9.

  • +1.拥有Sun Developer Network帐户的每个人都请为此错误投票. (13认同)
  • 凉.这已在较新版本的Java中修复.但我的问题是关于使用旧版本..当我使用旧版本时,有时它有效,有时它会给出以上异常.为什么这么随机的行为?如果它是java中的一个bug,那么我想它应该永远不会工作? (3认同)
  • BouncyCastle的JCE提供程序实现对我有用 (3认同)
  • 2048修复程序被移植到IcedTea 2.5.3.较新版本的IcedTea将其增加到4096. (2认同)

mjj*_*409 67

"Java密码学扩展(JCE)无限强度管辖权政策文件"答案对我不起作用,但BouncyCastle的JCE提供商建议做了.

以下是我在Mac OSC 10.7.5上使用Java 1.6.0_65-b14-462所采取的步骤

1)下载这些罐子:

2)将这些罐子移到$ JAVA_HOME/lib/ext

3)编辑$ JAVA_HOME/lib/security/java.security,如下所示:security.provider.1 = org.bouncycastle.jce.provider.BouncyCastleProvider

使用JRE重启app并尝试一下

  • security.provider.2 = org.bouncycastle.jce.provider.BouncyCastleProvider做得更好,将其置于1.导致默认软件出错. (11认同)
  • 适合我!虽然不太清楚我在做什么. (5认同)

Zso*_*zso 15

这是我的解决方案(java 1.6),也有兴趣为什么我必须这样做:

我注意到javax.security.debug = ssl,有时候使用的密码套件是TLS_DHE _...而且有时它是TLS_ECDHE _....如果我添加了BouncyCastle,那么后者会发生.如果选择了TLS_ECDHE_,那么大部分工作时间,但不是总是如此,所以添加甚至BouncyCastle提供程序是不可靠的(每隔一次左右都会出现相同的错误).我想在Sun SSL实现的某个地方有时会选择DHE,有时它会选择ECDHE.

因此,此处发布的解决方案依赖于完全删除TLS_DHE_密码.注意:解决方案不需要BouncyCastle.

因此,创建服务器认证文件:

echo |openssl s_client -connect example.org:443 2>&1 |sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'
Run Code Online (Sandbox Code Playgroud)

保存它,因为它将在稍后引用,而不是这里是SSL http get的解决方案,不包括TLS_DHE_密码套件.

package org.example.security;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.KeyStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;

import org.apache.log4j.Logger;

public class SSLExcludeCipherConnectionHelper {

    private Logger logger = Logger.getLogger(SSLExcludeCipherConnectionHelper.class);

    private String[] exludedCipherSuites = {"_DHE_","_DH_"};

    private String trustCert = null;

    private TrustManagerFactory tmf;

    public void setExludedCipherSuites(String[] exludedCipherSuites) {
        this.exludedCipherSuites = exludedCipherSuites;
    }

    public SSLExcludeCipherConnectionHelper(String trustCert) {
        super();
        this.trustCert = trustCert;
        //Security.addProvider(new BouncyCastleProvider());
        try {
            this.initTrustManager();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private void initTrustManager() throws Exception {
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        InputStream caInput = new BufferedInputStream(new FileInputStream(trustCert));
        Certificate ca = null;
        try {
            ca = cf.generateCertificate(caInput);
            logger.debug("ca=" + ((X509Certificate) ca).getSubjectDN());
        } finally {
            caInput.close();
        }

        // Create a KeyStore containing our trusted CAs
        KeyStore keyStore = KeyStore.getInstance("jks");
        keyStore.load(null, null);
        keyStore.setCertificateEntry("ca", ca);

        // Create a TrustManager that trusts the CAs in our KeyStore
        String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
        tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
        tmf.init(keyStore);
    }

    public String get(URL url) throws Exception {
        // Create an SSLContext that uses our TrustManager
        SSLContext context = SSLContext.getInstance("TLS");
        context.init(null, tmf.getTrustManagers(), null);
        SSLParameters params = context.getSupportedSSLParameters();
        List<String> enabledCiphers = new ArrayList<String>();
        for (String cipher : params.getCipherSuites()) {
            boolean exclude = false;
            if (exludedCipherSuites != null) {
                for (int i=0; i<exludedCipherSuites.length && !exclude; i++) {
                    exclude = cipher.indexOf(exludedCipherSuites[i]) >= 0;
                }
            }
            if (!exclude) {
                enabledCiphers.add(cipher);
            }
        }
        String[] cArray = new String[enabledCiphers.size()];
        enabledCiphers.toArray(cArray);

        // Tell the URLConnection to use a SocketFactory from our SSLContext
        HttpsURLConnection urlConnection =
            (HttpsURLConnection)url.openConnection();
        SSLSocketFactory sf = context.getSocketFactory();
        sf = new DOSSLSocketFactory(sf, cArray);
        urlConnection.setSSLSocketFactory(sf);
        BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
        String inputLine;
        StringBuffer buffer = new StringBuffer();
        while ((inputLine = in.readLine()) != null) 
            buffer.append(inputLine);
        in.close();

        return buffer.toString();
    }

    private class DOSSLSocketFactory extends javax.net.ssl.SSLSocketFactory {

        private SSLSocketFactory sf = null;
        private String[] enabledCiphers = null;

        private DOSSLSocketFactory(SSLSocketFactory sf, String[] enabledCiphers) {
            super();
            this.sf = sf;
            this.enabledCiphers = enabledCiphers;
        }

        private Socket getSocketWithEnabledCiphers(Socket socket) {
            if (enabledCiphers != null && socket != null && socket instanceof SSLSocket)
                ((SSLSocket)socket).setEnabledCipherSuites(enabledCiphers);

            return socket;
        }

        @Override
        public Socket createSocket(Socket s, String host, int port,
                boolean autoClose) throws IOException {
            return getSocketWithEnabledCiphers(sf.createSocket(s, host, port, autoClose));
        }

        @Override
        public String[] getDefaultCipherSuites() {
            return sf.getDefaultCipherSuites();
        }

        @Override
        public String[] getSupportedCipherSuites() {
            if (enabledCiphers == null)
                return sf.getSupportedCipherSuites();
            else
                return enabledCiphers;
        }

        @Override
        public Socket createSocket(String host, int port) throws IOException,
                UnknownHostException {
            return getSocketWithEnabledCiphers(sf.createSocket(host, port));
        }

        @Override
        public Socket createSocket(InetAddress address, int port)
                throws IOException {
            return getSocketWithEnabledCiphers(sf.createSocket(address, port));
        }

        @Override
        public Socket createSocket(String host, int port, InetAddress localAddress,
                int localPort) throws IOException, UnknownHostException {
            return getSocketWithEnabledCiphers(sf.createSocket(host, port, localAddress, localPort));
        }

        @Override
        public Socket createSocket(InetAddress address, int port,
                InetAddress localaddress, int localport) throws IOException {
            return getSocketWithEnabledCiphers(sf.createSocket(address, port, localaddress, localport));
        }

    }
}
Run Code Online (Sandbox Code Playgroud)

最后是如何使用它(如果从openssl保存证书的路径,则为certFilePath):

try {
            URL url = new URL("https://www.example.org?q=somedata");            
            SSLExcludeCipherConnectionHelper sslExclHelper = new SSLExcludeCipherConnectionHelper(certFilePath);
            logger.debug(
                    sslExclHelper.get(url)
            );
        } catch (Exception ex) {
            ex.printStackTrace();
        }
Run Code Online (Sandbox Code Playgroud)

  • 刚测试了你的解决方案 它按预期工作.谢谢.实际上,只需在`JDK_HOME/jre/lib/security/java.security`中添加`jdk.tls.disabledAlgorithms = DHE,ECDHE`也可以并且避免所有这些代码. (9认同)
  • 只是禁用DHE为我工作:`jdk.tls.disabledAlgorithms = DHE`.使用1.7.0_85-b15. (3认同)
  • 不要禁用 ECDHE。它与 DHE 不同,甚至不使用质数。 (2认同)

sam*_*sam 13

上面的答案是正确的,但就解决方法而言,当我将其设置为首选提供程序时,我遇到了BouncyCastle实现的问题:

java.lang.ArrayIndexOutOfBoundsException: 64
    at com.sun.crypto.provider.TlsPrfGenerator.expand(DashoA13*..)
Run Code Online (Sandbox Code Playgroud)

这也在我发现的一个论坛帖子中讨论过,它没有提到解决方案. http://www.javakb.com/Uwe/Forum.aspx/java-programmer/47512/TLS-problems

我找到了一个适合我案例的替代解决方案,尽管我对此并不满意.解决方案是设置它以使Diffie-Hellman算法根本不可用.然后,假设服务器支持替代算法,它将在正常协商期间进行选择.显然,这样做的缺点是,如果有人设法找到一个仅支持1024位或更少位的Diffie-Hellman的服务器,那么这实际上意味着它将无法在之前工作的地方工作.

这是给定SSLSocket的代码(在连接之前):

List<String> limited = new LinkedList<String>();
for(String suite : ((SSLSocket)s).getEnabledCipherSuites())
{
    if(!suite.contains("_DHE_"))
    {
        limited.add(suite);
    }
}
((SSLSocket)s).setEnabledCipherSuites(limited.toArray(
    new String[limited.size()]));
Run Code Online (Sandbox Code Playgroud)

讨厌.

  • 遗憾的是,这是唯一的方法:(。这张票自 07 年以来一直开放。奇怪的是,四年来没有采取任何措施。 (2认同)

小智 12

您可以动态安装提供程序:

1)下载这些罐子:

  • bcprov-jdk15on-152.jar
  • bcprov-ext-jdk15on-152.jar

2)将罐子复制到WEB-INF/lib(或你的类路径)

3)动态添加提供者:

import org.bouncycastle.jce.provider.BouncyCastleProvider;

...

Security.addProvider(new BouncyCastleProvider());


Tor*_*Tor 12

您可以在jdk中完全禁用DHE,编辑jre/lib/security/java.security并确保禁用DHE,例如.喜欢

jdk.tls.disabledAlgorithms=SSLv3, DHE.

  • 仅适用于JDK 1.7及更高版本.[链接](http://mail.openjdk.java.net/pipermail/security-dev/2014-April/010379.html) (4认同)

Ber*_*rtl 7

这是一篇相当古老的帖子,但如果您使用Apache HTTPD,则可以限制DH大小.见http://httpd.apache.org/docs/current/ssl/ssl_faq.html#javadh


Lek*_*kie 6

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

  • 我使用的是JDK 1.7.0_79.对我不起作用 (4认同)
  • 我刚刚下载了Java SE Development Kit 7u25,根据[我编写的小程序来确定支持的最大DH大小](https://gist.github.com/ppelleti/6275452),它仍然是1024. (2认同)
  • JDK 1.7.0_25 问题仍然存在 (2认同)

mjo*_*ble 5

尝试从Java下载站点下载“ Java密码学扩展(JCE)无限强度管辖权策略文件”,然后替换JRE中的文件。

这对我有用,我什至不需要使用BouncyCastle-标准的Sun JCE能够连接到服务器。

PS。我在更改策略文件之前尝试使用BouncyCastle时遇到了相同的错误(ArrayIndexOutOfBoundsException:64),因此看来我们的情况非常相似。


小智 5

如果你仍然被这个问题所困扰并且你正在使用Apache httpd v> 2.4.7,试试这个:http: //httpd.apache.org/docs/current/ssl/ssl_faq.html#javadh

从网址复制:

从版本2.4.7开始,mod_ssl将使用DH参数,其中包括长度超过1024位的素数.但是,Java 7及更早版本将它们对DH prime大小的支持限制为最多1024位.

如果基于Java的客户端因例外java.lang.RuntimeException而中止:无法生成DH密钥对和java.security.InvalidAlgorithmParameterException:Prime大小必须是64的倍数,并且只能在512到1024(包括)范围内,并且httpd logs tlsv1 alert内部错误(SSL警报号80)(在LogLevel信息或更高版本),您可以使用SSLCipherSuite重新排列mod_ssl的密码列表(可能与SSLHonorCipherOrder一起使用),或者您可以使用1024位素数的自定义DH参数,它总是优先于任何内置的DH参数.

要生成自定义DH参数,请使用

openssl dhparam 1024

命令.或者,您可以使用RFC 2409第6.2节中的以下标准1024位DH参数:

-----BEGIN DH PARAMETERS-----
MIGHAoGBAP//////////yQ/aoiFowjTExmKLgNwc0SkCTgiKZ8x0Agu+pjsTmyJR
Sgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHCReSFtXZiXn7G9ExC6aY37WsL
/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR7OZTgf//////////AgEC
-----END DH PARAMETERS-----
Run Code Online (Sandbox Code Playgroud)

将自定义参数(包括"BEGIN DH PARAMETERS"和"END DH PARAMETERS"行)添加到使用SSLCertificateFile指令配置的第一个证书文件的末尾.


我在客户端使用java 1.6,它解决了我的问题.我没有降低密码套件等,但是在cert文件中添加了自定义生成的DH参数.


小智 5

您可能有不正确的Maven依赖关系。您必须在Maven依赖层次结构中找到这些库:

bcprov-jdk14, bcpkix-jdk14, bcmail-jdk14
Run Code Online (Sandbox Code Playgroud)

如果您有这些依赖关系,那就是错误,那么您应该这样做:

添加依赖项:

<dependency>
    <groupId>org.bouncycastle</groupId>
    <artifactId>bcmail-jdk15on</artifactId>
    <version>1.59</version>
</dependency>
Run Code Online (Sandbox Code Playgroud)

从包含错误依赖关系的工件中排除这些依赖关系,在我的情况下是:

<dependency>
    <groupId>com.lowagie</groupId>
    <artifactId>itext</artifactId>
    <version>2.1.7</version>
    <exclusions>
        <exclusion>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bctsp-jdk14</artifactId>                
        </exclusion>
        <exclusion>
            <groupId>bouncycastle</groupId>
            <artifactId>bcprov-jdk14</artifactId>               
        </exclusion>
        <exclusion>
            <groupId>bouncycastle</groupId>
            <artifactId>bcmail-jdk14</artifactId>               
        </exclusion>
    </exclusions>       
</dependency>
Run Code Online (Sandbox Code Playgroud)