前言
在互聯(lián)網(wǎng)迅速發(fā)展的年代欧瘪,基本上天天都在跟網(wǎng)絡(luò)打交道葬毫。那么题篷,在網(wǎng)絡(luò)的通訊中怎么保證信息的安全性呢词身?這篇文章,我們就來講講悼凑,Alamofire
作為iOS
開發(fā)中一個非常優(yōu)秀的網(wǎng)絡(luò)請求相關(guān)的第三方庫偿枕,它的安全策略是怎么設(shè)計和使用的。
HTTPS簡介
在切入正題之前户辫,先來簡單的了解一下HTTPS
相關(guān)知識渐夸,方便對后面內(nèi)容的理解。如果你已經(jīng)了解了渔欢,可以直接跳過這一段墓塌。
為什么使用HTTPS
在以前,我們用的更多的是HTTP
奥额,那么是什么原因蘋果公司也主推我們使用HTTPS
這個更安全請求方式的呢苫幢?HTTP
存在的問題:
- 通訊使用明文,內(nèi)容容易被竊聽
- 不驗證通訊雙方的身份垫挨,容易被偽裝
- 無法驗證數(shù)據(jù)完整性韩肝,可能會被篡改數(shù)據(jù)
使用 HTTPS
通信機制可以有效地防止這些問題。HTTPS
并非是應(yīng)用層的一種新協(xié)議九榔,只是HTTP
通信接口部分用 SSL
和 TLS
協(xié)議代替而已哀峻。通常 HTTP
直接跟 TCP
通信涡相,當(dāng)使用 SSL
時,則變成先和 SSL
通信剩蟀,再由 SSL
和 TCP
通信了催蝗。簡言之,HTTPS
就是身披 SSL
協(xié)議這層外殼的 HTTP
育特。
HTTP+數(shù)據(jù)加密+身份認(rèn)證+數(shù)據(jù)完整性保護(hù)=HTTPS
HTTPS加密方式
HTTPS
采用混合加密機制丙号,就是共享秘鑰加密(對稱加密)和公開密鑰加密(非對稱加密)兩者并用的混合加密機制。在交換密鑰環(huán)節(jié)使用公開密鑰加密的方式缰冤,之后建立通訊交換報文階段則使用共享密鑰加密犬缨。公開密鑰加密比共享密鑰加密更加安全,那為什么不全用公開密鑰加密锋谐?因為與共享秘鑰加密相比遍尺,處理起來更復(fù)雜、處理速度慢涮拗。HTTPS
結(jié)合了兩種加密方式的優(yōu)劣來實現(xiàn)一種混合加密的流程乾戏。如圖所示:
HTTPS驗證和加密流程
HTTPS
有單向認(rèn)證和雙向認(rèn)證,原理基本差不多三热,這里就講一下單向認(rèn)證的整個流程鼓择,先看一張圖:
- 客戶端發(fā)起
HTTPS
請求:客戶端向服務(wù)端發(fā)送SSL
協(xié)議版本號、加密算法種類就漾、隨機數(shù)等信息呐能。 - 服務(wù)端的配置:采用
HTTPS
協(xié)議的服務(wù)器必須要有一套數(shù)字證書,可以自己制作抑堡,也可以向組織申請摆出。區(qū)別就是自己頒發(fā)的證書需要客戶端驗證通過,才可以繼續(xù)訪問首妖,而使用受信任的公司申請的證書可以直接通過偎漫。這套證書其實就是一對公鑰和私鑰。 - 傳送證書:這個證書其實就是公鑰有缆,只是包含了很多信息象踊,如證書的頒發(fā)機構(gòu),過期時間等等棚壁。同時服務(wù)端也會向客戶端發(fā)送
SSL
協(xié)議版本號杯矩、加密算法種類、隨機數(shù)等信息 - 客戶端解析證書:這部分工作是由客戶端的
TLS
來完成的袖外,首先會驗證公鑰是否有效史隆,比如頒發(fā)機構(gòu),過期時間等等曼验,如果發(fā)現(xiàn)異常泌射,則會拋出一個警告头镊,提示證書存在問題。如果證書沒有問題魄幕,那么就生成一個隨機值,然后用證書對該隨機值進(jìn)行加密颖杏。 - 傳送加密信息:這部分傳送的是用證書加密后的隨機值纯陨,目的就是讓服務(wù)端得到這個隨機值,以后客戶端和服務(wù)端的通信就可以通過這個隨機值來進(jìn)行加密解密了留储。
- 服務(wù)段解密信息:服務(wù)端用私鑰解密后翼抠,得到了客戶端傳過來的隨機值(私鑰),然后把內(nèi)容通過該值進(jìn)行對稱加密获讳。
- 傳輸加密后的信息:這部分信息是服務(wù)段用私鑰加密后的信息阴颖,可以在客戶端被還原。
- 客戶端解密信息:客戶端用之前生成的私鑰解密服務(wù)段傳過來的信息丐膝,于是獲取了解密后的內(nèi)容量愧。整個過程第三方即使監(jiān)聽到了數(shù)據(jù),也束手無策帅矗。
Alamofire的安全認(rèn)證
證書驗證模式
如果使用的是自簽證書需要我們進(jìn)行安全認(rèn)證偎肃,如果是CA機構(gòu)頒發(fā)的證書是不需要我們寫安全認(rèn)證的相關(guān)代碼的。
舉個Alamofire
發(fā)起HTTPS
請求的栗子??:
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)
}
- 使用起來也是非常的簡單浑此,跟
HTTP
相比累颂,只是在初始化SessionManager
的時候傳入了一個ServerTrustPolicyManager
對象,它是證書信任策略的管理者凛俱。 - 初始化
ServerTrustPolicyManager
對象的時候紊馏,傳入了一個[String: ServerTrustPolicy]
類型的集合作為參數(shù)并保存,key
是主機地址,value
是驗證模式蒲犬。
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
證書驗證模式、.pinPublicKeys
公鑰驗證模式和.disableEvaluation
不驗證模式暖哨。-
.performDefaultEvaluation
默認(rèn)策略赌朋,只有合法證書才能通過驗證 -
.performRevokedEvaluation
對注銷證書做的一種額外設(shè)置 -
.pinCertificates
證書驗證模式,代表客戶端會將服務(wù)器返回的證書和本地保存的證書中的 所有內(nèi)容 全部進(jìn)行校驗篇裁,如果正確沛慢,才繼續(xù)執(zhí)行。 -
.pinPublicKeys
公鑰驗證模式达布,代表客戶端會將服務(wù)器返回的證書和本地保存的證書中的 PublicKey部分 進(jìn)行校驗团甲,如果正確,才繼續(xù)執(zhí)行黍聂。 -
.disableEvaluation
該選項下驗證一直都是通過的躺苦,無條件信任身腻。 -
.customEvaluation
自定義驗證,需要返回一個布爾類型的結(jié)果匹厘。
-
- 前面的例子就是使用的
.pinCertificates
證書驗證模式嘀趟。它有三個關(guān)聯(lián)值:- 參數(shù)1:
certificates
代表的是證書 - 參數(shù)2:
validateCertificateChain
代表是否驗證證書鏈 - 參數(shù)3:
validateHost
代表是否驗證子地址
- 參數(shù)1:
- 那么,我們怎么找到項目中的證書呢愈诚?
Alamofire
為我們提供了一個方法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
來查找炕柔。 - 了解了驗證模式之后酌泰,來看看驗證的流程。當(dāng)發(fā)起請求之后匕累,會回調(diào)
URLSessionTaskDelegate
的下面這個方法陵刹。
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)這個閉包欢嘿,這就說明我們可以自己實現(xiàn)驗證的邏輯衰琐。可見Alamofire
是非常的友好的际插,既提供了常規(guī)實現(xiàn)方式碘耳,也支持開發(fā)者自定義實現(xiàn)。-
繼續(xù)跟蹤代碼進(jìn)入到
delegate.urlSession
方法里面 然后會執(zhí)行紅框所示的方法框弛,獲取的是前面設(shè)置的驗證模式,然后執(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
}
- 如果需要驗證證書鏈的話辛辨,會執(zhí)行
if
代碼塊,大概步驟如下:-
SecPolicyCreateSSL
:創(chuàng)建策略瑟枫,是否驗證host -
SecTrustSetPolicies
:為待驗證的對象設(shè)置策略 -
trustIsValid
:進(jìn)行驗證斗搞,成功返回YES
-
- 如果需要驗證證書鏈的話,會先把服務(wù)器證書和自己的證書處理成證書二進(jìn)制數(shù)組慷妙,然后進(jìn)行對比僻焚。
- 如果驗證通過就可以開始正常請求數(shù)據(jù),驗證失敗就會終止請求膝擂,拋出異常虑啤。
公鑰驗證模式
- 公鑰驗證模式跟證書驗證模式使用起來差不多,使用
.pinPublicKeys
這個枚舉值架馋,第一個參數(shù)傳公鑰狞山,其他參數(shù)和證書驗證模式一樣。Alamofire
同樣為我們提供了獲取公鑰的方法ServerTrustPolicy.publicKeys()
叉寂,直接調(diào)用這個方法即可萍启。 - 那么什么時候使用公鑰驗證模式呢?使用公鑰驗證模式的好處是:只要公鑰不變,就可以一直使用勘纯,不用更新證書局服,不用擔(dān)心證書過期了。
總結(jié)
這篇文章大概聊了一下HTTPS
的加密流程驳遵,以及通過如何Alamofire
請求HTTPS
的接口和網(wǎng)絡(luò)挑戰(zhàn)流程的分析淫奔。當(dāng)然,如果想徹底明白HTTPS
的實現(xiàn)方式堤结,還需繼續(xù)學(xué)習(xí)搏讶。推薦一本書《圖解HTTP》,講的非常通俗易懂,文章的幾張圖片也是來源于這本書霍殴。
有問題或者建議和意見,歡迎大家評論或者私信系吩。
喜歡的朋友可以點下關(guān)注和喜歡来庭,后續(xù)會持續(xù)更新文章。