一、Android6.0的一些修改
因為該問題僅僅出現在Android6.0版本中,因此,考慮是由版本升級引起的。查看Google給出的Android6.0修改文檔,發現以下兩點:
即:
從Android6.0之后將不再支持HttpClient的使用,建議使用HttpURLConnection代替。
Android6.0之后,在Https請求中,SSL層將不再使用OpenSSL協議,改用自己的BoringSSL協議
在我們的項目中使用的是HttpClient執行Https請求,但是官方升級只是在API文檔中刪除了HttpClient相關的文件,但是并不影響其使用,用戶可以通過以下兩種方法繼續使用:
使用Android6.0進行編譯,則需要添加 org.apache.http.legacy.jar,文件目錄:SDK\platforms\android-23\optional;或者是在AndroidStudio中的build.gradle文件中加入:
android {useLibrary 'org.apache.http.legacy'}
使用Android6.0以下版本進行編譯。
因此,排除該可能。
因為考慮到是該問題引起的,因此回過頭重新對握手失敗原因進行查看,發現在之前Log結尾部分忽略了一句話,也正是因為忽略這句話,導致之前思維一直停在可能是HttpClient被取消導致的,浪費了很多時間,握手失敗后,在最后邊Log中,出現這樣的信息:
關鍵字:BAD_DH_P_LENGTH
經過一番尋找,發現意思應該是Diffie-Hellman的p參數長度錯誤。主要參考文章:謝謝大神——連接
通過對帖子的閱讀和資料的查找,總結其主要原因在于:
Https建立連接之前,會進行多次握手,即單向認證和雙向認證。在該過程中,客戶端會將自己支持的所有加密方式發送給服務端,供服務端選擇,服務端選擇好加密程度較高的加密方式后,會以明文或者是客戶端私鑰加密密文的方式發送給客戶端。
具體認證過程可參考我的上一篇文章:Https單向認證和雙向認證
在握手過程中,必定會涉及到公鑰加密,私鑰解密的過程,而該過程中,當服務端選擇使用諸如TLS_DHE_RSA_WITH_AES_128_CBC_SHA等算法進行加密時,需要使用到Diffie-Hellman算法進行加密解密,通過閱讀Diffie-Hellman算法的介紹,發現在加密解密計算過程中,會使用到兩個參數,一個是q,一個是a,而在JDK8之前,服務器端提供的q參數只是用了768bit的長度,而不足1024bit則存在相應的安全漏洞,會被替換后的BroingSSL拒絕,因此出現了Handshake failed錯誤。
終于找到問題所在了,總結這個糾結的過程,我只想說:一定要認真看Log?。。?/p>
通過閱讀上邊的文檔,發現兩種可以解決的辦法:
升級JDK到8(條件限制,未經過測試)
配置Tomcat服務器,限制加密方式:
修改Tomcat服務器conf/server.xml文件中和Https有關的Connector節點,添加ciphers用于指定密鑰:
SSLEnabled="true"
clientAuth="false"
connectionTimeout="20000"
keystoreFile="/usr/xinwei/tienlen/apache-tomcat-https/server.keystore"
keystorePass="xinwei"
maxThreads="150"
port="443"
protocol="org.apache.coyote.http11.Http11Protocol"
redirectPort="8443"
scheme="https"
secure="true"
ciphers="TLS_RSA_WITH_AES_128_CBC_SHA256,
TLS_RSA_WITH_AES_128_CBC_SHA,
TLS_RSA_WITH_AES_256_CBC_SHA256,
TLS_RSA_WITH_AES_256_CBC_SHA,
SSL_RSA_WITH_3DES_EDE_CBC_SHA"
sslProtocol="TLS"
truststoreFile="/usr/xinwei/tienlen/apache-tomcat-https/server.keystore"
truststorePass="密碼"
/>
添加完該配置后,重啟,測試,Android6.0版本沒有再發現Handshake failed錯誤。