[java] SSL 핸드 셰이크에서 ‘DH 키 쌍을 생성 할 수 없습니다’예외가 발생하는 이유는 무엇입니까?

일부 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

최종 원인 :

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

예외를 던지는 것은 마지막 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)
---

언급했듯이, 결국 Java 연결에 대해 말할 수있는 것보다 성공적으로 연결됩니다.

관련이 있다면 OS X 10.6.8, Java 버전 1.6.0_26을 사용하고 있습니다.



답변

문제는 소수입니다. Java가 허용하는 최대 허용 크기는 1024 비트입니다. 이것은 알려진 문제입니다 ( JDK-6521495 참조 ).

내가 링크 한 버그 보고서는 BouncyCastle의 JCE 구현을 사용 하는 해결 방법 을 언급합니다 . 잘하면 그것은 당신을 위해 작동해야합니다.

최신 정보

버그 JDK-7044060 으로보고되었습니다. 되어 최근에 수정되었습니다.

그러나이 제한은 2048 비트로 만 높아졌습니다. 2048 비트보다 큰 크기의 경우 JDK-8072452가 있습니다. DH 키의 최대 소수를 제거합니다 . 수정은 9에 대한 것으로 보입니다.


답변

“JCE (Java Cryptography Extension) 무제한 강도 관할 정책 파일”답변은 저에게는 효과가 없었지만 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를 사용하여 앱을 다시 시작하고 사용해보십시오.


답변

여기 내 솔루션 (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'

TLS_DHE_ 암호 스위트를 제외하고 SSL http get에 대한 솔루션보다 나중에 참조되므로 나중에 저장하십시오.

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

    }
}

마지막으로 사용 방법은 다음과 같습니다 (인증서 경로가 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();
        }


답변

위의 답변은 정확하지만 해결 방법 측면에서 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

나는 그것에 만족하지 않지만 내 경우에 맞는 대안 솔루션을 찾았습니다. 해결책은 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()]));

추잡한.


답변

jdk에서 DHE를 완전히 비활성화하고 jre / lib / security / java.security를 ​​편집하고 DHE가 비활성화되었는지 확인할 수 있습니다. 처럼

jdk.tls.disabledAlgorithms=SSLv3, DHE.


답변

제공자를 동적으로 설치할 수 있습니다.

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


답변

이것은 매우 오래된 게시물이지만 Apache HTTPD를 사용하는 경우 DH 크기를 제한 할 수 있습니다. http://httpd.apache.org/docs/current/ssl/ssl_faq.html#javadh를 참조하십시오 .