【问题标题】:HTTPS Socket "javax.net.ssl.SSLHandshakeException: no cipher suites in common"HTTPS 套接字“javax.net.ssl.SSLHandshakeException:没有共同的密码套件”
【发布时间】:2015-12-06 02:00:43
【问题描述】:

我正在尝试为 Web 控制台创建安全的 Web 服务器,但目前我遇到了问题。这是我启动 HTTPS 服务器的代码:

public void startServer()
{
    try
    {
        SSLServerSocketFactory ssf = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
        SSLServerSocket ss = (SSLServerSocket) ssf.createServerSocket(8080);
        for (String s : ss.getEnabledCipherSuites())
        {
            logger.info(s);
        }

        while (true)
        {
            Socket s = ss.accept();
            OutputStream out = s.getOutputStream();
            BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));

            String line = null;
            while (((line = in.readLine()) != null) && (!("".equals(line))))
            {
                System.out.println(line);
            }
            StringBuffer buffer = new StringBuffer();
            buffer.append("<HTML><HEAD><TITLE>HTTPS Server</TITLE></HEAD>\n");
            buffer.append("<BODY>\n<H1>Success!</H1></BODY></HTML>\n");

            String string = buffer.toString();
            byte[] data = string.getBytes();
            out.write("HTTP/1.0 200 OK\n".getBytes());
            out.write(new String("Content-Length: " + data.length + "\n").getBytes());
            out.write("Content-Type: text/html\n\n".getBytes());
            out.write(data);
            out.flush();

            out.close();
            in.close();
            s.close();
        }
    }
    catch (Throwable thrown)
    {
        logger.error(thrown);
    }
}

这会打印出以下结果作为启用的密码套件:

TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
TLS_RSA_WITH_AES_128_CBC_SHA256
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256
TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256
TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
TLS_RSA_WITH_AES_128_CBC_SHA
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA
TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
TLS_DHE_RSA_WITH_AES_128_CBC_SHA
TLS_DHE_DSS_WITH_AES_128_CBC_SHA
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
TLS_RSA_WITH_AES_128_GCM_SHA256
TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256
TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256
TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
TLS_DHE_DSS_WITH_AES_128_GCM_SHA256
TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA
SSL_RSA_WITH_3DES_EDE_CBC_SHA
TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA
TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA
SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA
SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA
TLS_EMPTY_RENEGOTIATION_INFO_SCSV

而且,当我尝试从我的网络浏览器 (Chrome) 连接到 https://localhost:8080/ 时,出现以下异常:

javax.net.ssl.SSLHandshakeException: no cipher suites in common
    at sun.security.ssl.Alerts.getSSLException(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.fatal(Unknown Source)
    at sun.security.ssl.Handshaker.fatalSE(Unknown Source)
    at sun.security.ssl.Handshaker.fatalSE(Unknown Source)
    at sun.security.ssl.ServerHandshaker.chooseCipherSuite(Unknown Source)
    at sun.security.ssl.ServerHandshaker.clientHello(Unknown Source)
    at sun.security.ssl.ServerHandshaker.processMessage(Unknown Source)
    at sun.security.ssl.Handshaker.processLoop(Unknown Source)
    at sun.security.ssl.Handshaker.process_record(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source)
    at sun.security.ssl.SSLSocketImpl.readDataRecord(Unknown Source)
    at sun.security.ssl.AppInputStream.read(Unknown Source)
    at sun.nio.cs.StreamDecoder.readBytes(Unknown Source)
    at sun.nio.cs.StreamDecoder.implRead(Unknown Source)
    at sun.nio.cs.StreamDecoder.read(Unknown Source)
    at java.io.InputStreamReader.read(Unknown Source)
    at java.io.BufferedReader.fill(Unknown Source)
    at java.io.BufferedReader.readLine(Unknown Source)
    at java.io.BufferedReader.readLine(Unknown Source)
    at net.jibini.inventory.http.InventoryHttp.startServer(InventoryHttp.java:54)
    at net.jibini.inventory.http.InventoryHttp.start(InventoryHttp.java:33)
    at net.jibini.inventory.server.InventoryServer$1.run(InventoryServer.java:47)
    at java.lang.Thread.run(Unknown Source)

【问题讨论】:

  • 你能找出 Chrome 支持哪些密码套件吗?
  • 对于服务器虚拟机,使用-Djavax.net.debug=ssl,看看发生了什么。
  • 检查这个问题:stackoverflow.com/questions/15076820/… 另外,您在哪里为您的自签名证书指定证书存储?系统商店里有吗?
  • -Djavax.net.debug=ssl 的输出:pastebin.com/XicjfPXE
  • 听起来您错过了设置密钥库,您需要从 CA 下载证书文件,将其导入 jks 文件并在尝试使用任何 ssl 上下文之前指定它

标签: java sockets ssl https


【解决方案1】:

正如其他人所提到的,使用 -Djavax.net.debug=ssl 将允许您诊断网络级别实际发生的情况。这种情况下的问题可能是您没有指定密钥库,因此您最终将没有证书/密钥,因此您将无法协商密码套件(例如 TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA 需要 DSA 密钥)。

所以生成一个密钥:

keytool -genkey -keystore mySrvKeystore -keyalg RSA

然后这需要作为系统属性 (-Djavax.net.ssl.keyStore ...) 或使用 set 属性调用提供:

System.setProperty("javax.net.ssl.keyStore","mySrvKeystore");
System.setProperty("javax.net.ssl.keyStorePassword","1234567");

您可以在 -Djavax.net.debug=all 中查看客户端正在发送哪些密码套件作为支持,但如果没有密钥/证书,您的服务器将不支持其中任何一个,并且将返回关于没有共享密码套件的相当神秘的消息。

更新

作为更完整的示例,请参考以下内容:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;

import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;

public class TestSSL
{    
    public static void main( String args[] )
    {
        try
        {
            System.setProperty("javax.net.ssl.keyStore","mySrvKeystore");
            System.setProperty("javax.net.ssl.keyStorePassword","1234567");
            SSLServerSocketFactory ssf = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
            SSLServerSocket ss = (SSLServerSocket) ssf.createServerSocket(9001);
            ss.setEnabledProtocols( new String[]{"TLSv1","TLSv1.1","TLSv1.2"} );
            for (String s : ss.getEnabledCipherSuites())
            {
                System.out.println( s );
            }

            while (true)
            {
                Socket s = ss.accept();

                OutputStream out = s.getOutputStream();
                BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));

                String line = null;
                while (((line = in.readLine()) != null) && (!("".equals(line))))
                {
                    System.out.println(line);
                }

                if( line != null )
                {
                    StringBuffer buffer = new StringBuffer();
                    buffer.append("<HTML><HEAD><TITLE>HTTPS Server</TITLE></HEAD>\n");
                    buffer.append("<BODY>\n<H1>Success!</H1></BODY></HTML>\n");

                    String string = buffer.toString();
                    byte[] data = string.getBytes();

                    out.write("HTTP/1.0 200 OK\n".getBytes());
                    out.write(new String("Content-Length: " + data.length + "\n").getBytes());
                    out.write("Content-Type: text/html\n\n".getBytes());
                    out.write(data);
                    out.flush();
                }

                out.close();
                in.close();
                s.close();
            }
        }
        catch (Throwable thrown)
        {
            thrown.printStackTrace();
        }
    }
}

请注意与您之前发布的示例的以下主要区别:

  1. 两个 System.setProperty 调用引用密钥库和密钥库密码。这是根据我上面给出的命令生成的。生成自签名证书没什么特别的。它应该位于启动 java 的工作目录中。请注意,您连接到此的浏览器会显示有关证书不受“信任”的警告,因此如果您只是在玩这个,则需要忽略这些。
  2. 我将启用的协议设置为限制为 TLS(SSL 已失效,坦率地说,从 PCI 的角度来看,TLSv1 和 TLSv1.1 可能是下一个)。
  3. if(line != null) 块可能看起来没有必要,但如果您使用 Chrome 进行测试,您会发现当您拉出该页面时,Chrome 实际上会与您的服务器建立几个“推测性”连接,因此如果之前您的服务器将会死机它能够提供页面内容,因为它获取您的证书的第一个连接将终止它,并且下一个连接将被拒绝。

【讨论】:

  • 我应该把密钥库放在哪里?
  • 这是另一个代码 sn-p 我尝试在根 src 目录中使用一个名为“keystore”的文件。 pastebin.com/423TR9X1我用keytool -genkey -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000创建了文件
  • 我没有拉下你的替代 sn-p,因为你的第一个 sn-p 是在实际问题中,并且想确保我解决了那个问题。请参阅我的更新以获取工作示例和更多详细信息。
  • Nitpick:ECHDE_ECDSA 密钥交换需要 EC 密钥和允许 ECDSA 的证书。 ECDSA 和 ECDH 算法使用相同的密钥格式,请参阅java.security.interfaces.EC{,Private,Public}Key,这与经典 DSA 和经典 DH 的密钥(和证书)有很大不同。但同意您的主要观点,即服务器需要一个带有 privatekey&cert(chain) 的密钥库。
  • 在这里查看您的浏览器支持的密码套件:cc.dcsec.uni-hannover.de。另请查看您的 Java 版本的 JSSE 文档,了解您在服务器端支持哪些套件 docs.oracle.com/javase/8/docs/technotes/guides/security/… - 密码套件。
猜你喜欢
  • 1970-01-01
  • 2015-07-27
  • 2017-05-08
  • 1970-01-01
  • 2014-05-07
  • 1970-01-01
  • 2020-01-07
  • 1970-01-01
  • 2017-08-28
相关资源
最近更新 更多