自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

HTTPS 原理淺析及其在 Android 中的使用

移動(dòng)開(kāi)發(fā) Android
本文首先分析HTTP協(xié)議在安全性上的不足,進(jìn)而闡述HTTPS實(shí)現(xiàn)安全通信的關(guān)鍵技術(shù)點(diǎn)和原理。然后通過(guò)抓包分析HTTPS協(xié)議的握手以及通信過(guò)程。最后總結(jié)一下自己在開(kāi)發(fā)過(guò)程中遇到的HTTPS相關(guān)的問(wèn)題,并給出當(dāng)前項(xiàng)目中對(duì)HTTPS問(wèn)題的系統(tǒng)解決方案,以供總結(jié)和分享。

[[192101]]

本文首先分析HTTP協(xié)議在安全性上的不足,進(jìn)而闡述HTTPS實(shí)現(xiàn)安全通信的關(guān)鍵技術(shù)點(diǎn)和原理。然后通過(guò)抓包分析HTTPS協(xié)議的握手以及通信過(guò)程。最后總結(jié)一下自己在開(kāi)發(fā)過(guò)程中遇到的HTTPS相關(guān)的問(wèn)題,并給出當(dāng)前項(xiàng)目中對(duì)HTTPS問(wèn)題的系統(tǒng)解決方案,以供總結(jié)和分享。

1.HTTP協(xié)議的不足

HTTP1.x在傳輸數(shù)據(jù)時(shí),所有傳輸?shù)膬?nèi)容都是明文,客戶端和服務(wù)器端都無(wú)法驗(yàn)證對(duì)方的身份,存在的問(wèn)題如下:

  • 通信使用明文(不加密),內(nèi)容可能會(huì)被竊聽(tīng);
  • 不驗(yàn)證通信方的身份,有可能遭遇偽裝;
  • 無(wú)法證明報(bào)文的完整性,所以有可能已遭篡改;

  其實(shí)這些問(wèn)題不僅在HTTP上出現(xiàn),其他未加密的協(xié)議中也會(huì)存在這類問(wèn)題。

(1) 通信使用明文可能會(huì)被竊聽(tīng)

  按TCP/IP協(xié)議族的工作機(jī)制,互聯(lián)網(wǎng)上的任何角落都存在通信內(nèi)容被竊聽(tīng)的風(fēng)險(xiǎn)。而HTTP協(xié)議本身不具備加密的功能,所傳輸?shù)亩际敲魑摹<词挂呀?jīng)經(jīng)過(guò)過(guò)加密處理的通信,也會(huì)被窺視到通信內(nèi)容,這點(diǎn)和未加密的通信是相同的。只是說(shuō)如果通信經(jīng)過(guò)加密,就有可能讓人無(wú)法破解報(bào)文信息的含義,但加密處理后的報(bào)文信息本身還是會(huì)被看到的。

(2) 不驗(yàn)證通信方的身份可能遭遇偽裝

在HTTP協(xié)議通信時(shí),由于不存在確認(rèn)通信方的處理步驟,因此任何人都可以發(fā)起請(qǐng)求。另外,服務(wù)器只要接收到請(qǐng)求,不管對(duì)方是誰(shuí)都會(huì)返回一個(gè)響應(yīng)。因此不確認(rèn)通信方,存在以下隱患:

  • 無(wú)法確定請(qǐng)求發(fā)送至目標(biāo)的Web服務(wù)器是否是按真實(shí)意圖返回響應(yīng)的那臺(tái)服務(wù)器。有可能是已偽裝的 Web 服務(wù)器;
  • 無(wú)法確定響應(yīng)返回到的客戶端是否是按真實(shí)意圖接收響應(yīng)的那個(gè)客戶端。有可能是已偽裝的客戶端;
  • 無(wú)法確定正在通信的對(duì)方是否具備訪問(wèn)權(quán)限。因?yàn)槟承¦eb服務(wù)器上保存著重要的信息,只想發(fā)給特定用戶通信的權(quán)限;
  • 無(wú)法判定請(qǐng)求是來(lái)自何方、出自誰(shuí)手;
  • 即使是無(wú)意義的請(qǐng)求也會(huì)照單全收,無(wú)法阻止海量請(qǐng)求下的DoS攻擊;

(3) 無(wú)法證明報(bào)文完整性,可能已遭篡改

所謂完整性是指信息的準(zhǔn)確度。若無(wú)法證明其完整性,通常也就意味著無(wú)法判斷信息是否準(zhǔn)確。HTTP協(xié)議無(wú)法證明通信的報(bào)文完整性,在請(qǐng)求或響應(yīng)送出之后直到對(duì)方接收之前的這段時(shí)間內(nèi),即使請(qǐng)求或響應(yīng)的內(nèi)容遭到篡改,也沒(méi)有辦法獲悉。

比如,從某個(gè)Web網(wǎng)站下載內(nèi)容,是無(wú)法確定客戶端下載的文件和服務(wù)器上存放的文件是否前后一致的。文件內(nèi)容在傳輸途中可能已經(jīng)被篡改為其他的內(nèi)容。即使內(nèi)容真的已改變,作為接收方的客戶端也是覺(jué)察不到的。像這樣,請(qǐng)求或響應(yīng)在傳輸途中,遭攻擊者攔截并篡改內(nèi)容的攻擊稱為中間人攻擊(Man-in-the-Middle attack,MITM)。

(4) 安全的HTTP版本應(yīng)該具備的幾個(gè)特征

由于上述的幾個(gè)問(wèn)題,需要一種能夠提供如下功能的HTTP安全技術(shù):

(1) 服務(wù)器認(rèn)證(客戶端知道它們是在與真正的而不是偽造的服務(wù)器通話);

(2) 客戶端認(rèn)證(服務(wù)器知道它們是在與真正的而不是偽造的客戶端通話);

(3) 完整性(客戶端和服務(wù)器的數(shù)據(jù)不會(huì)被修改);

(4) 加密(客戶端和服務(wù)器的對(duì)話是私密的,無(wú)需擔(dān)心被竊聽(tīng));

(5) 效率(一個(gè)運(yùn)行的足夠快的算法,以便低端的客戶端和服務(wù)器使用);

(6) 普適性(基本上所有的客戶端和服務(wù)器都支持這些協(xié)議);

2.HTTPS的關(guān)鍵技術(shù)

在這樣的需求背景下,HTTPS技術(shù)誕生了。HTTPS協(xié)議的主要功能基本都依賴于TLS/SSL協(xié)議,提供了身份驗(yàn)證、信息加密和完整性校驗(yàn)的功能,可以解決HTTP存在的安全問(wèn)題。本節(jié)就重點(diǎn)探討一下HTTPS協(xié)議的幾個(gè)關(guān)鍵技術(shù)點(diǎn)。

(1) 加密技術(shù)

加密算法一般分為兩種:

對(duì)稱加密:加密與解密的密鑰相同。以DES算法為代表;

非對(duì)稱加密:加密與解密的密鑰不相同。以RSA算法為代表;

對(duì)稱加密強(qiáng)度非常高,一般破解不了,但存在一個(gè)很大的問(wèn)題就是無(wú)法安全地生成和保管密鑰,假如客戶端和服務(wù)器之間每次會(huì)話都使用固定的、相同的密鑰加密和解密,肯定存在很大的安全隱患。

在非對(duì)稱密鑰交換算法出現(xiàn)以前,對(duì)稱加密一個(gè)很大的問(wèn)題就是不知道如何安全生成和保管密鑰。非對(duì)稱密鑰交換過(guò)程主要就是為了解決這個(gè)問(wèn)題,使密鑰的生成和使用更加安全。但同時(shí)也是HTTPS性能和速度嚴(yán)重降低的“罪魁禍?zhǔn)?rdquo;。

HTTPS采用對(duì)稱加密和非對(duì)稱加密兩者并用的混合加密機(jī)制,在交換密鑰環(huán)節(jié)使用非對(duì)稱加密方式,之后的建立通信交換報(bào)文階段則使用對(duì)稱加密方式。

(2) 身份驗(yàn)證--證明公開(kāi)密鑰正確性的證書

非對(duì)稱加密最大的一個(gè)問(wèn)題,就是無(wú)法證明公鑰本身就是貨真價(jià)實(shí)的公鑰。比如,正準(zhǔn)備和某臺(tái)服務(wù)器建立公開(kāi)密鑰加密方式下的通信時(shí),如何證明收到的公開(kāi)密鑰就是原本預(yù)想的那臺(tái)服務(wù)器發(fā)行的公開(kāi)密鑰?;蛟S在公開(kāi)密鑰傳輸途中,真正的公開(kāi)密鑰已經(jīng)被攻擊者替換掉了。

如果不驗(yàn)證公鑰的可靠性,至少會(huì)存在如下的兩個(gè)問(wèn)題:中間人攻擊和信息抵賴。

為了解決上述問(wèn)題,可以使用由數(shù)字證書認(rèn)證機(jī)構(gòu)(CA,Certificate Authority)和其相關(guān)機(jī)關(guān)頒發(fā)的公開(kāi)密鑰證書。

CA使用具體的流程如下:

(1) 服務(wù)器的運(yùn)營(yíng)人員向數(shù)字證書認(rèn)證機(jī)構(gòu)(CA)提出公開(kāi)密鑰的申請(qǐng);

(2) CA通過(guò)線上、線下等多種手段驗(yàn)證申請(qǐng)者提供信息的真實(shí)性,如組織是否存在、企業(yè)是否合法,是否擁有域名的所有權(quán)等;

(3) 如果信息審核通過(guò),CA會(huì)對(duì)已申請(qǐng)的公開(kāi)密鑰做數(shù)字簽名,然后分配這個(gè)已簽名的公開(kāi)密鑰,并將該公開(kāi)密鑰放入公鑰證書后綁定在一起。 證書包含以下信息:申請(qǐng)者公鑰、申請(qǐng)者的組織信息和個(gè)人信息、簽發(fā)機(jī)構(gòu)CA的信息、有效時(shí)間、證書序列號(hào)等信息的明文,同時(shí)包含一個(gè)簽名; 簽名的產(chǎn)生算法:首先,使用散列函數(shù)計(jì)算公開(kāi)的明文信息的信息摘要,然后,采用CA的私鑰對(duì)信息摘要進(jìn)行加密,密文即簽名;

(4) 客戶端在HTTPS握手階段向服務(wù)器發(fā)出請(qǐng)求,要求服務(wù)器返回證書文件;

(5) 客戶端讀取證書中的相關(guān)的明文信息,采用相同的散列函數(shù)計(jì)算得到信息摘要,然后,利用對(duì)應(yīng)CA的公鑰解密簽名數(shù)據(jù),對(duì)比證書的信息摘要,如果一致,則可以確認(rèn)證書的合法性,即公鑰合法;

(6) 客戶端然后驗(yàn)證證書相關(guān)的域名信息、有效時(shí)間等信息;

(7) 客戶端會(huì)內(nèi)置信任CA的證書信息(包含公鑰),如果CA不被信任,則找不到對(duì)應(yīng)CA的證書,證書也會(huì)被判定非法。

在這個(gè)過(guò)程注意幾點(diǎn):

(1) 申請(qǐng)證書不需要提供私鑰,確保私鑰永遠(yuǎn)只能被服務(wù)器掌握;

(2) 證書的合法性仍然依賴于非對(duì)稱加密算法,證書主要是增加了服務(wù)器信息以及簽名;

(3) 內(nèi)置CA對(duì)應(yīng)的證書稱為根證書;頒發(fā)者和使用者相同,自己為自己簽名,叫自簽名證書;

(4) 證書=公鑰+申請(qǐng)者與頒發(fā)者信息+簽名;

3.HTTPS協(xié)議原理

(1) HTTPS的歷史

HTTPS協(xié)議歷史簡(jiǎn)介:

  • (1) SSL協(xié)議的第一個(gè)版本由Netscape公司開(kāi)發(fā),但這個(gè)版本從未發(fā)布過(guò);
  • (2) SSL協(xié)議第二版于1994年11月發(fā)布。第一次部署是在Netscape Navigator1.1瀏覽器上,發(fā)行于1995年3月;
  • (3) SSL 3于1995年年底發(fā)布,雖然名稱與早先的協(xié)議版本相同,但SSL3是完全重新設(shè)計(jì)的協(xié)議,該設(shè)計(jì)一直沿用到今天。
  • (4) TLS 1.0于1999年1月問(wèn)世,與SSL 3相比,版本修改并不大;
  • (5) 2006年4月,下一個(gè)版本TLS 1.1才問(wèn)世,僅僅修復(fù)了一些關(guān)鍵的安全問(wèn)題;
  • (6) 2008年8月,TLS1.2發(fā)布。該版本添加了對(duì)已驗(yàn)證加密的支持,并且基本上刪除了協(xié)議說(shuō)明中所有硬編碼的安全基元,使協(xié)議完全彈性化;

(2) 協(xié)議實(shí)現(xiàn)

宏觀上,TLS以記錄協(xié)議(record protocol)實(shí)現(xiàn)。記錄協(xié)議負(fù)責(zé)在傳輸連接上交換所有的底層消息,并可以配置加密。每一條TLS記錄以一個(gè)短標(biāo)頭起始。標(biāo)頭包含記錄內(nèi)容的類型(或子協(xié)議)、協(xié)議版本和長(zhǎng)度。消息數(shù)據(jù)緊跟在標(biāo)頭之后,如下圖所示:

TLS的主規(guī)格說(shuō)明書定義了四個(gè)核心子協(xié)議:

  • 握手協(xié)議(handshake protocol);
  • 密鑰規(guī)格變更協(xié)議(change cipher spec protocol);
  • 應(yīng)用數(shù)據(jù)協(xié)議(application data protocol);
  • 警報(bào)協(xié)議(alert protocol);

(3) 握手協(xié)議

握手是TLS協(xié)議中最精密復(fù)雜的部分。在這個(gè)過(guò)程中,通信雙方協(xié)商連接參數(shù),并且完成身份驗(yàn)證。根據(jù)使用的功能的不同,整個(gè)過(guò)程通常需要交換6~10條消息。根據(jù)配置和支持的協(xié)議擴(kuò)展的不同,交換過(guò)程可能有許多變種。在使用中經(jīng)??梢杂^察到以下三種流程:

  • (1) 完整的握手,對(duì)服務(wù)器進(jìn)行身份驗(yàn)證(單向驗(yàn)證,最常見(jiàn));
  • (2) 對(duì)客戶端和服務(wù)器都進(jìn)行身份驗(yàn)證的握手(雙向驗(yàn)證);
  • (3) 恢復(fù)之前的會(huì)話采用的簡(jiǎn)短握手;

(4) 單向驗(yàn)證的握手流程

本節(jié)以QQ郵箱的登錄過(guò)程為例,通過(guò)抓包來(lái)對(duì)單向驗(yàn)證的握手流程進(jìn)行分析。單向驗(yàn)證的一次完整的握手流程如下所示:

主要分為四個(gè)步驟:

  • (1) 交換各自支持的功能,對(duì)需要的連接參數(shù)達(dá)成一致;
  • (2) 驗(yàn)證出示的證書,或使用其他方式進(jìn)行身份驗(yàn)證;
  • (3) 對(duì)將用于保護(hù)會(huì)話的共享主密鑰達(dá)成一致;
  • (4) 驗(yàn)證握手消息是否被第三方團(tuán)體修改;

下面對(duì)這一過(guò)程進(jìn)行詳細(xì)的分析。

1.ClientHello

在握手流程中,ClientHello是第一條消息。這條消息將客戶端的功能和首選項(xiàng)傳送給服務(wù)器。包含客戶端支持的SSL的指定版本、加密組件(Cipher Suite)列表(所使用的加密算法及密鑰長(zhǎng)度等)。

2.ServerHello

  ServerHello消息將服務(wù)器選擇的連接參數(shù)傳送回客戶端。這個(gè)消息的結(jié)構(gòu)與ClientHello類似,只是每個(gè)字段只包含一個(gè)選項(xiàng)。服務(wù)器的加密組件內(nèi)容以及壓縮方法等都是從接收到的客戶端加密組件內(nèi)篩選出來(lái)的。

3.Certificate

   之后服務(wù)器發(fā)送Certificate報(bào)文,報(bào)文中包含公開(kāi)密鑰證書,服務(wù)器必須保證它發(fā)送的證書與選擇的算法套件一致。不過(guò)Certificate消息是可選的,因?yàn)椴⒎撬刑准际褂蒙矸蒡?yàn)證,也并非所有身份驗(yàn)證方法都需要證書。

4.ServerKeyExchange

ServerKeyExchange消息的目的是攜帶密鑰交換的額外數(shù)據(jù)。消息內(nèi)容對(duì)于不同的協(xié)商算法套件都會(huì)存在差異。在某些場(chǎng)景中,服務(wù)器不需要發(fā)送任何內(nèi)容,在這些場(chǎng)景中就不需要發(fā)送ServerKeyExchange消息。

5.ServerHelloDone

ServerHelloDone消息表明服務(wù)器已經(jīng)將所有預(yù)計(jì)的握手消息發(fā)送完畢。在此之后,服務(wù)器會(huì)等待客戶端發(fā)送消息。

6.ClientKeyExchange

ClientKeyExchange消息攜帶客戶端為密鑰交換提供的所有信息。這個(gè)消息受協(xié)商的密碼套件的影響,內(nèi)容隨著不同的協(xié)商密碼套件而不同。

7.ChangeCipherSpec

  ChangeCipherSpec消息表明發(fā)送端已取得用以生成連接參數(shù)的足夠信息,已生成加密密鑰,并且將切換到加密模式??蛻舳撕头?wù)器在條件成熟時(shí)都會(huì)發(fā)送這個(gè)消息。注意:ChangeCipherSpec不屬于握手消息,它是另一種協(xié)議,只有一條消息,作為它的子協(xié)議進(jìn)行實(shí)現(xiàn)。

8.Finished

Finished消息意味著握手已經(jīng)完成。消息內(nèi)容將加密,以便雙方可以安全地交換驗(yàn)證整個(gè)握手完整性所需的數(shù)據(jù)??蛻舳撕头?wù)器在條件成熟時(shí)都會(huì)發(fā)送這個(gè)消息。

(5) 雙向驗(yàn)證的握手流程

在一些對(duì)安全性要求更高的場(chǎng)景下,可能會(huì)出現(xiàn)雙向驗(yàn)證的需求。完整的雙向驗(yàn)證流程如下:

 

可以看到,同單向驗(yàn)證流程相比,雙向驗(yàn)證多了如下兩條消息:CertificateRequest與CertificateVerify,其余流程大致相同。

1.Certificate Request

Certificate Request是TLS規(guī)定的一個(gè)可選功能,用于服務(wù)器認(rèn)證客戶端的身份。通過(guò)服務(wù)器要求客戶端發(fā)送一個(gè)證書實(shí)現(xiàn),服務(wù)器應(yīng)該在ServerKeyExchange之后立即發(fā)送CertificateRequest消息。

消息結(jié)構(gòu)如下:

  1. enum { 
  2.     rsa_sign(1), dss_sign(2), rsa_fixed_dh(3),dss_fixed_dh(4), 
  3.     rsa_ephemeral_dh_RESERVED(5),dss_ephemeral_dh_RESERVED(6), 
  4.     fortezza_dms_RESERVED(20), 
  5.     ecdsa_sign(64), rsa_fixed_ecdh(65), 
  6.     ecdsa_fixed_ecdh(66),  
  7.     (255) 
  8. } ClientCertificateType; 
  9.  
  10. opaque DistinguishedName<1..2^16-1>;struct { 
  11.     ClientCertificateType certificate_types<1..2^8-1>; 
  12.     SignatureAndHashAlgorithm 
  13.       supported_signature_algorithms<2^16-1>; 
  14.     DistinguishedName certificate_authorities<0..2^16-1>; 
  15. } CertificateRequest; 

可以選擇發(fā)送一份自己接受的證書頒發(fā)機(jī)構(gòu)列表,這些機(jī)構(gòu)都用其可分辨名稱來(lái)表示.

2.CertificateVerify

當(dāng)需要做客戶端認(rèn)證時(shí),客戶端發(fā)送CertificateVerify消息,來(lái)證明自己確實(shí)擁有客戶端證書的私鑰。這條消息僅僅在客戶端證書有簽名能力的情況下發(fā)送。CertificateVerify必須緊跟在ClientKeyExchange之后。消息結(jié)構(gòu)如下:

  1. struct {  
  2. Signature handshake_messages_signature;  
  3. } CertificateVerify; 

(6) 應(yīng)用數(shù)據(jù)協(xié)議(application data protocol)

應(yīng)用數(shù)據(jù)協(xié)議攜帶著應(yīng)用消息,只以TLS的角度考慮的話,這些就是數(shù)據(jù)緩沖區(qū)。記錄層使用當(dāng)前連接安全參數(shù)對(duì)這些消息進(jìn)行打包、碎片整理和加密。如下圖所示,可以看到傳輸?shù)臄?shù)據(jù)已經(jīng)是經(jīng)過(guò)加密之后的了。

(7) 警報(bào)協(xié)議(alert protocol)

警報(bào)的目的是以簡(jiǎn)單的通知機(jī)制告知對(duì)端通信出現(xiàn)異常狀況。它通常會(huì)攜帶close_notify異常,在連接關(guān)閉時(shí)使用,報(bào)告錯(cuò)誤。警報(bào)非常簡(jiǎn)單,只有兩個(gè)字段:

  1. struct {  
  2.     AlertLevel level;  
  3.     AlertDescription description;  
  4. } Alert; 
  • AlertLevel字段:表示警報(bào)的嚴(yán)重程度;
  • AlertDescription:直接表示警報(bào)代碼;

4.在Android中使用HTTPS的常見(jiàn)問(wèn)題

(1) 服務(wù)器證書驗(yàn)證錯(cuò)誤

這是最常見(jiàn)的一種問(wèn)題,通常會(huì)拋出如下類型的異常:

出現(xiàn)此類錯(cuò)誤通常可能由以下的三種原因?qū)е拢?/p>

  • (1) 頒發(fā)服務(wù)器證書的CA未知;
  • (2) 服務(wù)器證書不是由CA簽署的,而是自簽署(比較常見(jiàn));
  • (3) 服務(wù)器配置缺少中間 CA;

當(dāng)服務(wù)器的CA不被系統(tǒng)信任時(shí),就會(huì)發(fā)生 SSLHandshakeException。可能是購(gòu)買的CA證書比較新,Android系統(tǒng)還未信任,也可能是服務(wù)器使用的是自簽名證書(這個(gè)在測(cè)試階段經(jīng)常遇到)。

解決此類問(wèn)題常見(jiàn)的做法是:指定HttpsURLConnection信任特定的CA集合。在本文的第5部分代碼實(shí)現(xiàn)模塊,會(huì)詳細(xì)的講解如何讓Android應(yīng)用信任自簽名證書集合或者跳過(guò)證書校驗(yàn)的環(huán)節(jié)。

(2) 域名驗(yàn)證失敗

SSL連接有兩個(gè)關(guān)鍵環(huán)節(jié)。首先是驗(yàn)證證書是否來(lái)自值得信任的來(lái)源,其次確保正在通信的服務(wù)器提供正確的證書。如果沒(méi)有提供,通常會(huì)看到類似于下面的錯(cuò)誤:

出現(xiàn)此類問(wèn)題的原因通常是由于服務(wù)器證書中配置的域名和客戶端請(qǐng)求的域名不一致所導(dǎo)致的。

有兩種解決方案:

(1) 重新生成服務(wù)器的證書,用真實(shí)的域名信息;

(2) 自定義HostnameVerifier,在握手期間,如果URL的主機(jī)名和服務(wù)器的標(biāo)識(shí)主機(jī)名不匹配,則驗(yàn)證機(jī)制可以回調(diào)此接口的實(shí)現(xiàn)程序來(lái)確定是否應(yīng)該允許此連接。可以通過(guò)自定義HostnameVerifier實(shí)現(xiàn)一個(gè)白名單的功能。

代碼如下:

  1. HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() { 
  2.   @Override 
  3.   public boolean verify(String hostname, SSLSession session) { 
  4.     // 設(shè)置接受的域名集合 
  5.     if (hostname.equals(...))  { 
  6.          return true
  7.     } 
  8.   } 
  9. }; 
  10.  
  11. HttpsURLConnection.setDefaultHostnameVerifier(DO_NOT_VERIFY); 

(3) 客戶端證書驗(yàn)證

SSL支持服務(wù)端通過(guò)驗(yàn)證客戶端的證書來(lái)確認(rèn)客戶端的身份。這種技術(shù)與TrustManager的特性相似。本文將在第5部分代碼實(shí)現(xiàn)模塊,講解如何讓Android應(yīng)用支持客戶端證書驗(yàn)證的方式。

(4) Android上TLS版本兼容問(wèn)題

之前在接口聯(lián)調(diào)的過(guò)程中,測(cè)試那邊反饋過(guò)一個(gè)問(wèn)題是在Android 4.4以下的系統(tǒng)出現(xiàn)HTTPS請(qǐng)求不成功而在4.4以上的系統(tǒng)上卻正常的問(wèn)題。相應(yīng)的錯(cuò)誤如下:

  1. 03-09 09:21:38.427: W/System.err(2496): javax.net.ssl.SSLHandshakeException: javax.net.ssl.SSLProtocolException: SSL handshake aborted: ssl=0xb7fa0620: Failure in SSL library, usually a protocol error 
  2.  
  3. 03-09 09:21:38.427: W/System.err(2496): error:14077410:SSL routines:SSL23_GET_SERVER_HELLO:sslv3 alert handshake failure (external/openssl/ssl/s23_clnt.c:741 0xa90e6990:0x00000000) 

按照官方文檔的描述,Android系統(tǒng)對(duì)SSL協(xié)議的版本支持如下:

也就是說(shuō),按官方的文檔顯示,在API 16+以上,TLS1.1和TLS1.2是默認(rèn)開(kāi)啟的。但是實(shí)際上在API 20+以上才默認(rèn)開(kāi)啟,4.4以下的版本是無(wú)法使用TLS1.1和TLS 1.2的,這也是Android系統(tǒng)的一個(gè)bug。

參照stackoverflow上的一些方式,比較好的一種解決方案如下:

  1. SSLSocketFactory noSSLv3Factory; 
  2. if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { 
  3.     noSSLv3Factory = new TLSSocketFactory(mSSLContext.getSSLSocket().getSocketFactory()); 
  4. else { 
  5.     noSSLv3Factory = mSSLContext.getSSLSocket().getSocketFactory(); 

對(duì)于4.4以下的系統(tǒng),使用自定義的TLSSocketFactory,開(kāi)啟對(duì)TLS1.1和TLS1.2的支持,核心代碼:

  1. public class TLSSocketFactory extends SSLSocketFactory { 
  2.  
  3.     private SSLSocketFactory internalSSLSocketFactory; 
  4.  
  5.     public TLSSocketFactory() throws KeyManagementException, NoSuchAlgorithmException { 
  6.         SSLContext context = SSLContext.getInstance("TLS"); 
  7.         context.init(nullnullnull); 
  8.         internalSSLSocketFactory = context.getSocketFactory(); 
  9.     } 
  10.  
  11.     public TLSSocketFactory(SSLSocketFactory delegate) throws KeyManagementException, NoSuchAlgorithmException { 
  12.         internalSSLSocketFactory = delegate; 
  13.     } 
  14.  
  15.     ...... 
  16.  
  17.     @Override 
  18.     public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { 
  19.         return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort)); 
  20.     } 
  21.  
  22.     // 開(kāi)啟對(duì)TLS1.1和TLS1.2的支持 
  23.     private Socket enableTLSOnSocket(Socket socket) { 
  24.         if(socket != null && (socket instanceof SSLSocket)) { 
  25.             ((SSLSocket)socket).setEnabledProtocols(new String[] {"TLSv1.1""TLSv1.2"}); 
  26.         } 
  27.         return socket; 
  28.     } 

5.代碼實(shí)現(xiàn)

本部分主要基于第四部分提出的Android應(yīng)用中使用HTTPS遇到的一些常見(jiàn)的問(wèn)題,給出一個(gè)比較系統(tǒng)的解決方案。

(1) 整體結(jié)構(gòu)

不管是使用自簽名證書,還是采取客戶端身份驗(yàn)證,核心都是創(chuàng)建一個(gè)自己的KeyStore,然后使用這個(gè)KeyStore創(chuàng)建一個(gè)自定義的SSLContext。整體類圖如下:

 

類圖中的MySSLContext可以應(yīng)用在HTTPUrlConnection的方式與服務(wù)端連接的過(guò)程中:

  1. if (JarConfig.__self_signed_https) { 
  2.     SSLContextByTrustAll mSSLContextByTrustAll = new SSLContextByTrustAll(); 
  3.     MySSLContext mSSLContext = new MySSLContext(mSSLContextByTrustAll); 
  4.    SSLSocketFactory noSSLv3Factory; 
  5.    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) { 
  6.         noSSLv3Factory = new TLSSocketFactory(mSSLContext.getSSLSocket().getSocketFactory()); 
  7.     } else { 
  8.         noSSLv3Factory = mSSLContext.getSSLSocket().getSocketFactory(); 
  9.     } 
  10.  
  11.     httpsURLConnection.setSSLSocketFactory(noSSLv3Factory); 
  12.     httpsURLConnection.setHostnameVerifier(MY_DOMAIN_VERIFY); 
  13. }else { 
  14.     httpsURLConnection.setSSLSocketFactory((SSLSocketFactory) SSLSocketFactory.getDefault()); 
  15.     httpsURLConnection.setHostnameVerifier(DO_NOT_VERIFY); 

核心是通過(guò)httpsURLConnection.setSSLSocketFactory使用自定義的校驗(yàn)邏輯。整體設(shè)計(jì)上使用策略模式?jīng)Q定采用哪種驗(yàn)證機(jī)制:

  • makeContextWithCilentAndServer 單向驗(yàn)證方式(自定義信任的證書集合)
  • makeContextWithServer 雙向驗(yàn)證方式(自定義信任的證書集合,并使用客戶端證書)
  • makeContextToTrustAll (信任所有的CA證書,不安全,僅供測(cè)試階段使用)

(2) 單向驗(yàn)證并自定義信任的證書集合

在App中,把服務(wù)端證書放到資源文件下(通常是asset目錄下,因?yàn)樽C書對(duì)于每一個(gè)用戶來(lái)說(shuō)都是相同的,并且也不會(huì)經(jīng)常發(fā)生改變),但是也可以放在設(shè)備的外部存儲(chǔ)上。

  1. public class SSLContextWithServer implements GetSSLSocket { 
  2.  
  3.     // 在這里進(jìn)行服務(wù)器正式的名稱的配置 
  4.     private String[] serverCertificateNames = {"serverCertificateNames1" ,"serverCertificateNames2"}; 
  5.  
  6.     @Override 
  7.     public SSLContext getSSLSocket() { 
  8.         String[] caCertString = new String[serverCertificateNames.length]; 
  9.         for(int i = 0 ; i < serverCertificateNames.length ; i++) { 
  10.             try { 
  11.                 caCertString[i] = readCaCert(serverCertificateNames[i]); 
  12.             } catch(Exception e) { 
  13.  
  14.             } 
  15.         } 
  16.         SSLContext mSSLContext = null
  17.         try { 
  18.             mSSLContext = SSLContextFactory.getInstance().makeContextWithServer(caCertString); 
  19.         } catch(Exception e) { 
  20.  
  21.         } 
  22.         return mSSLContext; 
  23.     } 

serverCertificateNames中定義了App所信任的證書名稱(這些證書文件必須要放在指定的文件路徑下,并其要保證名稱相同),而后就可以加載服務(wù)端證書鏈到keystore,通過(guò)獲取到的可信任并帶有服務(wù)端證書的keystore,就可以用它來(lái)初始化自定義的SSLContext了:

  1. @Override 
  2.     public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { 
  3.         try { 
  4.             originalX509TrustManager.checkServerTrusted(chain, authType); 
  5.         } catch(CertificateException originalException) { 
  6.             try { 
  7.                 X509Certificate[] reorderedChain = reorderCertificateChain(chain); 
  8.                 CertPathValidator validator = CertPathValidator.getInstance("PKIX"); 
  9.                 CertificateFactory factory = CertificateFactory.getInstance("X509"); 
  10.                 CertPath certPath = factory.generateCertPath(Arrays.asList(reorderedChain)); 
  11.                 PKIXParameters params = new PKIXParameters(trustStore); 
  12.                 params.setRevocationEnabled(false); 
  13.                 validator.validate(certPath, params); 
  14.             } catch(Exception ex) { 
  15.                 throw originalException; 
  16.             } 
  17.         } 
  18.     } 

(3) 跳過(guò)證書校驗(yàn)過(guò)程

和上面的過(guò)程類似,只不過(guò)這里提供的TrustManager不需要提供信任的證書集合,默認(rèn)接受任意客戶端證書即可:

  1. public class AcceptAllTrustManager implements X509TrustManager { 
  2.  
  3.     @Override 
  4.     public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { 
  5.         //do nothing,接受任意客戶端證書 
  6.     } 
  7.  
  8.     @Override 
  9.     public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { 
  10.         //do nothing,接受任意服務(wù)端證書 
  11.     } 
  12.  
  13.     @Override 
  14.     public X509Certificate[] getAcceptedIssuers() { 
  15.         return null
  16.     } 

而后構(gòu)造相應(yīng)的SSLContext:

  1. public SSLContext makeContextToTrustAll() throws Exception { 
  2.         AcceptAllTrustManager tm = new AcceptAllTrustManager(); 
  3.         SSLContext sslContext = SSLContext.getInstance("TLS"); 
  4.         sslContext.init(null, new TrustManager[] { tm }, null); 
  5.  
  6.         return sslContext; 

 

責(zé)任編輯:武曉燕 來(lái)源: 騰訊Bugly博客
相關(guān)推薦

2022-03-24 10:23:51

時(shí)間輪方法任務(wù)

2017-09-01 15:21:18

Raft算法CMQ應(yīng)用

2017-09-01 15:49:41

Raft算法CMQ

2017-01-17 09:38:52

ZooKeeperHadoopHBase

2009-06-04 20:36:03

CheckStyle的Eclipse中的集成

2009-06-16 15:20:48

ApplicationJSP程序

2019-05-17 09:20:01

2019-04-09 11:07:15

HTTPS加密安全

2019-08-05 13:20:35

Android繪制代碼

2021-03-06 14:22:39

池化對(duì)象類庫(kù)

2011-08-29 17:27:47

HTML 5交互移動(dòng)應(yīng)用

2023-12-25 19:28:59

RocketMQ大數(shù)據(jù)

2009-09-28 13:23:54

HTTP編程

2009-09-11 11:33:58

C# WinForm控Attribute

2024-03-25 03:00:00

.NET 6await編程

2019-08-20 14:01:22

HTTPSSSL協(xié)議

2014-06-13 11:22:18

Https

2010-02-04 15:29:40

Android實(shí)用程序

2024-06-27 08:26:10

LooperAndroid內(nèi)存

2022-03-22 09:16:24

HTTPS數(shù)據(jù)安全網(wǎng)絡(luò)協(xié)議
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)