前言
在互聯(lián)網(wǎng)迅速發(fā)展的年代,基本上天天都在跟網(wǎng)絡(luò)打交道。那么,在網(wǎng)絡(luò)的通訊中怎么保證信息的安全性呢?這篇文章,我們就來講講,Alamofire作為iOS開發(fā)中一個(gè)非常優(yōu)秀的網(wǎng)絡(luò)請(qǐng)求相關(guān)的第三方庫,它的安全策略是怎么設(shè)計(jì)和使用的。
HTTPS簡(jiǎn)介
在切入正題之前,先來簡(jiǎn)單的了解一下HTTPS相關(guān)知識(shí),方便對(duì)后面內(nèi)容的理解。如果你已經(jīng)了解了,可以直接跳過這一段。
為什么使用HTTPS
在以前,我們用的更多的是HTTP,那么是什么原因蘋果公司也主推我們使用HTTPS這個(gè)更安全請(qǐng)求方式的呢?HTTP存在的問題:
- 通訊使用明文,內(nèi)容容易被竊聽
- 不驗(yàn)證通訊雙方的身份,容易被偽裝
- 無法驗(yàn)證數(shù)據(jù)完整性,可能會(huì)被篡改數(shù)據(jù)
使用 HTTPS 通信機(jī)制可以有效地防止這些問題。HTTPS 并非是應(yīng)用層的一種新協(xié)議,只是HTTP通信接口部分用 SSL 和 TLS 協(xié)議代替而已。通常 HTTP 直接跟 TCP 通信,當(dāng)使用 SSL 時(shí),則變成先和 SSL 通信,再由 SSL 和 TCP 通信了。簡(jiǎn)言之,HTTPS 就是身披 SSL 協(xié)議這層外殼的 HTTP。

HTTP+數(shù)據(jù)加密+身份認(rèn)證+數(shù)據(jù)完整性保護(hù)=HTTPS
HTTPS加密方式
HTTPS 采用混合加密機(jī)制,就是共享秘鑰加密(對(duì)稱加密)和公開密鑰加密(非對(duì)稱加密)兩者并用的混合加密機(jī)制。在交換密鑰環(huán)節(jié)使用公開密鑰加密的方式,之后建立通訊交換報(bào)文階段則使用共享密鑰加密。公開密鑰加密比共享密鑰加密更加安全,那為什么不全用公開密鑰加密?因?yàn)榕c共享秘鑰加密相比,處理起來更復(fù)雜、處理速度慢。HTTPS結(jié)合了兩種加密方式的優(yōu)劣來實(shí)現(xiàn)一種混合加密的流程。如圖所示:

HTTPS驗(yàn)證和加密流程
HTTPS有單向認(rèn)證和雙向認(rèn)證,原理基本差不多,這里就講一下單向認(rèn)證的整個(gè)流程,先看一張圖:

- 客戶端發(fā)起
HTTPS請(qǐng)求:客戶端向服務(wù)端發(fā)送SSL協(xié)議版本號(hào)、加密算法種類、隨機(jī)數(shù)等信息。 - 服務(wù)端的配置:采用
HTTPS協(xié)議的服務(wù)器必須要有一套數(shù)字證書,可以自己制作,也可以向組織申請(qǐng)。區(qū)別就是自己頒發(fā)的證書需要客戶端驗(yàn)證通過,才可以繼續(xù)訪問,而使用受信任的公司申請(qǐng)的證書可以直接通過。這套證書其實(shí)就是一對(duì)公鑰和私鑰。 - 傳送證書:這個(gè)證書其實(shí)就是公鑰,只是包含了很多信息,如證書的頒發(fā)機(jī)構(gòu),過期時(shí)間等等。同時(shí)服務(wù)端也會(huì)向客戶端發(fā)送
SSL協(xié)議版本號(hào)、加密算法種類、隨機(jī)數(shù)等信息 - 客戶端解析證書:這部分工作是由客戶端的
TLS來完成的,首先會(huì)驗(yàn)證公鑰是否有效,比如頒發(fā)機(jī)構(gòu),過期時(shí)間等等,如果發(fā)現(xiàn)異常,則會(huì)拋出一個(gè)警告,提示證書存在問題。如果證書沒有問題,那么就生成一個(gè)隨機(jī)值,然后用證書對(duì)該隨機(jī)值進(jìn)行加密。 - 傳送加密信息:這部分傳送的是用證書加密后的隨機(jī)值,目的就是讓服務(wù)端得到這個(gè)隨機(jī)值,以后客戶端和服務(wù)端的通信就可以通過這個(gè)隨機(jī)值來進(jìn)行加密解密了。
- 服務(wù)段解密信息:服務(wù)端用私鑰解密后,得到了客戶端傳過來的隨機(jī)值(私鑰),然后把內(nèi)容通過該值進(jìn)行對(duì)稱加密。
- 傳輸加密后的信息:這部分信息是服務(wù)段用私鑰加密后的信息,可以在客戶端被還原。
- 客戶端解密信息:客戶端用之前生成的私鑰解密服務(wù)段傳過來的信息,于是獲取了解密后的內(nèi)容。整個(gè)過程第三方即使監(jiān)聽到了數(shù)據(jù),也束手無策。
Alamofire的安全認(rèn)證
證書驗(yàn)證模式
如果使用的是自簽證書需要我們進(jìn)行安全認(rèn)證,如果是CA機(jī)構(gòu)頒發(fā)的證書是不需要我們寫安全認(rèn)證的相關(guān)代碼的。
舉個(gè)Alamofire發(fā)起HTTPS請(qǐng)求的栗子??:
let serverTrustPlolicies: [String: ServerTrustPolicy] = [
hostUrl: .pinCertificates(
certificates: ServerTrustPolicy.certificates(),
validateCertificateChain: true,
validateHost: true)
]
self.sessionManager = SessionManager(serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPlolicies))
self.sessionManager?.request(urlString).response { (defaultResponse) in
print(defaultResponse)
}
- 使用起來也是非常的簡(jiǎn)單,跟
HTTP相比,只是在初始化SessionManager的時(shí)候傳入了一個(gè)ServerTrustPolicyManager對(duì)象,它是證書信任策略的管理者。 - 初始化
ServerTrustPolicyManager對(duì)象的時(shí)候,傳入了一個(gè)[String: ServerTrustPolicy]類型的集合作為參數(shù)并保存,key是主機(jī)地址,value是驗(yàn)證模式。
public enum ServerTrustPolicy {
case performDefaultEvaluation(validateHost: Bool)
case performRevokedEvaluation(validateHost: Bool, revocationFlags: CFOptionFlags)
case pinCertificates(certificates: [SecCertificate], validateCertificateChain: Bool, validateHost: Bool)
case pinPublicKeys(publicKeys: [SecKey], validateCertificateChain: Bool, validateHost: Bool)
case disableEvaluation
case customEvaluation((_ serverTrust: SecTrust, _ host: String) -> Bool)
}
-
Alamofire安全認(rèn)證策略的六種模式,其中最常用的有這三種:.pinCertificates證書驗(yàn)證模式、.pinPublicKeys公鑰驗(yàn)證模式和.disableEvaluation不驗(yàn)證模式。-
.performDefaultEvaluation默認(rèn)策略,只有合法證書才能通過驗(yàn)證 -
.performRevokedEvaluation對(duì)注銷證書做的一種額外設(shè)置 -
.pinCertificates證書驗(yàn)證模式,代表客戶端會(huì)將服務(wù)器返回的證書和本地保存的證書中的 所有內(nèi)容 全部進(jìn)行校驗(yàn),如果正確,才繼續(xù)執(zhí)行。 -
.pinPublicKeys公鑰驗(yàn)證模式,代表客戶端會(huì)將服務(wù)器返回的證書和本地保存的證書中的 PublicKey部分 進(jìn)行校驗(yàn),如果正確,才繼續(xù)執(zhí)行。 -
.disableEvaluation該選項(xiàng)下驗(yàn)證一直都是通過的,無條件信任。 -
.customEvaluation自定義驗(yàn)證,需要返回一個(gè)布爾類型的結(jié)果。
-
- 前面的例子就是使用的
.pinCertificates證書驗(yàn)證模式。它有三個(gè)關(guān)聯(lián)值:- 參數(shù)1:
certificates代表的是證書 - 參數(shù)2:
validateCertificateChain代表是否驗(yàn)證證書鏈 - 參數(shù)3:
validateHost代表是否驗(yàn)證子地址
- 參數(shù)1:
- 那么,我們?cè)趺凑业巾?xiàng)目中的證書呢?
Alamofire為我們提供了一個(gè)方法ServerTrustPolicy.certificates()方便使用。
public static func certificates(in bundle: Bundle = Bundle.main) -> [SecCertificate] {
var certificates: [SecCertificate] = []
let paths = Set([".cer", ".CER", ".crt", ".CRT", ".der", ".DER"].map { fileExtension in
bundle.paths(forResourcesOfType: fileExtension, inDirectory: nil)
}.joined())
for path in paths {
if
let certificateData = try? Data(contentsOf: URL(fileURLWithPath: path)) as CFData,
let certificate = SecCertificateCreateWithData(nil, certificateData)
{
certificates.append(certificate)
}
}
return certificates
}
- 默認(rèn)是在
Bundle.main中查找,我們也可以指定存放證書的Bundle來查找。 - 了解了驗(yàn)證模式之后,來看看驗(yàn)證的流程。當(dāng)發(fā)起請(qǐng)求之后,會(huì)回調(diào)
URLSessionTaskDelegate的下面這個(gè)方法。
open func urlSession(
_ session: URLSession,
task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
{
guard taskDidReceiveChallengeWithCompletion == nil else {
taskDidReceiveChallengeWithCompletion?(session, task, challenge, completionHandler)
return
}
if let taskDidReceiveChallenge = taskDidReceiveChallenge {
let result = taskDidReceiveChallenge(session, task, challenge)
completionHandler(result.0, result.1)
} else if let delegate = self[task]?.delegate {
delegate.urlSession(
session,
task: task,
didReceive: challenge,
completionHandler: completionHandler
)
} else {
urlSession(session, didReceive: challenge, completionHandler: completionHandler)
}
}
如果
taskDidReceiveChallengeWithCompletion有值的話,直接回調(diào)這個(gè)閉包,這就說明我們可以自己實(shí)現(xiàn)驗(yàn)證的邏輯??梢?code>Alamofire是非常的友好的,既提供了常規(guī)實(shí)現(xiàn)方式,也支持開發(fā)者自定義實(shí)現(xiàn)。-
繼續(xù)跟蹤代碼進(jìn)入到
delegate.urlSession方法里面 然后會(huì)執(zhí)行紅框所示的方法,獲取的是前面設(shè)置的驗(yàn)證模式,然后執(zhí)行
evaluate方法
public func evaluate(_ serverTrust: SecTrust, forHost host: String) -> Bool {
var serverTrustIsValid = false
switch self {
// 省略無法代碼.....
case let .pinCertificates(pinnedCertificates, validateCertificateChain, validateHost):
if validateCertificateChain {
let policy = SecPolicyCreateSSL(true, validateHost ? host as CFString : nil)
SecTrustSetPolicies(serverTrust, policy)
SecTrustSetAnchorCertificates(serverTrust, pinnedCertificates as CFArray)
SecTrustSetAnchorCertificatesOnly(serverTrust, true)
serverTrustIsValid = trustIsValid(serverTrust)
} else {
let serverCertificatesDataArray = certificateData(for: serverTrust)
let pinnedCertificatesDataArray = certificateData(for: pinnedCertificates)
outerLoop: for serverCertificateData in serverCertificatesDataArray {
for pinnedCertificateData in pinnedCertificatesDataArray {
if serverCertificateData == pinnedCertificateData {
serverTrustIsValid = true
break outerLoop
}
}
}
}
// 省略無法代碼.....
return serverTrustIsValid
}
- 如果需要驗(yàn)證證書鏈的話,會(huì)執(zhí)行
if代碼塊,大概步驟如下:-
SecPolicyCreateSSL:創(chuàng)建策略,是否驗(yàn)證host -
SecTrustSetPolicies:為待驗(yàn)證的對(duì)象設(shè)置策略 -
trustIsValid:進(jìn)行驗(yàn)證,成功返回YES
-
- 如果需要驗(yàn)證證書鏈的話,會(huì)先把服務(wù)器證書和自己的證書處理成證書二進(jìn)制數(shù)組,然后進(jìn)行對(duì)比。
- 如果驗(yàn)證通過就可以開始正常請(qǐng)求數(shù)據(jù),驗(yàn)證失敗就會(huì)終止請(qǐng)求,拋出異常。
公鑰驗(yàn)證模式
- 公鑰驗(yàn)證模式跟證書驗(yàn)證模式使用起來差不多,使用
.pinPublicKeys這個(gè)枚舉值,第一個(gè)參數(shù)傳公鑰,其他參數(shù)和證書驗(yàn)證模式一樣。Alamofire同樣為我們提供了獲取公鑰的方法ServerTrustPolicy.publicKeys(),直接調(diào)用這個(gè)方法即可。 - 那么什么時(shí)候使用公鑰驗(yàn)證模式呢?使用公鑰驗(yàn)證模式的好處是:只要公鑰不變,就可以一直使用,不用更新證書,不用擔(dān)心證書過期了。
總結(jié)
這篇文章大概聊了一下HTTPS的加密流程,以及通過如何Alamofire請(qǐng)求HTTPS的接口和網(wǎng)絡(luò)挑戰(zhàn)流程的分析。當(dāng)然,如果想徹底明白HTTPS的實(shí)現(xiàn)方式,還需繼續(xù)學(xué)習(xí)。推薦一本書《圖解HTTP》,講的非常通俗易懂,文章的幾張圖片也是來源于這本書。
有問題或者建議和意見,歡迎大家評(píng)論或者私信。
喜歡的朋友可以點(diǎn)下關(guān)注和喜歡,后續(xù)會(huì)持續(xù)更新文章。
