Posts Java加密套件强度限制引起的SSL handshake_failure
Post
Cancel

Java加密套件强度限制引起的SSL handshake_failure

今天为客户解决了一个奇葩的SSL问题。通过Java代码使用HttpURLConnection去连接https系统时候总是报错handshake_failure。而使用浏览器访问一切正常。记录下诊断的过程。

HttpURLConnection的调用非常简单。

1
2
3
4
5
6
7
8
9
10
11
HttpURLConnection connection =
		(HttpURLConnection)m_url.openConnection();

connection.setRequestMethod("GET");
connection.setAllowUserInteraction(false);
connection.setDefaultUseCaches(false);
connection.setDoInput(true);
connection.setDoOutput(false);
connection.setInstanceFollowRedirects(true);
connection.setUseCaches(false);
connection.connect(); <----- handshake error

错误也很抽象。

1
2
3
4
5
6
7
8
9
10
11
12
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
	at sun.security.ssl.Alerts.getSSLException(Unknown Source)
	at sun.security.ssl.Alerts.getSSLException(Unknown Source)
	at sun.security.ssl.SSLSocketImpl.recvAlert(Unknown Source)
	at sun.security.ssl.SSLSocketImpl.readRecord(Unknown Source)
	at sun.security.ssl.SSLSocketImpl.performInitialHandshake(Unknown Source)
	at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
	at sun.security.ssl.SSLSocketImpl.startHandshake(Unknown Source)
	at sun.net.www.protocol.https.HttpsClient.afterConnect(Unknown Source)
	at sun.net.www.protocol.https.AbstractDelegateHttpsURLConnection.connect(Unknown Source)
	at sun.net.www.protocol.https.HttpsURLConnectionImpl.connect(Unknown Source)
	at com.agile.common.HttpReader.getInputStream(HttpReader.java:76)

初步怀疑本地的JRE证书信任文件中没有包含对方服务器的根证书。

1
keytool -list -v -keystore "C:\Java\jdk1.8.0_152\jre\lib\security\cacerts" >store.txt

检查发现,根证书和中间证书都存在,信任链没有问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
## 中间证书
Alias name: comodorsaca [jdk]
Creation date: 25 Aug, 2016
Entry type: trustedCertEntry

Owner: CN=COMODO RSA Certification Authority, O=COMODO CA Limited, L=Salford, ST=Greater Manchester, C=GB
Issuer: CN=COMODO RSA Certification Authority, O=COMODO CA Limited, L=Salford, ST=Greater Manchester, C=GB
Serial number: 4caaf9cadb636fe01ff74ed85b03869d
Valid from: Tue Jan 19 05:30:00 IST 2010 until: Tue Jan 19 05:29:59 IST 2038
Certificate fingerprints:
	 MD5:  1B:31:B0:71:40:36:CC:14:36:91:AD:C4:3E:FD:EC:18
	 SHA1: AF:E5:D2:44:A8:D1:19:42:30:FF:47:9F:E2:F8:97:BB:CD:7A:8C:B4
	 SHA256: 52:F0:E1:C4:E5:8E:C6:29:29:1B:60:31:7F:07:46:71:B8:5D:7E:A8:0D:5B:07:27:34:63:53:4B:32:B4:02:34
	 Signature algorithm name: SHA384withRSA
	 Version: 3

## 根证书
Alias name: addtrustqualifiedca [jdk]
Creation date: 25 Aug, 2016
Entry type: trustedCertEntry

Owner: CN=AddTrust Qualified CA Root, OU=AddTrust TTP Network, O=AddTrust AB, C=SE
Issuer: CN=AddTrust Qualified CA Root, OU=AddTrust TTP Network, O=AddTrust AB, C=SE
Serial number: 1
Valid from: Tue May 30 16:14:50 IST 2000 until: Sat May 30 16:14:50 IST 2020
Certificate fingerprints:
	 MD5:  27:EC:39:47:CD:DA:5A:AF:E2:9A:01:65:21:A9:4C:BB
	 SHA1: 4D:23:78:EC:91:95:39:B5:00:7F:75:8F:03:3B:21:1E:C5:4D:8B:CF
	 SHA256: 80:95:21:08:05:DB:4B:BC:35:5E:44:28:D8:FD:6E:C2:CD:E3:AB:5F:B9:7A:99:42:98:8E:B8:F4:DC:D0:60:16
	 Signature algorithm name: SHA1withRSA
	 Version: 3

那就去抓SSL包吧。

请求包:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
Secure Sockets Layer
	TLSv1.2 Record Layer: Handshake Protocol: Client Hello
		Content Type: Handshake (22)
		Version: TLS 1.2 (0x0303)
		Length: 229
		Handshake Protocol: Client Hello
			Handshake Type: Client Hello (1)
			Length: 225
			Version: TLS 1.2 (0x0303)
			Random: 5af01897734438e606e3342398727fe8a539522a2ef0dfa6...
				GMT Unix Time: May  7, 2018 17:12:55.000000000 中国标准时间
				Random Bytes: 734438e606e3342398727fe8a539522a2ef0dfa6b698a1eb...
			Session ID Length: 0
			Cipher Suites Length: 58
			Cipher Suites (29 suites)
				Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 (0xc023)
				Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 (0xc027)
				Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA256 (0x003c)
				Cipher Suite: TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 (0xc025)
				Cipher Suite: TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 (0xc029)
				Cipher Suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 (0x0067)
				Cipher Suite: TLS_DHE_DSS_WITH_AES_128_CBC_SHA256 (0x0040)
				Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (0xc009)
				Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013)
				Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA (0x002f)
				Cipher Suite: TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA (0xc004)
				Cipher Suite: TLS_ECDH_RSA_WITH_AES_128_CBC_SHA (0xc00e)
				Cipher Suite: TLS_DHE_RSA_WITH_AES_128_CBC_SHA (0x0033)
				Cipher Suite: TLS_DHE_DSS_WITH_AES_128_CBC_SHA (0x0032)
				Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b)
				Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
				Cipher Suite: TLS_RSA_WITH_AES_128_GCM_SHA256 (0x009c)
				Cipher Suite: TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02d)
				Cipher Suite: TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 (0xc031)
				Cipher Suite: TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 (0x009e)
				Cipher Suite: TLS_DHE_DSS_WITH_AES_128_GCM_SHA256 (0x00a2)
				Cipher Suite: TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA (0xc008)
				Cipher Suite: TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA (0xc012)
				Cipher Suite: TLS_RSA_WITH_3DES_EDE_CBC_SHA (0x000a)
				Cipher Suite: TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA (0xc003)
				Cipher Suite: TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA (0xc00d)
				Cipher Suite: TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA (0x0016)
				Cipher Suite: TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA (0x0013)
				Cipher Suite: TLS_EMPTY_RENEGOTIATION_INFO_SCSV (0x00ff)

响应包:

1
2
3
4
5
6
7
8
Secure Sockets Layer
	TLSv1.2 Record Layer: Alert (Level: Fatal, Description: Handshake Failure)
		Content Type: Alert (21)
		Version: TLS 1.2 (0x0303)
		Length: 2
		Alert Message
			Level: Fatal (2)
			Description: Handshake Failure (40)

客户端发送了Hello,服务器翻了个白眼直接拒绝了。这个响应包,没有任何线索。但是能明确一点就是客户端和服务器都通过TLS 1.2协议协商。不用再怀疑协议版本问题了。

联想到浏览器访问对方https系统是成功的,再次抓取浏览器请求和响应包。发现了一点有用的线索。

浏览器的请求包同样采用TLS 1.2协议,但是加密套件 (Cipher Suites)和前面的差异很大,提供了17个可选加密套件。前面的包提供了29个可选列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
Secure Sockets Layer
	TLSv1.2 Record Layer: Handshake Protocol: Client Hello
		Content Type: Handshake (22)
		Version: TLS 1.0 (0x0301)
		Length: 512
		Handshake Protocol: Client Hello
			Handshake Type: Client Hello (1)
			Length: 508
			Version: TLS 1.2 (0x0303)
			Random: 7179caea804a5281b411adca67c11883f616e13e13756d0d...
				GMT Unix Time: May  1, 2030 03:20:10.000000000 中国标准时间
				Random Bytes: 804a5281b411adca67c11883f616e13e13756d0d5764d936...
			Session ID Length: 32
			Session ID: 324885cac7ecf4dd09e38acdd3e45ceb2e0c5e248b31d267...
			Cipher Suites Length: 34
			Cipher Suites (17 suites)
				Cipher Suite: Reserved (GREASE) (0xcaca)
				Cipher Suite: TLS_AES_128_GCM_SHA256 (0x1301)
				Cipher Suite: TLS_AES_256_GCM_SHA384 (0x1302)
				Cipher Suite: TLS_CHACHA20_POLY1305_SHA256 (0x1303)
				Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (0xc02b)
				Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)
				Cipher Suite: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (0xc02c)
				Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (0xc030)
				Cipher Suite: TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca9)
				Cipher Suite: TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (0xcca8)
				Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (0xc013)
				Cipher Suite: TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (0xc014)
				Cipher Suite: TLS_RSA_WITH_AES_128_GCM_SHA256 (0x009c)
				Cipher Suite: TLS_RSA_WITH_AES_256_GCM_SHA384 (0x009d)
				Cipher Suite: TLS_RSA_WITH_AES_128_CBC_SHA (0x002f)
				Cipher Suite: TLS_RSA_WITH_AES_256_CBC_SHA (0x0035)
				Cipher Suite: TLS_RSA_WITH_3DES_EDE_CBC_SHA (0x000a)

接着看浏览器接收到的tcp包Server Hello。服务器通过TLS 1.2协议协商,告诉浏览器它要使用 TLS_RSA_WITH_AES_256_GCM_SHA384 加密套件作为后续数据的加密算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Secure Sockets Layer
	TLSv1.2 Record Layer: Handshake Protocol: Server Hello
		Content Type: Handshake (22)
		Version: TLS 1.2 (0x0303)
		Length: 81
		Handshake Protocol: Server Hello
			Handshake Type: Server Hello (2)
			Length: 77
			Version: TLS 1.2 (0x0303)
			Random: f1649150aeb3366381e54392bfdb8f49ae8ead9f47dbcb1f...
				GMT Unix Time: May  3, 2098 04:10:56.000000000 中国标准时间
				Random Bytes: aeb3366381e54392bfdb8f49ae8ead9f47dbcb1fc13f95a4...
			Session ID Length: 32
			Session ID: 264c4906bbd0fd2b8f94f66ea2992433b82690fb7fb844d7...
			Cipher Suite: TLS_RSA_WITH_AES_256_GCM_SHA384 (0x009d)
			Compression Method: null (0)
			Extensions Length: 5
			Extension: renegotiation_info (len=1)
				Type: renegotiation_info (65281)
				Length: 1
				Renegotiation Info extension
					Renegotiation info extension length: 0

马上注意到在前面的请求包中,29个加密套件里都没有TLS_RSA_WITH_AES_256_GCM_SHA384。似乎问题就在这里,就是通过java代码访问https服务器时,候选的加密套件中没有服务器希望的TLS_RSA_WITH_AES_256_GCM_SHA384。

真的是这样吗?为了弄清服务器和本地JRE是否存在加密套件不匹配,还得使用有足够说服力的诊断方法来验证。

首先搬上OPENSSL来调试。

1
openssl s_client -connect server.mycompany.com:443

诊断发现,服务器确实需要AES256-GCM-SHA384,这是一个很长长度的强加密,一般128位长度加密很牛逼了。

1
2
3
4
5
6
7
8
9
10
New, TLSv1.2, Cipher is AES256-GCM-SHA384
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
	Protocol  : TLSv1.2
	Cipher    : AES256-GCM-SHA384
	Session-ID: 5BFE44E0BE1248156266BE6947FA113C1035DDDC3A3BD1888940EF8257CAA18C

对Java代码也来一次调试,确认它所支持的所有加密套件。

1
-Dssl.debug=true -Djavax.net.debug=all

返回结果确实如此,根本就不存在TLS_RSA_WITH_AES_256_GCM_SHA384,也不存在基于AES256-GCM-SHA384的加密方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Cipher Suites: [
	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
	]

问题找到了。解决方法看来现在只能回到JRE本身的加密授权问题上来了。

Java支持所有的加密套件,但是对于发行的JDK版本,它默认做了很多加密长度限制的裁剪,就是只出口强度低的加密,这是美国政府对于安全软件的强制性规定。但Oracle允许下载强加密的未限制版本,其实就是几个授权属性文件,因为源代码都在发行的JDK中。

当前问题发生在JDK1.8中,所以可以去官网下载一个压缩包叫做 “Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy Files 8”。

对于JDK1.8版本但是低于1.8.0_151版本的JDK,将下载的包里的两个文件直接覆盖到本地 Java\jre\lib\security\

1
2
local_policy.jar
US_export_policy.jar

1.8.0_151和以后的版本,无需下载任何文件,只要修改Java\jre\lib\security\java.security文件,修改这一行注释并启用就可以了。

1
crypto.policy=unlimited
This post is licensed under CC BY 4.0 by the author.