150 votes

Pourquoi le handshake SSL génère-t-il une exception "Could not generate DH keypair" (Impossible de générer une paire de clés DH) ?

Lorsque j'établis une connexion SSL avec certains serveurs IRC (mais pas avec d'autres - probablement en raison de la méthode de cryptage préférée du serveur), j'obtiens l'exception suivante :

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

Cause finale :

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

Un exemple de serveur qui démontre ce problème est aperture.esper.net:6697 (il s'agit d'un serveur IRC). Un exemple de serveur ne présentant pas ce problème est kornbluth.freenode.net:6697. [Il n'est pas surprenant de constater que tous les serveurs de chaque réseau ont le même comportement].

Mon code (qui, comme indiqué, fonctionne lors de la connexion à certains serveurs SSL) est le suivant :

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

C'est la dernière poignée de main de démarrage qui provoque l'exception. Et oui, il y a un peu de magie avec le code "trustAllCerts" ; ce code force le système SSL à ne pas valider les certificats. (Ce n'est donc pas un problème de certificat).

Évidemment, une possibilité est que le serveur d'esper soit mal configuré, mais j'ai cherché et je n'ai pas trouvé d'autres références à des personnes ayant des problèmes avec les ports SSL d'esper, et 'openssl' s'y connecte (voir ci-dessous). Je me demande donc s'il ne s'agit pas d'une limitation du support SSL par défaut de Java, ou quelque chose comme ça. Des suggestions ?

Voici ce qui se passe lorsque je me connecte à aperture.esper.net 6697 en utilisant 'openssl' depuis la ligne de commande :

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

Comme indiqué, après tout cela, il se connecte avec succès, ce qui est plus que ce que l'on peut dire de mon application Java.

Si cela s'avère pertinent, j'utilise OS X 10.6.8, Java version 1.6.0_26.

123voto

Vivin Paliath Points 40975

Le problème, c'est la taille de la prime. La taille maximale acceptable par Java est de 1024 bits. Il s'agit d'un problème connu (voir JDK-6521495 ).

Le rapport de bogue auquel j'ai renvoyé mentionne un solution de rechange en utilisant l'implémentation JCE de BouncyCastle. J'espère que cela fonctionnera pour vous.

MISE À JOUR

Ce problème a été signalé comme un bogue JDK-7044060 et corrigée récemment.

Notez toutefois que la limite n'a été portée qu'à 2048 bits. Pour les tailles > 2048 bits, il y a JDK-8072452 - Suppression de la taille maximale des clés DH ; la solution semble être pour le 9.

70voto

mjj1409 Points 68

La réponse "Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files" n'a pas fonctionné pour moi, mais la suggestion du fournisseur JCE de The BouncyCastle a fonctionné.

Voici les étapes que j'ai suivies en utilisant Java 1.6.0_65-b14-462 sur Mac OSC 10.7.5

1) Télécharger ces jarres :

2) déplacer ces jars dans $JAVA_HOME/lib/ext

3) modifiez $JAVA_HOME/lib/security/java.security comme suit : security.provider.1=org.bouncycastle.jce.provider.BouncyCastleProvider

redémarrer l'application en utilisant JRE et l'essayer

16voto

Zsozso Points 31

Voici ma solution (java 1.6), je serais également intéressé de savoir pourquoi j'ai dû faire cela :

J'ai remarqué dans javax.security.debug=ssl, que la suite de chiffrement utilisée est parfois TLS_DHE_... et parfois TLS_ECDHE_.... Ce dernier cas se produirait si j'ajoutais BouncyCastle. Si TLS_ECDHE_ était sélectionné, la plupart du temps cela fonctionnait, mais pas TOUJOURS, donc ajouter même le fournisseur BouncyCastle n'était pas fiable (échouait avec la même erreur, une fois sur deux environ). Je suppose que quelque part dans l'implémentation SSL de Sun, il arrive que l'on choisisse DHE Parfois, il choisit ECDHE .

La solution proposée ici repose donc sur la suppression complète des algorithmes de chiffrement TLS_DHE_. NOTE : BouncyCastle n'est PAS nécessaire pour la solution.

Créez donc le fichier de certification du serveur en

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

Voici la solution pour une connexion http SSL, à l'exclusion des suites de chiffrement 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));
        }

    }
}

Enfin, voici comment il est utilisé (certFilePath si le chemin du certificat sauvegardé par 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();
        }

14voto

sam Points 378

La réponse ci-dessus est correcte, mais en ce qui concerne la solution de contournement, j'ai eu des problèmes avec l'implémentation de BouncyCastle lorsque je l'ai définie comme fournisseur privilégié :

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

Cette question est également abordée dans un fil de discussion du forum que j'ai trouvé, qui ne mentionne pas de solution. http://www.javakb.com/Uwe/Forum.aspx/java-programmer/47512/TLS-problems

J'ai trouvé une solution alternative qui fonctionne dans mon cas, bien que je n'en sois pas du tout satisfait. La solution consiste à faire en sorte que l'algorithme Diffie-Hellman ne soit pas du tout disponible. Ensuite, en supposant que le serveur supporte un algorithme alternatif, il sera sélectionné pendant la négociation normale. Évidemment, l'inconvénient est que si quelqu'un parvient à trouver un serveur qui ne prend en charge que l'algorithme Diffie-Hellman à 1024 bits ou moins, cela signifie que le protocole ne fonctionnera pas là où il fonctionnait auparavant.

Voici un code qui fonctionne avec un SSLSocket (avant de le connecter) :

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

Méchant.

13voto

Tor Points 360

Vous pouvez désactiver complètement DHE dans votre jdk, en éditant jre/lib/security/java.security et en vous assurant que DHE est désactivé, par exemple comme suit

jdk.tls.disabledAlgorithms=SSLv3, DHE .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X