如何使Java 6成功建立SSL连接,而不像Java 7一样失败并显示“SSL peer shut down incorrectly”错误?

28

我发现客户端使用Java 6运行的SSL连接失败并出现异常,如下所示:

Caused by: javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:882)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.performInitialHandshake(SSLSocketImpl.java:1188)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1215)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:1199)
    at sun.net.www.protocol.https.HttpsClient.afterConnect(HttpsClient.java:434)
    at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(AbstractDelegateHttpsURLConnection.java:166)
    at sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(HttpsURLConnectionImpl.java:133)
    ... 35 more
Caused by: java.io.EOFException: SSL peer shut down incorrectly
    at com.sun.net.ssl.internal.ssl.InputRecord.read(InputRecord.java:462)
    at com.sun.net.ssl.internal.ssl.SSLSocketImpl.readRecord(SSLSocketImpl.java:863)
    ... 41 more

服务器是基于Tomcat 7的应用程序,运行在Java 7、Linux和Amazon EC2上,就这些而言。

我找到了许多可能原因的建议,包括意外连接到非SSL端口等。我相信我已经排除了所有可能性,主要是因为 完全相同的客户端在运行Java 7时可以正常工作,而没有任何改变。(两种情况下都是使用OS X)

下面我包含了Java 6和Java 7的SSL连接过程的调试输出。我的问题是,这是否表明一些可能的加密算法或协议设置(也许是Java 7中的默认设置)可以启用Java 6使其正常工作

Java 6:

Allow unsafe renegotiation: false
Allow legacy hello messages: true
Is initial handshake: true
Is secure renegotiation: false
%% No cached client session
*** ClientHello, TLSv1
RandomCookie:  GMT: 1363993281 bytes = { 77, 153, 100, 72, 45, 178, 253, 243, 195, 167, 17, 151, 39, 247, 148, 102, 213, 129, 39, 17, 26, 139, 157, 154, 63, 88, 41, 160 }
Session ID:  {}
Cipher Suites: [SSL_RSA_WITH_RC4_128_MD5, SSL_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_256_CBC_SHA, SSL_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA, SSL_DHE_DSS_WITH_3DES_EDE_CBC_SHA, SSL_RSA_WITH_DES_CBC_SHA, SSL_DHE_RSA_WITH_DES_CBC_SHA, SSL_DHE_DSS_WITH_DES_CBC_SHA, SSL_RSA_EXPORT_WITH_RC4_40_MD5, SSL_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_RSA_EXPORT_WITH_DES40_CBC_SHA, SSL_DHE_DSS_EXPORT_WITH_DES40_CBC_SHA, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
Compression Methods:  { 0 }
***
main, WRITE: TLSv1 Handshake, length = 81
main, WRITE: SSLv2 client hello message, length = 110
main, received EOFException: error
main, handling exception: javax.net.ssl.SSLHandshakeException: Remote host closed connection during handshake
main, SEND TLSv1 ALERT:  fatal, description = handshake_failure
main, WRITE: TLSv1 Alert, length = 2
main, called closeSocket()

Java 7:

Ignoring unavailable cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA
Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
Ignoring unsupported cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA256
Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256
Ignoring unsupported cipher suite: TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384
Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA256
Ignoring unavailable cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
Ignoring unsupported cipher suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
Ignoring unavailable cipher suite: TLS_DHE_DSS_WITH_AES_256_CBC_SHA
Ignoring unsupported cipher suite: TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384
Ignoring unsupported cipher suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
Ignoring unsupported cipher suite: TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256
Ignoring unavailable cipher suite: TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA
Ignoring unavailable cipher suite: TLS_RSA_WITH_AES_256_CBC_SHA
Ignoring unsupported cipher suite: TLS_RSA_WITH_AES_128_CBC_SHA256
Allow unsafe renegotiation: false
Allow legacy hello messages: true
Is initial handshake: true
Is secure renegotiation: false
main, setSoTimeout(0) called
%% No cached client session
*** ClientHello, TLSv1
RandomCookie:  GMT: 1363993435 bytes = { 131, 83, 80, 186, 215, 90, 171, 131, 231, 18, 184, 183, 249, 155, 197, 204, 73, 1, 74, 79, 32, 142, 236, 28, 111, 37, 58, 255 }
Session ID:  {}
Cipher Suites: [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_RC4_128_SHA, TLS_ECDHE_RSA_WITH_RC4_128_SHA, SSL_RSA_WITH_RC4_128_SHA, TLS_ECDH_ECDSA_WITH_RC4_128_SHA, TLS_ECDH_RSA_WITH_RC4_128_SHA, 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, SSL_RSA_WITH_RC4_128_MD5, TLS_EMPTY_RENEGOTIATION_INFO_SCSV]
Compression Methods:  { 0 }
Extension elliptic_curves, curve names: {secp256r1, sect163k1, sect163r2, secp192r1, secp224r1, sect233k1, sect233r1, sect283k1, sect283r1, secp384r1, sect409k1, sect409r1, secp521r1, sect571k1, sect571r1, secp160k1, secp160r1, secp160r2, sect163r1, secp192k1, sect193r1, sect193r2, secp224k1, sect239k1, secp256k1}
Extension ec_point_formats, formats: [uncompressed]
Extension server_name, server_name: [host_name: ec2-xx-xx-xx-xx.compute-1.amazonaws.com]

客户端能看到故障吗?我以前见过这种类型的错误,作为正常的SSL协商的一部分,即从用户的角度来看,它“工作”了。 - Taylor
使用Java 6时,客户端hello会回退到SSLv2,而使用Java 7时,它似乎会使用TLSv1进行通信。我相信在Java 6下的JSSE并不正式支持SSL V2,但我需要进行研究。 - Bruno Grieder
1
使用 openssl s_client -connect servername:443 命令,您可以调试服务器支持的协议,并通过强制标志如 ssl2ssl3 等进行测试,详见此处 - Bruno Grieder
@Taylor 是的,在Java 6中请求每次都失败,连接也无法成功。 - Sean Owen
@BGR 是的,我已经确认它只支持 tls1ssl3,这是我预期的。现在我正在研究如何让 Java 6 不使用 SSLv2。 - Sean Owen
@SeanOwen - 我们如何调试这个问题并找出原因?也就是说,达到与你相同的解决方案? - MasterJoe
5个回答

18

最终,Bruno的回答是正确的。这可以通过https.protocols系统属性最轻松地控制。这就是你能够控制工厂方法返回结果的方式。例如,将其设置为"TLSv1"。


2
当您想要使用两个协议而不是一个时,这会导致问题。例如,SSLv3TLSv1。如果您尝试连接SSLv3或TLSv1,则此-Dhttps.protocols=TLSv1,SSLv3将导致异常。 - Dragon
1
@Dragon 真的吗?是哪些异常?为什么? - user207421

6
似乎在Java 6的调试日志中,请求以SSLv2格式发送。

主要,写入:SSLv2客户端hello消息,长度=110

这在Java 7中默认情况下未被启用。 更改客户端使用SSLv3及以上版本可避免此类互操作性问题。 查找Java 7和Java 6中JSSE提供程序的差异。请参阅Java 7Java 6中的JSSE提供程序的区别。

是的,这是很好的证据,确实SSLv2似乎是问题所在。这也导致了我的答案,尽管@EJP是正确的,最好找到一种明确关闭SSLv2的方法。 - Sean Owen

4

在客户端SSLSocket或HttpsURLConnection中将"SSLv2ClientHello"从启用的协议中移除。


我认为这是完全正确的,但我不确定该怎么做。我正在为平台构建一个SSLSocketFactorySSLContext.createSSLEngine() 创建了一个具有设置(似乎不能删除)协议的方法的SSLEngine。但我认为这对SSLSocketFactory没有帮助。 SSLContext 也只允许您请求一个协议,并且无法禁用涉及其他协议的内容(?)。如果您恰好知道如何操作,请告诉我。 - Sean Owen
我不相信有这样的方法。至少我在SSLContextSSLSocketFactory中没有看到它。我认为SSLEngine也没有帮助,因为框架将会自己创建。我会继续寻找。 - Sean Owen
6
如果你正在使用HttpsURLConnection,那么https.protocols系统属性可以帮助你设置启用的加密套件。 - Bruno
1
确认在Java 6中使用“-Dhttps.protocols=TLSv1”可以使其正常工作。 - Sean Owen
@SeanOwen 如果你正在创建一个套接字工厂,你可以获取套接字或自己创建它,并在返回之前调用这些方法。 - user207421
显示剩余3条评论

3

将服务器参数从-Dhttps.protocols=SSLv3更改为-Dhttps.protocols=TLSv1,SSLv3


1

Do it like this:

SSLSocket socket = (SSLSocket) sslFactory.createSocket(host, port);
socket.setEnabledProtocols(new String[]{"SSLv3", "TLSv1"});

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接