本文首先分析HTTP協定在安全性上的不足,進而闡述HTTPS實作安全通信的關鍵技術點和原理。然後通過抓包分析HTTPS協定的握手以及通信過程。最後總結一下自己在開發過程中遇到的HTTPS相關的問題,并給出目前項目中對HTTPS問題的系統解決方案,以供總結和分享。如有不當之處,歡迎批評和指正。
HTTP1.x在傳輸資料時,所有傳輸的内容都是明文,用戶端和伺服器端都無法驗證對方的身份,存在的問題如下:
通信使用明文(不加密),内容可能會被竊聽;
不驗證通信方的身份,有可能遭遇僞裝;
無法證明封包的完整性,是以有可能已遭篡改;
其實這些問題不僅在HTTP上出現,其他未加密的協定中也會存在這類問題。
按TCP/IP協定族的工作機制,網際網路上的任何角落都存在通信内容被竊聽的風險。而HTTP協定本身不具備加密的功能,所傳輸的都是明文。即使已經經過過加密處理的通信,也會被窺視到通信内容,這點和未加密的通信是相同的。隻是說如果通信經過加密,就有可能讓人無法破解封包資訊的含義,但加密處理後的封包資訊本身還是會被看到的。
在HTTP協定通信時,由于不存在确認通信方的處理步驟,是以任何人都可以發起請求。另外,伺服器隻要接收到請求,不管對方是誰都會傳回一個響應。是以不确認通信方,存在以下隐患:
無法确定請求發送至目标的Web伺服器是否是按真實意圖傳回響應的那台伺服器。有可能是已僞裝的 Web 伺服器;
無法确定響應傳回到的用戶端是否是按真實意圖接收響應的那個用戶端。有可能是已僞裝的用戶端;
無法确定正在通信的對方是否具備通路權限。因為某些Web伺服器上儲存着重要的資訊,隻想發給特定使用者通信的權限;
無法判定請求是來自何方、出自誰手;
即使是無意義的請求也會照單全收,無法阻止海量請求下的DoS攻擊;
所謂完整性是指資訊的準确度。若無法證明其完整性,通常也就意味着無法判斷資訊是否準确。HTTP協定無法證明通信的封包完整性,在請求或響應送出之後直到對方接收之前的這段時間内,即使請求或響應的内容遭到篡改,也沒有辦法獲悉。
比如,從某個Web網站下載下傳内容,是無法确定用戶端下載下傳的檔案和伺服器上存放的檔案是否前後一緻的。檔案内容在傳輸途中可能已經被篡改為其他的内容。即使内容真的已改變,作為接收方的用戶端也是覺察不到的。像這樣,請求或響應在傳輸途中,遭攻擊者攔截并篡改内容的攻擊稱為中間人攻擊(Man-in-the-Middle attack,MITM)。

由于上述的幾個問題,需要一種能夠提供如下功能的HTTP安全技術:
1 .伺服器認證(用戶端知道它們是在與真正的而不是僞造的伺服器通話);
2 .用戶端認證(伺服器知道它們是在與真正的而不是僞造的用戶端通話);
3 .完整性(用戶端和伺服器的資料不會被修改);
4 .加密(用戶端和伺服器的對話是私密的,無需擔心被竊聽);
5 .效率(一個運作的足夠快的算法,以便低端的用戶端和伺服器使用);
6 .普适性(基本上所有的用戶端和伺服器都支援這些協定);
在這樣的需求背景下,HTTPS技術誕生了。HTTPS協定的主要功能基本都依賴于TLS/SSL協定,提供了身份驗證、資訊加密和完整性校驗的功能,可以解決HTTP存在的安全問題。本節就重點探讨一下HTTPS協定的幾個關鍵技術點。
加密算法一般分為兩種:
對稱加密:加密與解密的密鑰相同。以DES算法為代表;
非對稱加密:加密與解密的密鑰不相同。以RSA算法為代表;
對稱加密強度非常高,一般破解不了,但存在一個很大的問題就是無法安全地生成和保管密鑰,假如用戶端和伺服器之間每次會話都使用固定的、相同的密鑰加密和解密,肯定存在很大的安全隐患。
在非對稱密鑰交換算法出現以前,對稱加密一個很大的問題就是不知道如何安全生成和保管密鑰。非對稱密鑰交換過程主要就是為了解決這個問題,使密鑰的生成和使用更加安全。但同時也是HTTPS性能和速度嚴重降低的“罪魁禍首”。
HTTPS采用對稱加密和非對稱加密兩者并用的混合加密機制,在交換密鑰環節使用非對稱加密方式,之後的建立通信交換封包階段則使用對稱加密方式。
非對稱加密最大的一個問題,就是無法證明公鑰本身就是貨真價實的公鑰。比如,正準備和某台伺服器建立公開密鑰加密方式下的通信時,如何證明收到的公開密鑰就是原本預想的那台伺服器發行的公開密鑰。或許在公開密鑰傳輸途中,真正的公開密鑰已經被攻擊者替換掉了。
如果不驗證公鑰的可靠性,至少會存在如下的兩個問題:中間人攻擊和資訊抵賴。
為了解決上述問題,可以使用由數字證書認證機構(CA,Certificate Authority)和其相關機關頒發的公開密鑰證書。
CA使用具體的流程如下:
伺服器的營運人員向數字證書認證機構(CA)提出公開密鑰的申請;
CA通過線上、線下等多種手段驗證申請者提供資訊的真實性,如組織是否存在、企業是否合法,是否擁有域名的所有權等;
如果資訊稽核通過,CA會對已申請的公開密鑰做數字簽名,然後配置設定這個已簽名的公開密鑰,并将該公開密鑰放入公鑰證書後綁定在一起。
證書包含以下資訊:申請者公鑰、申請者的組織資訊和個人資訊、簽發機構CA的資訊、有效時間、證書序列号等資訊的明文,同時包含一個簽名;
簽名的産生算法:首先,使用散列函數計算公開的明文資訊的資訊摘要,然後,采用CA的私鑰對資訊摘要進行加密,密文即簽名;
用戶端在HTTPS握手階段向伺服器送出請求,要求伺服器傳回證書檔案;
用戶端讀驗證書中的相關的明文資訊,采用相同的散列函數計算得到資訊摘要,然後,利用對應CA的公鑰解密簽名資料,對比證書的資訊摘要,如果一緻,則可以确認證書的合法性,即公鑰合法;
用戶端然後驗證證書相關的域名資訊、有效時間等資訊;
用戶端會内置信任CA的證書資訊(包含公鑰),如果CA不被信任,則找不到對應CA的證書,證書也會被判定非法。
在這個過程注意幾點:
申請證書不需要提供私鑰,確定私鑰永遠隻能被伺服器掌握;
證書的合法性仍然依賴于非對稱加密算法,證書主要是增加了伺服器資訊以及簽名;
内置CA對應的證書稱為根證書;頒發者和使用者相同,自己為自己簽名,叫自簽名證書;
證書=公鑰+申請者與頒發者資訊+簽名;
HTTPS協定曆史簡介:
SSL協定的第一個版本由Netscape公司開發,但這個版本從未釋出過;
SSL協定第二版于1994年11月釋出。第一次部署是在Netscape
Navigator1.1浏覽器上,發行于1995年3月;
SSL 3于1995年年底釋出,雖然名稱與早先的協定版本相同,但SSL3是完全重新設計的協定,該設計一直沿用到今天。
TLS 1.0于1999年1月問世,與SSL 3相比,版本修改并不大;
2006年4月,下一個版本TLS 1.1才問世,僅僅修複了一些關鍵的安全問題;
2008年8月,TLS1.2釋出。該版本添加了對已驗證加密的支援,并且基本上删除了協定說明中所有寫死的安全基元,使協定完全彈性化;
宏觀上,TLS以記錄協定(record protocol)實作。記錄協定負責在傳輸連接配接上交換所有的底層消息,并可以配置加密。每一條TLS記錄以一個短标頭起始。标頭包含記錄内容的類型(或子協定)、協定版本和長度。消息資料緊跟在标頭之後,如下圖所示:
TLS的主規格說明書定義了四個核心子協定:
握手協定(handshake protocol);
密鑰規格變更協定(change cipher spec protocol);
應用資料協定(application data protocol);
警報協定(alert protocol);
握手是TLS協定中最精密複雜的部分。在這個過程中,通信雙方協商連接配接參數,并且完成身份驗證。根據使用的功能的不同,整個過程通常需要交換6~10條消息。根據配置和支援的協定擴充的不同,交換過程可能有許多變種。在使用中經常可以觀察到以下三種流程:
完整的握手,對伺服器進行身份驗證(單向驗證,最常見);
對用戶端和伺服器都進行身份驗證的握手(雙向驗證);
恢複之前的會話采用的簡短握手;
本節以QQ郵箱的登入過程為例,通過抓包來對單向驗證的握手流程進行分析。單向驗證的一次完整的握手流程如下所示:
主要分為四個步驟:
交換各自支援的功能,對需要的連接配接參數達成一緻;
驗證出示的證書,或使用其他方式進行身份驗證;
對将用于保護會話的共享主密鑰達成一緻;
驗證握手消息是否被第三方團體修改;
.
下面對這一過程進行詳細的分析。
1.ClientHello
在握手流程中,ClientHello是第一條消息。這條消息将用戶端的功能和首選項傳送給伺服器。包含用戶端支援的SSL的指定版本、加密元件(Cipher Suite)清單(所使用的加密算法及密鑰長度等)。
2.ServerHello
ServerHello消息将伺服器選擇的連接配接參數傳送回用戶端。這個消息的結構與ClientHello類似,隻是每個字段隻包含一個選項。伺服器的加密元件内容以及壓縮方法等都是從接收到的用戶端加密元件内篩選出來的。
3.Certificate
之後伺服器發送Certificate封包,封包中包含公開密鑰證書,伺服器必須保證它發送的證書與選擇的算法套件一緻。不過Certificate消息是可選的,因為并非所有套件都使用身份驗證,也并非所有身份驗證方法都需要證書。
4.ServerKeyExchange
ServerKeyExchange消息的目的是攜帶密鑰交換的額外資料。消息内容對于不同的協商算法套件都會存在差異。在某些場景中,伺服器不需要發送任何内容,在這些場景中就不需要發送ServerKeyExchange消息。
5.ServerHelloDone
ServerHelloDone消息表明伺服器已經将所有預計的握手消息發送完畢。在此之後,伺服器會等待用戶端發送消息。
6.ClientKeyExchange
ClientKeyExchange消息攜帶用戶端為密鑰交換提供的所有資訊。這個消息受協商的密碼套件的影響,内容随着不同的協商密碼套件而不同。
7.ChangeCipherSpec
ChangeCipherSpec消息表明發送端已取得用以生成連接配接參數的足夠資訊,已生成加密密鑰,并且将切換到加密模式。用戶端和伺服器在條件成熟時都會發送這個消息。注意:ChangeCipherSpec不屬于握手消息,它是另一種協定,隻有一條消息,作為它的子協定進行實作。
8.Finished
Finished消息意味着握手已經完成。消息内容将加密,以便雙方可以安全地交換驗證整個握手完整性所需的資料。用戶端和伺服器在條件成熟時都會發送這個消息。
在一些對安全性要求更高的場景下,可能會出現雙向驗證的需求。完整的雙向驗證流程如下:
可以看到,同單向驗證流程相比,雙向驗證多了如下兩條消息:CertificateRequest與CertificateVerify,其餘流程大緻相同。
1.Certificate Request
=Certificate Request是TLS規定的一個可選功能,用于伺服器認證用戶端的身份。通過伺服器要求用戶端發送一個證書實作,伺服器應該在ServerKeyExchange之後立即發送CertificateRequest消息。
消息結構如下:
可以選擇發送一份自己接受的證書頒發機構清單,這些機構都用其可分辨名稱來表示.
2.CertificateVerify
當需要做用戶端認證時,用戶端發送CertificateVerify消息,來證明自己确實擁有用戶端證書的私鑰。這條消息僅僅在用戶端證書有簽名能力的情況下發送。CertificateVerify必須緊跟在ClientKeyExchange之後。消息結構如下:
應用資料協定攜帶着應用消息,隻以TLS的角度考慮的話,這些就是資料緩沖區。記錄層使用目前連接配接安全參數對這些消息進行打包、碎片整理和加密。如下圖所示,可以看到傳輸的資料已經是經過加密之後的了。
警報的目的是以簡單的通知機制告知對端通信出現異常狀況。它通常會攜帶close_notify異常,在連接配接關閉時使用,報告錯誤。警報非常簡單,隻有兩個字段:
這是最常見的一種問題,通常會抛出如下類型的異常:
出現此類錯誤通常可能由以下的三種原因導緻:
頒發伺服器證書的CA未知;
伺服器證書不是由CA簽署的,而是自簽署(比較常見);
伺服器配置缺少中間 CA;
當伺服器的CA不被系統信任時,就會發生 SSLHandshakeException。可能是購買的CA憑證比較新,Android系統還未信任,也可能是伺服器使用的是自簽名證書(這個在測試階段經常遇到)。
解決此類問題常見的做法是:指定HttpsURLConnection信任特定的CA集合。在本文的第5部分代碼實作子產品,會詳細的講解如何讓Android應用信任自簽名證書集合或者跳過證書校驗的環節。
SSL連接配接有兩個關鍵環節。首先是驗證證書是否來自值得信任的來源,其次確定正在通信的伺服器提供正确的證書。如果沒有提供,通常會看到類似于下面的錯誤:
出現此類問題的原因通常是由于伺服器證書中配置的域名和用戶端請求的域名不一緻所導緻的。
有兩種解決方案:
重新生成伺服器的證書,用真實的域名資訊;
自定義HostnameVerifier,在握手期間,如果URL的主機名和伺服器的辨別主機名不比對,則驗證機制可以回調此接口的實作程式來确定是否應該允許此連接配接。可以通過自定義HostnameVerifier實作一個白名單的功能。
代碼如下:
SSL支援服務端通過驗證用戶端的證書來确認用戶端的身份。這種技術與TrustManager的特性相似。本文将在第5部分代碼實作子產品,講解如何讓Android應用支援用戶端證書驗證的方式。
之前在接口聯調的過程中,測試那邊回報過一個問題是在Android 4.4以下的系統出現HTTPS請求不成功而在4.4以上的系統上卻正常的問題。相應的錯誤如下:
按照官方文檔的描述,Android系統對SSL協定的版本支援如下:
Name Supported (API Levels)
Default 10+
SSL 10–TBD
SSLv3 10–TBD
TLS 1+
TLSv1 10+
TLSv1.1 16+
TLSv1.2 16+
Name
Supported (API Levels)
Default
10+
SSL
10–TBD
SSLv3
TLS
1+
TLSv1
TLSv1.1
16+
TLSv1.2
也就是說,按官方的文檔顯示,在API 16+以上,TLS1.1和TLS1.2是預設開啟的。但是實際上在API 20+以上才預設開啟,4.4以下的版本是無法使用TLS1.1和TLS 1.2的,這也是Android系統的一個bug。
參照stackoverflow上的一些方式,比較好的一種解決方案如下:
對于4.4以下的系統,使用自定義的TLSSocketFactory,開啟對TLS1.1和TLS1.2的支援,核心代碼:
本部分主要基于第四部分提出的Android應用中使用HTTPS遇到的一些常見的問題,給出一個比較系統的解決方案。
不管是使用自簽名證書,還是采取用戶端身份驗證,核心都是建立一個自己的KeyStore,然後使用這個KeyStore建立一個自定義的SSLContext。整體類圖如下:
類圖中的MySSLContext可以應用在HTTPUrlConnection的方式與服務端連接配接的過程中:
核心是通過httpsURLConnection.setSSLSocketFactory使用自定義的校驗邏輯。整體設計上使用政策模式決定采用哪種驗證機制:
makeContextWithCilentAndServer 單向驗證方式(自定義信任的證書集合)
makeContextWithServer 雙向驗證方式(自定義信任的證書集合,并使用用戶端證書)
makeContextToTrustAll (信任所有的CA憑證,不安全,僅供測試階段使用)
在App中,把服務端證書放到資源檔案下(通常是asset目錄下,因為證書對于每一個使用者來說都是相同的,并且也不會經常發生改變),但是也可以放在裝置的外部存儲上。
serverCertificateNames中定義了App所信任的證書名稱(這些證書檔案必須要放在指定的檔案路徑下,并其要保證名稱相同),而後就可以加載服務端證書鍊到keystore,通過擷取到的可信任并帶有服務端證書的keystore,就可以用它來初始化自定義的SSLContext了:
和上面的過程類似,隻不過這裡提供的TrustManager不需要提供信任的證書集合,預設接受任意用戶端證書即可:
而後構造相應的SSLContext:
TLS協定分析(5) handshake協定 證書與密鑰交換
HTTPS深入了解
HTTPS原理介紹之内容加密
How to disable SSLv3 in android for HttpsUrlConnection?
Android 4.1+ enable TLS 1.1 and TLS 1.2
Google關于https的官方文檔
使用HTTPS與SSL來保證安全性
Android App 安全的HTTPS 通信
詳解https是如何確定安全的?
圖解HTTP—第七章
HTTP: The Definitive Guide
Bulletproof SSL and TLS:
Understanding and Deploying SSL/TLS and PKI to Secure Servers and Web Applications