Почему рукопожатие SSL дает исключение «Не удалось создать пару ключей DH»? - PullRequest
135 голосов
/ 28 июля 2011

Когда я устанавливаю SSL-соединение с некоторыми IRC-серверами (но не с другими - предположительно из-за предпочтительного метода шифрования сервера), я получаю следующее исключение:

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

Конечная причина:

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

Примером сервера, демонстрирующего эту проблему, является 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();

Это такпоследний стартHandshake, который выбрасывает исключение.И да, с «trustAllCerts» происходит какая-то магия;этот код заставляет систему SSL не проверять сертификаты.(Так что ... не проблема.)

Очевидно, что одна из возможностей заключается в неправильной настройке сервера esper, но я искал и не нашел никаких других ссылок на людей, имеющих проблемы с портами SSL esper, и 'opensslподключается к нему (см. ниже).Поэтому мне интересно, является ли это ограничением поддержки SSL по умолчанию в Java или чем-то еще.Любые предложения?

Вот что происходит, когда я подключаюсь к aperture.esper.net 6697 с помощью 'openssl' из командной строки:

~ $ 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)
---

Как уже отмечалось, после этого он действительно успешно соединяется, чтоэто больше, чем вы можете сказать для моего приложения Java.

Если это уместно, я использую OS X 10.6.8, версия Java 1.6.0_26.

Ответы [ 21 ]

112 голосов
/ 28 июля 2011

Проблема в простом размере.Максимально допустимый размер, который принимает Java, составляет 1024 бита.Это известная проблема (см. JDK-6521495 ).

В сообщении об ошибке, на которое я ссылался, упоминается обходной путь с использованием реализации JCE BouncyCastle.Надеемся, что это сработает для вас.

ОБНОВЛЕНИЕ

Это было сообщено как ошибка JDK-7044060 и недавно исправлено.Обратите внимание, однако, что предел был увеличен только до 2048 бит.Для размеров> 2048 бит есть JDK-8072452 - Удалить максимальный основной размер ключей DH ;исправление, по-видимому, относится к 9.

67 голосов
/ 21 марта 2015

Ответ «Файлы политики неограниченной юрисдикции Java Cryptography Extension (JCE)» не сработал для меня, но предложение поставщика JCE от BouncyCastle сработало.

Вот шаги, которые я предпринял, используя Java 1.6.0_65-b14-462 на Mac OSC 10.7.5

1) Скачать эти банки:

2) переместить эти банки в $ JAVA_HOME / lib / ext

3) отредактируйте файл $ JAVA_HOME / lib / security / java.security следующим образом: security.provider.1 = org.bouncycastle.jce.provider.BouncyCastleProvider

перезапустите приложение с помощью JRE и попробуйте

15 голосов
/ 15 августа 2013

Вот мое решение (Java 1.6), также было бы интересно, почему я должен был сделать это:

Я заметил из javax.security.debug = ssl, что иногда используемым комплектом шифров является TLS_DHE _... и иногда это TLS_ECDHE _.... Позднее произойдет, если я добавлю BouncyCastle. Если был выбран TLS_ECDHE_, БОЛЬШЕ времени, когда он работал, но не ВСЕГДА, поэтому добавление даже поставщика BouncyCastle было ненадежным (сбой с той же ошибкой, каждый раз или около того). Я предполагаю, что где-то в реализации SSL SSL иногда выбирают DHE , иногда выбирают ECDHE .

Таким образом, решение, опубликованное здесь, основано на полном удалении шифров TLS_DHE_. ПРИМЕЧАНИЕ: BouncyCastle НЕ требуется для решения.

Создайте файл сертификации сервера:

echo |openssl s_client -connect example.org:443 2>&1 |sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p'

Сохраните это, так как на него будет ссылаться позже, чем здесь решение для HTTP 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));
        }

    }
}

Наконец, вот как это используется (certFilePath, если путь к сертификату сохранен из openssl):

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();
        }
13 голосов
/ 28 июля 2011

Ответ выше верен, но с точки зрения обходного пути у меня возникли проблемы с реализацией BouncyCastle, когда я установил ее в качестве предпочтительного поставщика:

java.lang.ArrayIndexOutOfBoundsException: 64
    at com.sun.crypto.provider.TlsPrfGenerator.expand(DashoA13*..)

Это также обсуждается в одной найденной мной ветке форума, в которой не упоминается решение. http://www.javakb.com/Uwe/Forum.aspx/java-programmer/47512/TLS-problems

Я нашел альтернативное решение, которое работает для моего случая, хотя я совсем не доволен им. Решение состоит в том, чтобы установить его так, чтобы алгоритм Диффи-Хеллмана вообще не был доступен. Затем, предположив, что сервер поддерживает альтернативный алгоритм, он будет выбирать во время обычного согласования. Очевидно, что недостатком этого является то, что если кому-то удастся найти сервер, который поддерживает только Диффи-Хеллмана со скоростью 1024 бит или меньше, то это фактически означает, что он не будет работать там, где раньше работал.

Вот код, который работает при наличии 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()]));

Насти.

12 голосов
/ 10 июля 2015

Вы можете полностью отключить DHE в своем jdk, отредактировать jre / lib / security / java.security и убедиться, что DHE отключен, например. как

jdk.tls.disabledAlgorithms=SSLv3, DHE.

11 голосов
/ 07 мая 2015

Вы можете установить провайдера динамически:

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

7 голосов
/ 19 ноября 2014

Это довольно старый пост, но если вы используете Apache HTTPD, вы можете ограничить размер DH. Смотри http://httpd.apache.org/docs/current/ssl/ssl_faq.html#javadh

6 голосов
/ 22 мая 2013

Если вы используете jdk1.7.0_04, обновитесь до jdk1.7.0_21. Проблема была исправлена ​​в этом обновлении.

5 голосов
/ 18 ноября 2011

Попробуйте загрузить «Файлы политики неограниченной юрисдикции расширения криптографии Java (JCE)» с сайта загрузки Java и заменить файлы в своей JRE.

Это сработало для меня, и мне даже не нужно было использовать BouncyCastle - стандарт Sun JCE мог подключаться к серверу.

PS. Я получил ту же ошибку (ArrayIndexOutOfBoundsException: 64), когда пытался использовать BouncyCastle перед изменением файлов политики, поэтому, похоже, наша ситуация очень похожа.

5 голосов
/ 14 ноября 2015

Если вы все еще укушены этой проблемой И вы используете Apache httpd v> 2.4.7, попробуйте следующее: http://httpd.apache.org/docs/current/ssl/ssl_faq.html#javadh

скопировано с URL :

Начиная с версии 2.4.7, mod_ssl будет использовать параметры DH, которые включают простые числа длиной более 1024 бит.Java 7 и более ранние версии ограничивают поддержку простейших размеров DH максимум 1024 битами.

Если ваш клиент на основе Java прерывает работу с такими исключениями, как java.lang.RuntimeException: не удалось сгенерировать пару ключей DH и java.security.InvalidAlgorithmParameterException: простой размер должен быть кратным 64, и может варьироваться от 512 до 1024 (включительно), а httpd регистрирует внутреннюю ошибку tlsv1 alert (SSL alert number 80) (в LogLevel info или выше), вы можете либо переставитьСписок шифров mod_ssl с SSLCipherSuite (возможно, в сочетании с SSLHonorCipherOrder), или вы можете использовать пользовательские параметры DH с 1024-битным простым числом, которое всегда будет иметь приоритет над любым из встроенных параметров DH.

Для генерацииПользовательские параметры DH, используйте команду

openssl dhparam 1024

.В качестве альтернативы вы можете использовать следующие стандартные 1024-битные параметры DH из RFC 2409, раздел 6.2:

-----BEGIN DH PARAMETERS-----
MIGHAoGBAP//////////yQ/aoiFowjTExmKLgNwc0SkCTgiKZ8x0Agu+pjsTmyJR
Sgh5jjQE3e+VGbPNOkMbMCsKbfJfFDdP4TVtbVHCReSFtXZiXn7G9ExC6aY37WsL
/1y29Aa37e44a/taiZ+lrp8kEXxLH+ZJKGZR7OZTgf//////////AgEC
-----END DH PARAMETERS-----

Добавить пользовательские параметры, включая строки «BEGIN DH PARAMETERS» и «END DH PARAMETERS», в конецпервого файла сертификата, который вы настроили с помощью директивы SSLCertificateFile.


Я использую Java 1.6 на стороне клиента, и это решило мою проблему.Я не опустил наборы шифров и т.п., но добавил специально созданный параметр DH в файл сертификата ..

...