收到致命警报:handshake_failure通过SSLHandshakeException

Den*_*ees 118 java ssl ssl-certificate sslhandshakeexception

我有授权SSL连接的问题.我创建了Struts Action,它通过Client Authorized SSL证书连接到外部服务器.在我的行动中我试图将一些数据发送到银行服务器但没有任何运气,因为我从服务器得到以下错误的结果:

error: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
Run Code Online (Sandbox Code Playgroud)

我的Action类从我的Action类发送数据到服务器

//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;
Run Code Online (Sandbox Code Playgroud)

我的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
Run Code Online (Sandbox Code Playgroud)

我第一次认为这是证书问题,我将它从.pfx转换为.jks,但我有相同的错误,没有任何更改.

Vin*_*lds 231

由于各种原因,可能发生握手失败:

  • 客户端和服务器使用的密码套件不兼容.这将要求客户端使用(或启用)服务器支持的密码套件.
  • 正在使用的SSL版本不兼容(服务器可能只接受TLS v1,而客户端只能使用SSL v3).同样,客户端可能必须确保它使用兼容版本的SSL/TLS协议.
  • 服务器证书的信任路径不完整; 客户端可能不信任服务器的证书.这通常会导致更详细的错误,但很有可能.通常,修复方法是将服务器的CA证书导入客户端的信任库.
  • 证书是针对不同的域颁发的.同样,这会导致更详细的消息,但我会在此处说明修复,以防这是原因.在这种情况下,解决方案是使用正确的证书获取服务器(它似乎不属于您).

由于无法精确定位基础故障,因此最好打开 -Djavax.net.debug=all标志以启用已建立的SSL连接的调试.打开调试后,您可以查明握手中的哪些活动失败.

更新

根据现有的详细信息,问题似乎是由于颁发给服务器的证书与根CA之间的证书信任路径不完整.在大多数情况下,这是因为信任存储中缺少根CA的证书,导致证书信任路径不存在的情况; 证书基本上不受客户信任.浏览器可以显示警告,以便用户可以忽略这一点,但SSL客户端(例如HttpsURLConnection类或任何HTTP客户端库,如Apache HttpComponents Client)也不会这样.

大多数这些客户端类/库将依赖于JVM用于证书验证的信任存储.在大多数情况下,这将是cacertsJRE_HOME/lib/security目录中的文件.如果已使用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 : 
Run Code Online (Sandbox Code Playgroud)

如果使用了错误的信任库,那么您需要将服务器的证书重新导入到正确的信任库中,或者重新配置服务器以使用列出的服务器(如果您有多个JVM,则不建议使用它们,并且所有这些都用于不同的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
Run Code Online (Sandbox Code Playgroud)

您需要查找服务器的CA是否为主题.

握手过程将有一些显着的条目(您需要知道SSL以详细了解它们,但是为了调试当前问题,知道通常在ServerHello中报告handshake_failure就足够了.

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 }
***
Run Code Online (Sandbox Code Playgroud)

请注意使用的密码套件.这可能必须与您的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
***
Run Code Online (Sandbox Code Playgroud)

注意它选择的密码套件; 这是服务器和客户端都可以使用的最佳套件.通常,如果出现错误,则不指定密码套件.服务器的证书(以及可选的整个链)由服务器发送,并在条目中找到:

*** 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
***
Run Code Online (Sandbox Code Playgroud)

如果证书验证成功,您将找到类似于以下内容的条目:

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
Run Code Online (Sandbox Code Playgroud)

上述步骤之一不会成功,导致handshake_failure,因为握手通常在此阶段完成(不是真的,但握手的后续阶段通常不会导致握手失败).您需要确定哪个步骤失败,并将相应的消息发布为问题的更新(除非您已经理解了该消息,并且您知道如何解决它).

  • 默认值通常是`changeit`.除非改变了. (5认同)
  • 好吧,可能是依赖于cacerts。但是只有在您了解网络调试的输出后才能确定这一点。如果你想检查这个,你需要使用`keytool -list -v -keystore $JAVA_HOME/jre/lib/security/cacerts` 命令来打印内容。然后验证 cacerts 中的证书是否与银行证书的 CA 匹配。 (2认同)

小智 20

安装Java Cryptography Extension(JCE)Unlimited Strength(适用于JDK7 | 用于JDK8)将修复此错误.解压缩文件并按照自述文件进行安装.


Bri*_*rig 15

当客户端需要提供证书时,也会发生这种情况.服务器列出证书链后,可能会发生以下情况:

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
Run Code Online (Sandbox Code Playgroud)

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 
Run Code Online (Sandbox Code Playgroud)

如果链中没有证书且服务器需要证书,那么您将在此处获得握手错误.可能的原因是找不到证书的路径.

5.证书验证 客户端要求服务器验证证书

*** CertificateVerify
... payload of verify check
Run Code Online (Sandbox Code Playgroud)

只有在您发送证书时才会执行此步骤.

6.完成 服务器将响应验证响应

*** Finished
verify_data:  { 345, ... }
Run Code Online (Sandbox Code Playgroud)


mot*_*bói 15

我不认为这解决了第一个提问者的问题,但是对于来到这里寻求答案的googlers:


在更新51上,默认情况下java 1.8禁止[1] RC4密码,我们可以在发行说明页面上看到:

错误修复:禁止RC4密码套件

RC4现在被视为受损密码.

已从Oracle JSSE实现中的客户端和服务器默认启用的密码套件列表中删除RC4密码套件.这些密码套件仍然可以通过SSLEngine.setEnabledCipherSuites()SSLSocket.setEnabledCipherSuites()方法启用.请参阅JDK-8077109(非公开).

如果您的服务器对此密码具有强烈的偏好(或仅使用此密码),则可以触发handshake_failureon java.

您可以测试连接到启用RC4密码的服务器(首先,尝试不带enabled参数查看是否触发a 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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

1 - https://www.java.com/en/download/faq/release_changes.xml


Bel*_*izy 14

握手失败可能是一个错误的TLSv1协议实现.

在我们的例子中,这有助于java 7:

java -Dhttps.protocols=TLSv1.2,TLSv1.1,TLSv1 
Run Code Online (Sandbox Code Playgroud)

jvm将按此顺序进行谈判.具有最新更新的服务器将执行1.2,有缺陷的服务器将下载到v1,并且与java 7中的类似v1一起使用.

  • 这对我有帮助。有我的ClientHello,但没有服务器,结束得相当突然。这在 Java 7 上为我解决了这个问题。非常感谢。 (2认同)

小智 8

我尝试使用JDK 1.7时遇到此错误.当我将JDK升级到jdk1.8.0_66时,一切正常.

因此,解决此问题的最简单方法是 - 升级JDK并开始运行良好.

  • 尼斯.最简单的解决方案是升级JDK?:D你知道这取决于环境的复杂程度吗?假设亚马逊运行JDK 7并且现在需要突然升级到JDK 8 ...很好! (3认同)
  • 一个简单的次要版本升级为我解决了这个问题..从 JDK 11.0.1 到 11.0.6 (3认同)

hee*_*eez 5

假设您使用正确的 SSL/TLS 协议,正确配置您的keyStoretrustStore,并确认证书本身不存在任何问题,您可能需要加强您的安全算法

正如Vineet 的回答中所述,您收到此错误的一个可能原因是使用了不兼容的密码套件。通过使用Java Cryptography Extension (JCE) 中提供的内容更新JDK文件夹中的mylocal_policyUS_export_policyjars ,我能够成功完成握手。security


小智 5

在我的情况下,证书被导入,错误仍然存​​在,通过System.setProperty("https.protocols", "TLSv1.2,TLSv1.1,SSLv3");在连接之前添加来解决这个问题