收到致命警报:handshake_failure通过SSLHandshakeException
我有一个授权SSL连接的问题。 我创建了使用客户端授权SSL证书连接到外部服务器的Struts Action。 在我的行动中,我试图发送一些数据到银行服务器,但没有任何运气,因为我从服务器的结果有以下错误:
error: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
我的方法从我的操作类发送数据到服务器
//Getting external IP from host URL whatismyip = new URL("http://automation.whatismyip.com/n09230945.asp"); BufferedReader inIP = new BufferedReader(new InputStreamReader(whatismyip.openStream())); String IPStr = inIP.readLine(); //IP as a String Merchant merchant; System.out.println("amount: " + amount + ", currency: " + currency + ", clientIp: " + IPStr + ", description: " + description); try { merchant = new Merchant(context.getRealPath("/") + "merchant.properties"); } catch (ConfigurationException e) { Logger.getLogger(HomeAction.class.getName()).log(Level.INFO, "message", e); System.err.println("error: " + e.getMessage()); return ERROR; } String result = merchant.sendTransData(amount, currency, IPStr, description); System.out.println("result: " + result); return SUCCESS;
我的merchant.properties文件:
bank.server.url=https://-servernameandport-/ https.cipher=-cipher- keystore.file=-key-.jks keystore.type=JKS keystore.password=-password- ecomm.server.version=2.0 encoding.source=UTF-8 encoding.native=UTF-8
我第一次以为这是一个证书问题,我把它从.pfx转换成.jks,但是我有同样的错误,没有任何改变。
握手失败可能是由于各种原因发生的:
- 客户端和服务器使用的密码套件不兼容。 这将要求客户端使用(或启用)服务器支持的密码套件。
- 不兼容的SSL版本(服务器可能只接受TLS v1,而客户端只能使用SSL v3)。 同样,客户端可能必须确保它使用兼容版本的SSL / TLS协议。
- 服务器证书的信任路径不完整; 服务器的证书可能不被客户端信任。 这通常会导致更详细的错误,但是这是很有可能的。 通常解决方法是将服务器的CA证书导入到客户端的信任存储中。
- 证书颁发给不同的域。 再次,这将导致一个更详细的消息,但我会在这里说明这个问题的原因。 在这种情况下的解决方案将得到服务器(它似乎不是你的)使用正确的证书。
由于无法确定底层故障,因此最好打开-Djavax.net.debug=all
标志以启用对已建立SSL连接的调试。 调试开启后,您可以确定握手中的哪个活动失败。
更新
根据现在可用的详细信息,看起来问题是由于颁发给服务器的证书和根CA之间的证书信任路径不完整所致。 在大多数情况下,这是因为根CA的证书在信任存储中不存在,导致证书信任路径不能存在的情况; 证书本质上不受客户端的信任。 浏览器可以显示一个警告,以便用户可以忽略这一点,但SSL客户端(例如HttpsURLConnection类或任何HTTP客户端库,如Apache HttpComponents客户端 )的情况也是如此。
大多数这些客户端类/库将依靠JVM使用的信任存储进行证书验证。 在大多数情况下,这将是JRE_HOME / lib / security目录中的cacerts
文件。 如果使用JVM系统属性javax.net.ssl.trustStore
指定了信任存储的位置,则该路径中的存储通常是客户端库使用的存储。 如果您有疑问,请查看您的Merchant
类,并找出它用于建立连接的类/库。
将服务器的证书颁发CA添加到此信任存储应该解决此问题。 您可以参考我的答案,以获取有关此目的的工具的相关问题 ,但Java keytool实用程序已足够用于此目的。
警告 :信任存储本质上是您信任的所有CA的列表。 如果您放入的证书不属于您不信任的CA,那么如果私钥可用,则可以解密到该实体颁发的证书的站点的SSL / TLS连接。
更新#2:了解JSSE跟踪的输出
JVM使用的密钥库和信任库通常在开始时列出,如下所示:
keyStore is : keyStore type is : jks keyStore provider is : init keystore init keymanager of type SunX509 trustStore is: C:\Java\jdk1.6.0_21\jre\lib\security\cacerts trustStore type is : jks trustStore provider is :
如果使用了错误的信任库,那么您需要将服务器的证书重新导入到正确的证书库中,或者重新配置服务器以使用所列的服务器(不建议如果您有多个JVM,并且所有服务器都用于不同的需要)。
如果您想验证信任证书列表是否包含所需的证书,则会有一个相同的部分,开头为:
adding as trusted cert: Subject: CN=blah, O=blah, C=blah Issuer: CN=biggerblah, O=biggerblah, C=biggerblah Algorithm: RSA; Serial number: yadda Valid from SomeDate until SomeDate
您需要查看服务器的CA是否是主题。
握手过程将有一些重要的条目(你需要知道SSL来详细了解它们,但是为了调试当前的问题,只需要知道sendHke_failure通常在ServerHello中被报告就足够了。
1. ClientHello
连接初始化时会报告一系列条目。 客户端在SSL / TLS连接设置中发送的第一条消息是ClientHello消息,通常在日志中报告为:
*** ClientHello, TLSv1 RandomCookie: GMT: 1291302508 bytes = { some byte array } Session ID: {} Cipher Suites: [SSL_RSA_WITH_RC4_128_MD5, SSL_RSA_WITH_RC4_128_SHA, TLS_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, TLS_DHE_DSS_WITH_AES_128_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] Compression Methods: { 0 } ***
请注意使用的密码套件。 这可能必须与您的merchant.properties文件中的条目一致 ,因为银行的图书馆可能会使用相同的约定。 如果使用的约定不同,则不用担心,因为如果密码套件不兼容,ServerHello将声明这一点。
2. ServerHello
服务器响应一个ServerHello,这将指示连接设置是否可以继续。 日志中的条目通常是以下类型:
*** ServerHello, TLSv1 RandomCookie: GMT: 1291302499 bytes = { some byte array} Cipher Suite: SSL_RSA_WITH_RC4_128_SHA Compression Method: 0 ***
注意它选择的密码套件; 这是最适合服务器和客户端的套件。 通常,如果有错误,密码套件不会被指定。 服务器的证书(以及可选的整个链)由服务器发送,并且可以在条目中找到:
*** Certificate chain chain [0] = [ [ Version: V3 Subject: CN=server, O=server's org, L=server's location, ST =Server's state, C=Server's country Signature Algorithm: SHA1withRSA, OID = some identifer .... the rest of the certificate ***
如果证书验证成功,您将找到类似于以下内容的条目:
Found trusted certificate: [ [ Version: V1 Subject: OU=Server's CA, O="Server's CA's company name", C=CA's country Signature Algorithm: SHA1withRSA, OID = some identifier
上述步骤之一不会成功,导致handshake_failure,握手通常在这个阶段完成(不是真的,但握手的后续阶段通常不会导致握手失败)。 你需要找出哪一步失败了,然后发布相应的消息作为问题的更新(除非你已经理解了这个消息,而且你知道如何解决这个消息)。
我不认为这解决了问题的第一个提问者,但谷歌来这里的答案:
在更新51上,默认情况下,java 1.8禁止使用[1] RC4密码,正如我们在“发行注记”页面上所看到的:
错误修复: 禁止RC4密码套件
RC4现在被认为是一个妥协的密码。
已经从Oracle JSSE实现中的客户端和服务器默认启用的密码套件列表中删除了RC4密码套件。 这些密码套件仍然可以通过
SSLEngine.setEnabledCipherSuites()
和SSLSocket.setEnabledCipherSuites()
方法启用。 参见JDK-8077109(不公开)。
如果你的服务器有一个强烈的偏好这个密码(或者只使用这个密码),这可能会触发java的handshake_failure
。
您可以测试连接到启用RC4密码的服务器(首先,尝试不enabled
参数来查看是否触发handshake_failure
,然后设置enabled
:
import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import java.io.*; import java.util.Arrays; /** Establish a SSL connection to a host and port, writes a byte and * prints the response. See * http://confluence.atlassian.com/display/JIRA/Connecting+to+SSL+services */ public class SSLRC4Poke { public static void main(String[] args) { String[] cyphers; if (args.length < 2) { System.out.println("Usage: "+SSLRC4Poke.class.getName()+" <host> <port> enable"); System.exit(1); } try { SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault(); SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket(args[0], Integer.parseInt(args[1])); cyphers = sslsocketfactory.getSupportedCipherSuites(); if (args.length ==3){ sslsocket.setEnabledCipherSuites(new String[]{ "SSL_DH_anon_EXPORT_WITH_RC4_40_MD5", "SSL_DH_anon_WITH_RC4_128_MD5", "SSL_RSA_EXPORT_WITH_RC4_40_MD5", "SSL_RSA_WITH_RC4_128_MD5", "SSL_RSA_WITH_RC4_128_SHA", "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", "TLS_ECDHE_RSA_WITH_RC4_128_SHA", "TLS_ECDH_ECDSA_WITH_RC4_128_SHA", "TLS_ECDH_RSA_WITH_RC4_128_SHA", "TLS_ECDH_anon_WITH_RC4_128_SHA", "TLS_KRB5_EXPORT_WITH_RC4_40_MD5", "TLS_KRB5_EXPORT_WITH_RC4_40_SHA", "TLS_KRB5_WITH_RC4_128_MD5", "TLS_KRB5_WITH_RC4_128_SHA" }); } InputStream in = sslsocket.getInputStream(); OutputStream out = sslsocket.getOutputStream(); // Write a test byte to get a reaction :) out.write(1); while (in.available() > 0) { System.out.print(in.read()); } System.out.println("Successfully connected"); } catch (Exception exception) { exception.printStackTrace(); } } }
1 – https://www.java.com/en/download/faq/release_changes.xml
安装Java加密扩展(JCE)无限强度( 对于JDK8,JDK8 )将修复此错误。 解压缩文件并按照自述进行安装。
当客户端需要提交证书时,也会发生这种情况。 服务器列出证书链后,可能会发生以下情况:
3.证书请求服务器将从客户端发出证书请求。 该请求将列出服务器接受的所有证书。
*** CertificateRequest Cert Types: RSA Cert Authorities: <CN=blah, OU=blah, O=blah, L=blah, ST=blah, C=blah> <CN=yadda, DC=yadda, DC=yadda> <CN=moreblah, OU=moreblah, O=moreblah, C=moreblah> <CN=moreyada, OU=moreyada, O=moreyada, C=moreyada> ... the rest of the request *** ServerHelloDone
4.客户端证书链这是客户端发送到服务器的证书。
*** Certificate chain chain [0] = [ [ Version: V3 Subject: EMAILADDRESS=client's email, CN=client, OU=client's ou, O=client's Org, L=client's location, ST=client's state, C=client's Country Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5 ... the rest of the certificate *** ClientKeyExchange, RSA PreMasterSecret, TLSv1 ... key exchange info
如果链中没有证书并且服务器需要证书,则会在此处收到握手错误。 可能的原因是没有找到证书的路径。
5.证书验证客户端要求服务器验证证书
*** CertificateVerify ... payload of verify check
只有在您发送证书时才会执行此步骤。
6.完成服务器将以验证响应进行响应
*** Finished verify_data: { 345, ... }
我尝试使用JDK 1.7时遇到此错误。 当我将JDK升级到jdk1.8.0_66时,所有工作都正常。
所以这个问题的最简单的解决方案可能是 – 升级您的JDK ,它可以开始工作。
握手失败可能是一个有问题的TLSv1协议实现。
在我们的例子中这有助于Java 7:
java -Dhttps.protocols=TLSv1.2,TLSv1.1,TLSv1
jvm将按此顺序进行协商。 具有最新更新的服务器将执行1.2,有问题的将下降到v1,并与java 7中的类似v1一起使用。
假设您使用正确的SSL / TLS协议,正确配置了您的keyStore
和trustStore
,并确认证书本身不存在任何问题,则可能需要加强安全算法 。
正如Vineet的回答中所提到的 ,您收到此错误的一个可能原因是由于使用了不兼容的密码套件。 通过使用Java Cryptography Extension(JCE)中提供的更新我的JDK security
文件夹中的local_policy
和US_export_policy
jar,我能够成功完成握手。
我是一个TLS
版本不兼容的错误。
以前它是TLSV1.2
我改变它TLSV1.2
这解决了我的问题。
在我的情况下,从jdk 1.7.0_71移动到jdk 1.8.0_60修复了错误
我有一个类似的问题; 升级到Apache HTTPClient 4.5.3修复它。
我今天遇到同样的问题与OkHttp客户端获取基于https的网址。 这是由服务器端和客户端之间的Https协议版本和Cipher方法不匹配造成的 。
1)检查您的网站https协议版本和密码方法。
openssl>s_client -connect your_website.com:443 -showcerts
您将获得许多详细信息,关键信息如下所示:
SSL-Session: Protocol : TLSv1 Cipher : RC4-SHA
2)配置你的http客户端,例如,在OkHttp客户端的情况下:
@Test() public void testHttpsByOkHttp() { ConnectionSpec spec = new ConnectionSpec.Builder(ConnectionSpec.MODERN_TLS) .tlsVersions(TlsVersion.TLS_1_0) //protocol version .cipherSuites( CipherSuite.TLS_RSA_WITH_RC4_128_SHA, //cipher method CipherSuite.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, CipherSuite.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, CipherSuite.TLS_DHE_RSA_WITH_AES_128_GCM_SHA256) .build(); OkHttpClient client = new OkHttpClient(); client.setConnectionSpecs(Collections.singletonList(spec)); Request request = new Request.Builder().url("https://your_website.com/").build(); try { Response response = client.newCall(request).execute(); if(response.isSuccessful()){ logger.debug("result= {}", response.body().string()); } } catch (IOException e) { e.printStackTrace(); } }
这将得到我们想要的。
免责声明:我不知道这个答案是否对许多人有用,只是分享,因为它可能。
我在使用Parasoft SOATest发送请求XML(SOAP)时遇到了这个错误。
问题是我在添加证书和验证后从下拉列表中选择了错误的别名 。
我正在使用com.google.api http客户端。 当我与公司内部网站沟通时,我错误地使用了https而不是http。
main, READ: TLSv1.2 Alert, length = 2 main, RECV TLSv1.2 ALERT: fatal, handshake_failure main, called closeSocket() main, handling exception: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure main, IOException in getSession(): javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure main, called close() main, called closeInternal(true) 262 [main] DEBUG org.apache.http.impl.conn.DefaultClientConnection - Connection shut down main, called close() main, called closeInternal(true) 263 [main] DEBUG org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager - Released connection is not reusable. 263 [main] DEBUG org.apache.http.impl.conn.tsccm.ConnPoolByRoute - Releasing connection [HttpRoute[{s}->https://<I-replaced>]][null] 263 [main] DEBUG org.apache.http.impl.conn.tsccm.ConnPoolByRoute - Notifying no-one, there are no waiting threads Exception in thread "main" javax.net.ssl.SSLPeerUnverifiedException: peer not authenticated at sun.security.ssl.SSLSessionImpl.getPeerCertificates(SSLSessionImpl.java:431) at org.apache.http.conn.ssl.AbstractVerifier.verify(AbstractVerifier.java:128) at org.apache.http.conn.ssl.SSLSocketFactory.connectSocket(SSLSocketFactory.java:339) at org.apache.http.impl.conn.DefaultClientConnectionOperator.openConnection(DefaultClientConnectionOperator.java:123) at org.apache.http.impl.conn.AbstractPoolEntry.open(AbstractPoolEntry.java:147) at org.apache.http.impl.conn.AbstractPooledConnAdapter.open(AbstractPooledConnAdapter.java:108) at org.apache.http.impl.client.DefaultRequestDirector.execute(DefaultRequestDirector.java:415) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:641) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:576) at org.apache.http.impl.client.AbstractHttpClient.execute(AbstractHttpClient.java:554) at com.google.api.client.http.apache.ApacheHttpRequest.execute(ApacheHttpRequest.java:67) at com.google.api.client.http.HttpRequest.execute(HttpRequest.java:960)