超文本傳輸安全協(xié)議(HTTPS,常稱為 HTTP over TLS/SSL)是一種通過計算機網(wǎng)絡進行安全通信的傳輸協(xié)議。HTTPS 經(jīng)由 HTTP 進行通信,但利用 SSL/TLS 來加密數(shù)據(jù)包。HTTPS 開發(fā)的主要目的,是提供對網(wǎng)站服務器的身份認證,保護交換數(shù)據(jù)的隱私與完整性。
本文主要介紹 :
-
Https如何保證數(shù)據(jù)傳輸?shù)陌踩?/li> -
CA的存在及其安全性 - 證書工具
keytool - 證書驗證流程
-
Https握手流程 -
Android下進行Https訪問
TLS/SSL
TCP (Transmission Control Protoco) 傳輸層控制協(xié)議
TLS (Transport Layer Security) 傳輸層安全協(xié)定
SSL (Secure Socket Layer) 安全套接層
HTTP(Hypertext Transfer Protocol) 基于 TCP 協(xié)議,無連接,每次連接只處理一個請求,結(jié)束后斷開連接;無狀態(tài),無法保持用戶狀態(tài),使用 cookie 和 session 解決。
HTTPS(HTTP over TLS/SSL) 安全的 http 協(xié)議,HTTP 協(xié)議和 TCP 協(xié)議之間增加了 TLS/SSL 保證數(shù)據(jù)的安全傳輸。
歷史進程:
1994年,NetScape公司設計了SSL協(xié)議(Secure Sockets Layer)的1.0版,但是未發(fā)布。
1995年,NetScape公司發(fā)布SSL 2.0版,很快發(fā)現(xiàn)有嚴重漏洞。
1996年,SSL 3.0版問世,得到大規(guī)模應用。
1999年,互聯(lián)網(wǎng)標準化組織ISOC接替NetScape公司,發(fā)布了SSL的升級版TLS 1.0版。
2006年和2008年,TLS進行了兩次升級,分別為TLS 1.1版和TLS 1.2版。最新的變動是2011年TLS
1.2的修訂版。
TLS 1.0通常被標示為SSL 3.1,TLS 1.1為SSL 3.2,TLS 1.2為SSL 3.3。
目前,應用最廣泛的是TLS 1.0,接下來是SSL 3.0。但是,主流瀏覽器都已經(jīng)實現(xiàn)了TLS 1.2的支持。
Https 安全性
HTTP 協(xié)議的不安全性體現(xiàn)在 3 個方面:
| 風險 | 描述 | https解決方案 |
|---|---|---|
| 竊聽風險 | 攻擊者可以獲知消息內(nèi)容 | 消息加密 |
| 篡改風險 | 攻擊者可以篡改消息內(nèi)容 | 消息摘要 |
| 冒充風險 | 攻擊者可以冒充其他人參與通信 | CA 身份認證 |
| CA 不可信 | 信任的 CA 亂簽發(fā)證書 | 證書鎖 |
竊聽/嗅探
指的是路由上的攻擊者,可以偷窺到傳輸?shù)南?nèi)容。
解決方案:
使用對稱加密算法加密通信內(nèi)容,竊聽者獲取到消息也無法識別,存在問題 -> 密鑰傳遞的安全性,在網(wǎng)絡上面的通信雙方都是陌生人,無法識別身份,密鑰要通過網(wǎng)絡傳輸時很有可能被竊取。?
使用非對稱加密算法加密通信內(nèi)容,發(fā)布的公鑰用來加密,私鑰用來解密,即使公鑰被竊取,依然無法解密消息內(nèi)容。存在問題 -> 速度慢,消耗大;公鑰被公開,如果回發(fā)私鑰加密的信息,任何持有公鑰的人都可以解密。?
最終,消息內(nèi)容仍舊使用對稱加密算法來加密,但是前期對稱加密的密鑰交換使用非對稱加密來進行,客戶端使用服務端公鑰加密對稱加密的密鑰,這樣就只有擁有私鑰的服務端可以獲取到加密內(nèi)容,由于對稱加密密鑰長度有限,加密的時間可以忽略不計。?
以上,可以防止嗅探的問題,路由上面的攻擊者即使獲取到消息也無法識別消息的內(nèi)容。
消息篡改
消息加密以后攻擊者無法獲取消息內(nèi)容的含義,但是可以篡改消息內(nèi)容,篡改之后接收方也無法感知。
解決方案:
- 采用 消息摘要(見文末注腳) 算法可以驗證數(shù)據(jù)的完整性,我們將發(fā)送的消息進行摘要,連同消息一起發(fā)送給接收方,接收方拿到消息之后對消息做同樣的摘要處理,對比摘要結(jié)果,即可知道消息有沒有被篡改。
以上,可以解決消息完整性和真實性的問題。
中間人攻擊
中間人攻擊(Man-in-the-middle Attack,MITM)指的是攻擊者在鏈路上偽裝自己,與通訊雙方分別建立聯(lián)系,并交換其所收到的數(shù)據(jù),使通訊的兩端認為他們正在通過一個私密的連接與對方直接對話,但事實上整個會話都被攻擊者完全控制。
作為 A 和 B 通信路由上的攻擊者 M,作為中間人偽造自己的身份。A 向 B 請求用于通信的 PK_A,但是被中間人 M 截獲,他偽造生成了假的公鑰 PK_M,發(fā)送給了 A,同時向 B 請求并獲取了 B 的公開密鑰 PK_B,原來安全的通信過程 <span class="spec">A(使用 PK_B 加密) -> 安全 -> B(使用 SK_B 解密)</span>,現(xiàn)在變成了 <span class="spec">A(使用 PK_M 加密) -> M(使用 SK_M 解密獲取明文內(nèi)容,再用 PK_B 加密,可能篡改數(shù)據(jù)) -> B(使用 SK_B 解密)</span>。
出現(xiàn)問題的原因在于,密鑰在交換的初期是不安全的,網(wǎng)絡上的通信雙方,無法確定對方的身份,即無法獲悉當前的公鑰是不是自己想要的公鑰。
解決方案:
引入第三方公正作信用背書,第三方公正具有權(quán)威性,他和通信雙方?jīng)]有關(guān)系,接收方無條件信任公證機構(gòu),也就會信任他簽名的信息。這種機構(gòu)被稱為
CA(Certificate Authority機構(gòu),即數(shù)字證書認證機構(gòu)。CA機構(gòu)與瀏覽器和操作系統(tǒng)廠商合作,將公鑰內(nèi)置在瀏覽器和操作系統(tǒng)中,也就是不走網(wǎng)絡傳輸了,這樣一定程度上保證了公鑰不會被竊取篡改。服務端
Server將自己的消息(消息內(nèi)容大致包括電子簽證機關(guān)的信息、公鑰用戶信息、公鑰、權(quán)威機構(gòu)的簽字和有效期等)進行摘要之后,發(fā)給CA機構(gòu)AUTH簽發(fā)證書,Server使用自己的私鑰對消息摘要加密,形成證書,并將證書和消息內(nèi)容發(fā)送給Client,Client收到后,發(fā)現(xiàn)是AUTH的證書,同時對AUTH是信任的,則會使用AUTH的公鑰對證書進行解密(這里涉及證書鏈的驗證,其實要更加復雜),獲取到消息摘要,同時對收到的消息進行摘要,對比,如果一致則說明內(nèi)容沒有被篡改,是可信的,因為生成加密數(shù)據(jù)的私鑰只有CA機構(gòu)才有,這一過程稱為驗證數(shù)字簽名。
以上,可以解決中間人攻擊的文圖,另外涉及到非對稱加密的兩種應用場景,詳細介紹見文末非對稱加密應用場景。
CA 錯誤簽發(fā)
受信任的 CA(證書頒發(fā)機構(gòu))有好幾百個,他們成為整個網(wǎng)站身份認證過程中一個較大的攻擊面。實際上,目前由于 CA 失誤導致錯誤簽發(fā)證書,以及個別 CA 出于某些目的(如監(jiān)控加密流量)故意向第三方隨意簽發(fā)證書這兩種情況時有發(fā)生。現(xiàn)有的證書信任鏈機制最大的問題是,任何一家受信任的 CA 都可以簽發(fā)任意網(wǎng)站的站點證書,這些證書在客戶端看來,都是合法的,是可以通過驗證的。
解決方案:
證書鎖(
Certificate Pinning),證書鎖是為了防范由 「偽造或不正當手段獲得網(wǎng)站證書」 造成的中間人攻擊。證書鎖類似于
HPKP技術(shù)(下面有簡單介紹),給予我們主動選擇信任CA的權(quán)利。它的工作原理就是使用預先設置的證書指紋和服務器傳過來的證書鏈中的證書指紋進行匹配,只要有任何一對指紋匹配成功,則認為是一次合法的連接,否則禁止本次鏈接。也就是說,使用證書鎖之后,不是所有被系統(tǒng)信任的
CA都可以通過驗證,只有我保存了指紋的一些CA簽發(fā)的證書才可以,比如有 100 個CA機構(gòu),但我就信任其中一個,我可以保證這個CA不會亂簽發(fā)證書,那我就只保存這個CA的指紋,即使攻擊者從其他的 99 個CA簽發(fā)證書,對我進行攻擊,也無法完成連接。證書鎖定增加了安全性,但限制了你的服務器團隊升級
TLS證書的能力。
以上,可以解決 CA 機構(gòu)簽發(fā)證書權(quán)威性不足的問題,相關(guān)解決方案詳見文末 簽發(fā)證書權(quán)威性問題
?? 綜上,對稱加密通信 + 非對稱加密交換密鑰 + CA 認證身份 + 證書鎖鎖定證書指紋 共同保證了 https 的安全性。
CA 安全性
作為全網(wǎng) https 連接的權(quán)威公正,CA 的安全性至關(guān)重要。
安全保存 CA
從根 CA 開始到直接給客戶發(fā)放證書的各層次 CA,都有其自身的密鑰對。CA 中心的密鑰對一般由硬件加密服務器在機器內(nèi)直接產(chǎn)生,并存儲于加密硬件內(nèi),或以一定的加密形式存放于密鑰數(shù)據(jù)庫內(nèi)。加密備份于 IC 卡或其他存儲介質(zhì)中,并以高等級的物理安全措施保護起來。
密鑰的銷毀要以安全的密鑰沖寫標準,徹底清除原有的密鑰痕跡。需要強調(diào)的是,根 CA 密鑰的安全性至關(guān)重要,它的泄露意味著整個公鑰信任體系的崩潰,所以 CA 的密鑰保護必須按照最高安全級的保護方式來進行設置和管理。CA 的私鑰是自己靠上述方法保管的,不對外公開。
所以 CA 密鑰的安全性依賴于物理硬件的安全性,不通過網(wǎng)絡傳輸避免了被攻擊的可能。
CA 的公鑰是廠商跟瀏覽器和操作系統(tǒng)合作,把公鑰默認裝到瀏覽器或者操作系統(tǒng)環(huán)境里。比如 firefox 就自己維護了一個可信任的 CA 列表,而 chrome 和 IE 使用的是操作系統(tǒng)的 CA 列表。
證書鏈
現(xiàn)在大的 CA 都會有證書鏈,證書鏈的好處一是安全,保持根 CA 的私鑰離線使用。第二個好處是方便部署和撤銷,即如果證書出現(xiàn)問題,只需要撤銷相應級別的證書,根證書依然安全。
根 CA 證書都是自簽名,即用自己的公鑰和私鑰完成了簽名的制作和驗證。而證書鏈上的證書簽名都是使用上一級證書的密鑰對完成簽名和驗證的。
證書驗證
證書是否是信任的有效證書
是否信任 :接收方內(nèi)置了信任根證書的公鑰,需要證書是不是這些信任根證書簽發(fā)的或者信任根證書的二級證書機構(gòu)頒發(fā)的。
是否有效:證書是否在有效期內(nèi)。
是否合法:對方是不是上述證書的合法持有者,證明對方是否持有證書的對應私鑰。驗證方法兩種,一種是對方簽個名,我用證書驗證簽名;另外一種是用證書做個信封,看對方是否能解開。
是否吊銷:驗證是否吊銷可以采用黑名單方式或者
OCSP方式。黑名單就是定期從CA下載一個名單列表,里面有吊銷的證書序列號,自己在本地比對一下就行。優(yōu)點是效率高。缺點是不實時。OCSP是實時連接CA去驗證,優(yōu)點是實時,缺點是效率不高。
自簽名證書如何驗證
自簽名證書是自己給自己簽發(fā)的證書,也就是說自己做自己的 CA 機構(gòu),為自己擔保,因此無法內(nèi)置在系統(tǒng)當中,因此我們通常會在客戶內(nèi)置一個證書文件,自己進行校驗。
簡單來說,握手流程需要兩對密鑰對:
- 一對
CA的密鑰對JKS_A,由CA機構(gòu)維護,通常他的公鑰內(nèi)置在os中,用來簽名服務端信息摘要,保證服務端公鑰的真實性,避免中間人攻擊。 - 一對服務器的密鑰對
JKS_B,他是握手過程中隨機生成的,然后用「它的公鑰及其他內(nèi)容」的摘要去向CA實時簽發(fā)證書,用來進行對稱密鑰的加密傳輸。
自簽名證書就是不走 CA 機構(gòu),而是自己生成一對密鑰對 JKS_C,他的作用就好比 CA 的密鑰對 JKS_A,也是為了保證公鑰的真實性,握手過程和原來一樣,只是我們不需要去 CA 簽發(fā)證書了,用自己的 JKS_C 簽發(fā)就可以了;同樣因為 JKS_C 是我們自己的密鑰對,公鑰沒有被內(nèi)置在 os 中,所以此時需要我們自己把 cert 文件(JKS_C 的公鑰)放到本地,自己完成原本由 os 完成的 CA 校驗任務。
雙向驗證
雙向驗證指的是,不光客戶端要驗證來自服務器的連接是不是可靠,服務器也要驗證客戶端。
服務端也會內(nèi)置一套受信任的 CA 證書列表,用于驗證客戶端證書的真實性。驗證過程和客戶端驗證服務端類似。
Https 握手
單向驗證握手過程:
- 1、Client Hello ??
客戶端向服務器發(fā)送握手信息,告知自己支持的加密算法、摘要算法、安全層協(xié)議版本、隨機數(shù)
Random-Secret-C。
- 2、Server Hello ??
服務端隨機生成本次握手需要的非對稱加密的密鑰對(私鑰+公鑰),將來用來傳輸對稱加密密鑰。
服務端生成消息,內(nèi)容包含隨機數(shù)
Random-Secret-S,確定的一組加密算法和摘要算法,服務端公鑰,域名信息等。
服務端對信息內(nèi)容摘要,使用摘要的信息向 CA 機構(gòu)申請的簽名證書。
服務端向客戶端發(fā)送消息和申請的證書。
如果需要雙向驗證的話,請求客戶端證書。
- 3、驗證服務端證書,提取服務端公鑰 ??
客戶端從信任證書列表中發(fā)現(xiàn)是受信任的證書,會首先驗證證書是否被信任、有效性、合法性等信息,驗證過程參照上面的 證書驗證。
驗證通過,客戶端使用
CA機構(gòu)的公鑰對證書解密,拿到消息的摘要,對真正的消息內(nèi)容進行摘要,對比確定消息沒有被篡改,則取出服務端公鑰。
如果需要雙向驗證的話,向服務端發(fā)送自己的證書。
客戶端生成隨機數(shù)字
Pre-Master-Secret,將其進行摘要處理,使用服務端公鑰對消息和摘要結(jié)果加密,發(fā)送給服務器,并發(fā)送一個編碼改變通知,說明以后將會開始加密通信。
- 4、生成對稱加密密鑰 ??
如果需要雙向驗證的話,首先驗證客戶端證書,驗證過程類似客戶端驗證,驗證失敗則斷開連接
服務器使用私鑰對收到的信息解密,對消息進行摘要對比無誤,則說明對稱加密的密鑰沒有被篡改,然后使用
Random-Secret-C,Random-Secret-S,Pre-Master-Secret生成最終將要進行對稱加密通信的密鑰Master-Secret。
服務器使用
Master-Secret加密一段握手信息及其摘要,發(fā)送給客戶端,并發(fā)送一個編碼改變通知,說明以后將會開始加密通信。
- 5、客戶端驗證加密結(jié)果,握手結(jié)束 ??
客戶端使用
Random-Secret-C,Random-Secret-S,Pre-Master-Secret生成同樣的對稱加密密鑰Master-Secret,使用密鑰解密,并驗證信息摘要,沒有問題則握手結(jié)束。
后面的通信將會使用新生成的對稱加密密鑰加密進行。
圖解:

問題記錄
Q:為什么要有 3 個隨機數(shù)?
不管是客戶端還是服務器,都需要隨機數(shù),這樣生成的密鑰才不會每次都一樣。由于
SSL協(xié)議中證書是靜態(tài)的,因此十分有必要引入一種隨機因素來保證協(xié)商出來的密鑰的隨機性。對于RSA密鑰交換算法來說,Pre-Master-Secret本身就是一個隨機數(shù),再加上hello消息中的隨機,三個隨機數(shù)通過一個密鑰導出器最終導出一個對稱密鑰。
Pre-Master的存在在于SSL協(xié)議不信任每個主機都能產(chǎn)生完全隨機的隨機數(shù),如果隨機數(shù)不隨機,那么Pre-Master-Secret就有可能被猜出來,那么僅適用Pre-Master-Secret作為密鑰就不合適了,因此必須引入新的隨機因素,那么客戶端和服務器加上Pre-Master-Secret三個隨機數(shù)一同生成的密鑰就不容易被猜出了,一個偽隨機可能完全不隨機,可是是三個偽隨機就十分接近隨機了,每增加一個自由度,隨機性增加的可不是一。"
Q:放在 Android 客戶端的 cert 文件是啥?
是自簽名證書的公鑰,自簽名證書就是自己做自己的
CA機構(gòu),服務端會自己維護一個密鑰對JKS,他就相當于CA的簽發(fā)證書的密鑰對,在握手過程中服務器不需要走CA申請簽名證書了,自己簽發(fā)就可以,原本CA的公鑰被內(nèi)置在os中,CA的驗證不需要我們來關(guān)注,但是現(xiàn)在是自簽名的,自己做自己的CA,所以JKS的公鑰我們要內(nèi)置在客戶端中,自己完成驗證過程,替代原來os的驗證。
證書工具-keytool
提取證書內(nèi)容為一個字符串
keytool -printcert -rfc -file [srca.cer]
生成密鑰對
keytool -genkey -alias [march_server] -keyalg RSA -keystore [march_server.jks] -validity 3600 -storepass [123456]
讀取密鑰對信息
keytool -list -v -keystore [srca.jks] -storepass [123456]
提取公鑰,簽發(fā) cert 文件
keytool -export -alias march_server -file march_server.cer -keystore march_server.jks -storepass 123456
將客戶端公鑰導入客戶端密鑰對中,主要是服務器不能使用 cert 證書,需要導入到 jks 文件中
keytool -import -alias [march_client] -file [march_client.cer] -keystore [march_client_for_server.jks]
搭建 https 服務
這部分內(nèi)容參考了 CSDN-hongyang-Android Https 完全解析,這里做一個匯總和整理。
借助 tomcat 搭建簡單的 https 服務,主要是用來測試,之前使用 12306 的證書測試,但是前段時間 12306 證書換掉了,現(xiàn)在已經(jīng)是正式證書了,所以不得以需要自己搭建一個簡單的服務來做訪問測試。
- 搭建單向驗證的
https服務
首先我們準備好密鑰和證書,使用 keytool 工具生成服務端 march_server.jks 密鑰對,然后提取服務端公鑰 march_server.cert。
配置服務,主要是配置 tomcat/conf/server.xml 文件,添加一個如下的 Connector。
<!--
https 測試服務
clientAuth 和 truststoreFile 配置服務器驗證客戶端
keystoreFile 和 keystorePass 配置客戶端驗證服務器
-->
<Connector
clientAuth="false"
keystoreFile="/Users/march/Documents/march_server.jks"
keystorePass="123456"
disableUploadTimeout="true"
enableLookups="true"
SSLEnabled="true"
acceptCount="100"
maxSpareThreads="75"
maxThreads="200"
minSpareThreads="5"
protocol="org.apache.coyote.http11.Http11NioProtocol"
scheme="https"
secure="true"
port="8443"
sslProtocol="TLS"/>
- 搭建雙向驗證的
https服務
再生成客戶端密鑰對 march_client.jks,然后簽發(fā)客戶端公鑰 march_client.cert,為了能在服務端使用,將 march_client.cert 導入到新的密鑰對 march_client_for_server.jks。使用的命令都可以在上一節(jié)找到,生成這幾個密鑰,是為了搭建后面的服務作準備。
更改 conf/server.xml 配置
<!--
https 測試服務
clientAuth 和 truststoreFile 配置服務器驗證客戶端
keystoreFile 和 keystorePass 配置客戶端驗證服務器
-->
<Connector
clientAuth="true"
truststoreFile="/Users/march/Documents/march_client_for_server.jks"
keystoreFile="/Users/march/Documents/march_server.jks"
keystorePass="123456"
disableUploadTimeout="true"
enableLookups="true"
SSLEnabled="true"
acceptCount="100"
maxSpareThreads="75"
maxThreads="200"
minSpareThreads="5"
protocol="org.apache.coyote.http11.Http11NioProtocol"
scheme="https"
secure="true"
port="8443"
sslProtocol="TLS"/>
此時瀏覽器已經(jīng)不能訪問
Android 進行 Https 訪問
如果你的證書是購買的 CA 簽發(fā)的證書,是可以直接進行 https 訪問的,不需要做什么特殊處理,下面主要介紹使用自簽名證書的情況。
啟用雙向驗證時,Android 客戶端也不能直接使用 march_client.jks 需要轉(zhuǎn)為 bks 文件,使用下面的工具。jks 轉(zhuǎn) bks 工具
注:這部分不是說如何發(fā)請求,只是討論怎么配置證書和密鑰對去發(fā)起 Https 訪問。
在 Android 中進行 Https 訪問,主要牽扯到以下幾個關(guān)鍵類:
-
TrustManagerFactory,它主要是用來導入自簽名證書,用來驗證來自服務器的連接。 -
KeyManagerFactory,當開啟雙向驗證時,用來導入客戶端的密鑰對。 -
SSLContext,SSL上下文,使用上面的兩個類進行初始化,就是個上下文環(huán)境。
?? 都讓開,我要貼代碼了!
SSLContext
為了簡單起見,我們先來看看如何生成 SSLContext
/**
* 創(chuàng)建 SSLContext
* @param keyManagerFactory 用于雙向驗證,不需要可以直接為空
* @param trustManagerFactory 用于導入證書
* @return SSLContext
*/
private SSLContext getSSLContext(@Nullable KeyManagerFactory keyManagerFactory,
TrustManagerFactory trustManagerFactory)
throws NoSuchAlgorithmException, KeyManagementException {
if (trustManagerFactory == null)
return null;
SSLContext sslContext = SSLContext.getInstance("TLS");
KeyManager[] keyManagers = null;
if (keyManagerFactory != null) {
keyManagers = keyManagerFactory.getKeyManagers();
}
sslContext.init(keyManagers, trustManagerFactory.getTrustManagers(), new SecureRandom());
return sslContext;
}
TrustManagerFactory
導入客戶端證書,生成 TrustManagerFactory
/**
* 導入客戶端證書,生成 TrustManagerFactory
* @param serverCertInputStreams 證書的輸入流
* @return TrustManagerFactory
*/
private TrustManagerFactory getTrustManagerFactory(InputStream... serverCertInputStreams)
throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException {
if (serverCertInputStreams == null || serverCertInputStreams.length == 0) {
return null;
}
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
// 為證書設置一個keyStore,并將證書放入 keyStore 中
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
keyStore.load(null, null);
int index = 0;
for (InputStream inputStream : serverCertInputStreams) {
String alias = Integer.toString(index++);
Certificate certificate = certificateFactory.generateCertificate(inputStream);
keyStore.setCertificateEntry(alias, certificate);
}
// 創(chuàng)建 TrustManagerFactory
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keyStore);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
return trustManagerFactory;
}
KeyManagerFactory
雙向驗證時,配置客戶端密鑰對,生成 KeyManagerFactory
/**
* 雙向驗證時,配置客戶端密鑰對,生成 KeyManagerFactory
*
* @param clientCertInputStream 密鑰對輸入流
* @param passWd 密鑰對密碼
* @return KeyManagerFactory
*/
private KeyManagerFactory getKeyManagerFactory(InputStream clientCertInputStream, String passWd)
throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, UnrecoverableKeyException {
if (clientCertInputStream == null || passWd == null) {
return null;
}
//本地證書 初始化keystore
KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
clientKeyStore.load(clientCertInputStream, passWd.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(clientKeyStore, passWd.toCharArray());
return keyManagerFactory;
}
Init Connection
這樣我們就拿到了已經(jīng)生成好的 SSLContext,我們需要的 KeyManagerFactory,TrustManagerFactory 都已經(jīng)加到 SSLContext 里面了。
如果是用 HttpsURLConnection
connection.setSSLSocketFactory(sslContext.getSocketFactory());
如果是用 OkHttp
// 這個方法已經(jīng)過時了
okHttpClientBuilder.sslSocketFactory(sslContext.getSocketFactory());
// 新的方法需要一個 X509TrustManager,那我們就從 TrustManagerFactory 取出來給他
TrustManagerFactory trustManagerFactory = getTrustManagerFactory(serverCertInputStreams);
KeyManagerFactory keyManagerFactory = getKeyManagerFactory(clientCertInputStream, passWd);
if (trustManagerFactory != null) {
X509TrustManager x509TrustManager = (X509TrustManager) trustManagerFactory.getTrustManagers()[0];
SSLContext sslContext = getSSLContext(keyManagerFactory, trustManagerFactory);
if (x509TrustManager != null && sslContext != null) {
okHttpClientBuilder.sslSocketFactory(sslContext.getSocketFactory(), x509TrustManager);
}
}
HostnameVerifier
另外 HostnameVerifier 也是必要的,這個 HttpsURLConnection 和 OkHttp 沒啥差別
connection.setHostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
okHttpClientBuilder.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
});
巨人的肩膀
CSDN 如何理解HTTP協(xié)議的 “無連接,無狀態(tài)” 特點?
個人博客-餓了么Android-聊聊 Android HTTPS 的使用姿勢
個人博客-https詳解 概要介紹
CSDN-hongyang-Android Https 完全解析
注腳
<span id="消息摘要"/>
消息摘要(Message Digest)
指的是將長度不固定的參數(shù)作為輸入?yún)?shù),運行特定 hash 函數(shù),生成固定長度的輸出,這個輸出就是消息的摘要,常用算法有 MD5 和 SHA1,摘要算法是單向不可逆的,即無法從摘要重新反向出原消息的內(nèi)容。
<span id="信用背書"/>
信用背書
票據(jù)的收款人或持有人在轉(zhuǎn)讓票據(jù)時,在票據(jù)背面簽名或書寫文句的手續(xù)。背書的人就會對這張支票負某種程度、類似擔保的償還責任。
<span id="非對稱加密應用場景"/>
非對稱加密的兩種應用場景
- 某用戶使用自己的私鑰對數(shù)據(jù)加密,任何人都可以使用公鑰對數(shù)據(jù)進行解密,因為私鑰只有該用戶持有,則說明該數(shù)據(jù)一定出自于該用戶。公眾可以用這一方法驗證內(nèi)容是否完整,是否被篡改,接受者可以認定該內(nèi)容出自該用戶,該用戶也無法抵賴,這被稱作數(shù)字簽名。
- 某用戶使用公開的公鑰對數(shù)據(jù)進行加密,那么可以保證只有發(fā)布公鑰的一方可以對數(shù)據(jù)進行解密,別人無法獲取數(shù)據(jù)的內(nèi)容,保證數(shù)據(jù)傳遞的安全。
<span id="簽發(fā)證書權(quán)威性問題"/>
應對 CA 簽發(fā)證書權(quán)威性問題的解決方案