新聞中心
簡介
最近,我們一個多機(jī)房部署的服務(wù),調(diào)用方反饋有問題,在調(diào)用新加坡機(jī)房時正常,而調(diào)用印度機(jī)房則報SSL握手異常。

排查花了一些時間,同時也積累了一些經(jīng)驗,故記錄一下,讀完本文,你將了解到如下內(nèi)容:
- SSL握手過程
- SSL握手異常時的排查思路與工具
- 同版本的JDK,也是有所差異的
廢話不多說,往下看...
發(fā)現(xiàn)問題
調(diào)用方調(diào)用印度機(jī)房服務(wù)時,報錯信息如下:
這個異常是同事一直在看,經(jīng)過一翻搜索,懷疑是JDK版本的問題,經(jīng)過詢問調(diào)用方,發(fā)現(xiàn)調(diào)用方版本是1.8.0_91-b14,于是同事打算下載此版本JDK本地測試一下。
但這個版本JDK不太好找,于是同事就問了下我,我也找了一會也沒找到,于是打算從源碼編譯一個此版本JDK。
經(jīng)過一段時間,我通過源碼編譯出來了這個版本的jdk,同時同事也在網(wǎng)上找到了一個此版本的JDK,如下:
JDK源碼:https://github.com/openjdk/jdk8u ,tag選擇jdk8u91-b14即可。
網(wǎng)上的JDK包:https://github.com/ojdkbuild/ojdkbuild/releases/download/1.8.0.91-3/java-1.8.0-openjdk-1.8.0.91-1.b14.el6.x86_64.zip
弄到1.8.0_91-b14版JDK后,我和同事都進(jìn)行了測試,奇怪的是,同事網(wǎng)上找的JDK重現(xiàn)了調(diào)用方的報錯,即新加坡機(jī)房正常,而印度機(jī)房SSL握手失敗,但我自己編譯的JDK則兩個機(jī)房都正常,我們可是相同版本的JDK啊!
好家伙,現(xiàn)在有2個疑問了,如下:
為啥新加坡機(jī)房正常,而印度機(jī)房SSL握手報錯?
為啥相同版本的JDK,自己編譯的沒有問題?
為啥SSL握手報錯?
粗略來講,SSL握手過程如下:
客戶端發(fā)送Client Hello包給服務(wù)端,其中除了包含密鑰協(xié)商相關(guān)的數(shù)據(jù)外,還會告知自己支持的密碼套件列表。
服務(wù)端收到Client Hello包后,會給客戶端回復(fù)Server Hello,其中也包含了密鑰協(xié)商數(shù)據(jù),以及服務(wù)端選擇了哪個密碼套件。
但有一種情況是,客戶端第一步發(fā)送的所有密碼套件,服務(wù)端都不支持,因此服務(wù)端會回復(fù)一個SSL握手異常包,進(jìn)而導(dǎo)致客戶端失敗報錯。
注:密碼套件,指的是加密系統(tǒng)將多種密碼學(xué)算法混合使用,以實現(xiàn)多種安全需求,如TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,使用ECDHE實現(xiàn)密鑰協(xié)商、RSA實現(xiàn)證書認(rèn)證、AES實現(xiàn)加密、SHA256實現(xiàn)消息防篡改。
如何確認(rèn)是否是上面原因呢?我進(jìn)行了如下測試:
添加JVM參數(shù)-Djavax.net.debug=SSL,并調(diào)用正常的新加坡機(jī)房,看看SSL握手選擇的是什么密碼套件。
$ bin/java -Djavax.net.debug=SSL SgSendRequest
可以看到,客戶端提供了很多密碼套件,服務(wù)端選擇了TLS_DHE_RSA_WITH_AES_128_GCM_SHA256,那么極有可能是印度機(jī)房不支持此密碼套件,導(dǎo)致印度機(jī)房請求失敗,可用curl確認(rèn)下:
使用curl以指定密碼套件DHE-RSA-AES128-GCM-SHA256訪問印度機(jī)房。
$ curl -v https://in.xxx.be.srv.com --ciphers DHE-RSA-AES128-GCM-SHA256
可以發(fā)現(xiàn),印度機(jī)房確實不支持此密碼套件。
注:jdk密碼套件名稱與curl的名稱稍微有點不一致,curl的可以在這里查找 https://curl.se/docs/ssl-ciphers.html
這也就是說,此JDK支持的密碼學(xué)套件與印度機(jī)房支持的密碼學(xué)套件沒有交集,服務(wù)端無法選出一個雙方都支持的密碼套件,可以進(jìn)一步確認(rèn)下,如下:
jdk支持的密碼套件可以通過SSLServerSocketFactory.getSupportedCipherSuites()獲取。
$ bin/jrunscript -e "print(java.util.Arrays.toString(javax.net.ssl.SSLServerSocketFactory.getDefault().getSupportedCipherSuites()))"
印度機(jī)房支持的密碼套件可以使用nmap掃描獲取,如下:
$ nmap --script ssl-enum-ciphers -p 443 in.xxx.be.srv.com
經(jīng)過我的檢查,發(fā)現(xiàn)jdk的密碼套件與印度機(jī)房的密碼套件確實沒有交集,印度機(jī)房只支持一些較新的密碼套件,這就是調(diào)用印度機(jī)房服務(wù)時SSL握手失敗的原因。
用相同的方法,我也確認(rèn)了新加坡機(jī)房,發(fā)現(xiàn)新加坡機(jī)房的密碼套件與jdk的密碼套件有交集,而TLS_DHE_RSA_WITH_AES_128_GCM_SHA256就在其中。
要解決這個問題也比較容易,要么讓調(diào)用方升級jdk以支持新的密碼套件,要么讓印度機(jī)房SRE調(diào)整SSL配置以支持舊的密碼套件,我們選擇了前者。
那么,還有一個問題,為啥我自己編譯的同版本的JDK就沒有問題呢?
為啥自行編譯的JDK沒有問題?
有點迷惑,我用上面相同方法確認(rèn)了一下我自己編譯的JDK支持哪些套件,如下:
$ bin/jrunscript -e "print(java.util.Arrays.toString(javax.net.ssl.SSLServerSocketFactory.getDefault().getSupportedCipherSuites()))"
可以發(fā)現(xiàn),我自己編譯的JDK,支持ECDH系列的新密碼套件,這是為啥?
為了弄清區(qū)別,我使用問題JDK進(jìn)行了調(diào)試,如下:
import javax.crypto.KeyAgreement;
import java.security.NoSuchAlgorithmException;
public class EcdhTest {
public static void main(String[] args) throws NoSuchAlgorithmException {
KeyAgreement ka = KeyAgreement.getInstance("ECDH");
System.out.println(ka);
}
}
在問題JDK里面,會報如下異常:
$ bin/java EcdhTest
Exception in thread "main" java.security.NoSuchAlgorithmException: Algorithm ECDH not available
at javax.crypto.KeyAgreement.getInstance(KeyAgreement.java:184)
at EcdhTest.main(EcdhTest.java:6)
有異常就好辦了,只要順著異常產(chǎn)生的過程調(diào)試下去即可,大概調(diào)試了如下相關(guān)方法:
sun.security.ssl.JsseJce.getKeyAgreement("ECDH")
sun.security.ec.SunEC當(dāng)調(diào)試到SunEC類時,我發(fā)現(xiàn)在加載sunec動態(tài)庫時會報錯,如下:
于是,我去問題jdk目錄下查找這個動態(tài)庫文件,動態(tài)庫文件在Linux下一般是.so結(jié)尾,如下:
$ find | grep sunec
./jre/lib/ext/sunec.jar
./jre/lib/amd64/libsunec.so_DISABLED
./jre/lib/amd64/libsunec.diz
懵逼了,在這個問題JDK里,libsunec.so?竟然被改名為了libsunec.so_DISABLED,而我看了下我自己編譯的JDK,這個文件是沒有改名的!
終于,第二個問題也找到了原因,原來是網(wǎng)上找的這個JDK,通過改名libsunec.so將EC系列算法禁用了。
我大概看了會那個JDK下載頁面,這個JDK構(gòu)建時間挺久了,是RedHat早期為CentOS6構(gòu)建的一個JDK8版本,至于為啥要禁用EC系列算法,也沒找到相關(guān)解釋,只好就此打住。
總結(jié)
這個問題在報錯能被穩(wěn)定重現(xiàn)出來時,其實就不難了,但排查思路與使用到的工具還是挺值得分享的,如下:
- 客戶端與服務(wù)端支持的密碼套件沒有交集,會導(dǎo)致SSL握手失敗。
- 使用-Djavax.net.debug=SSL可以調(diào)試java的SSL握手過程。
- 通過curl --ciphers指定客戶端密碼套件訪問服務(wù)端,可以確認(rèn)服務(wù)端是否支持此密碼套件。
- 通過SSLServerSocketFactory.getSupportedCipherSuites()可獲取JDK支持的密碼套件。
- 使用nmap --script ssl-enum-ciphers可掃描出服務(wù)端支持的密碼套件。
- 同樣版本的JDK,不同發(fā)行商發(fā)行的,也可能存在著差異。
本文標(biāo)題:一次SSL握手異常,我發(fā)現(xiàn)JDK還有發(fā)行版區(qū)別
網(wǎng)站路徑:http://m.fisionsoft.com.cn/article/djiejdh.html


咨詢
建站咨詢
