
類似Charles這樣的抓包工具,對(duì)于高效程序員是必不可少的;
本文不會(huì)介紹Charles的安裝及使用,主要是淺顯的探討其抓包原理;Charles的安裝及使用相關(guān)內(nèi)容可以參考以下文章:
iOS開發(fā)輔助工具-抓包工具-Charles青花瓷
Charles 4.5.6 Mac pojie版
大致原理
Charles作為一個(gè)中間人代理,在客戶端給服務(wù)器端發(fā)消息的時(shí)候,會(huì)截取客戶端發(fā)送給服務(wù)器的請(qǐng)求,然后偽裝成客戶端與服務(wù)器進(jìn)行通信;服務(wù)器返回?cái)?shù)據(jù)時(shí)將截取的數(shù)據(jù)發(fā)送給客戶端,偽裝成服務(wù)器與客戶端進(jìn)行通信。

這個(gè)過程其實(shí)很簡(jiǎn)單,但不同于HTTP,更安全的HTTPS能有效防止中間人攻擊;Charles是如何截取HTTPS鏈接的呢?
HTTPS的安全性
相比HTTP,HTTPS之所以更安全的是因?yàn)槠湓贖TTP傳輸層之上加了一個(gè)安全層(SSL或TLS協(xié)議);HTTPS的安全性主要體現(xiàn)在下面3個(gè)方面:
- 數(shù)據(jù)的保密性(防竊聽)
- 數(shù)據(jù)的完整性(防篡改)
- 通信雙方身份的真實(shí)性(防冒充)
數(shù)據(jù)的保密性
要實(shí)現(xiàn)數(shù)據(jù)的保密,就需要使用加密算法對(duì)數(shù)據(jù)進(jìn)行加密;加密算法大致分為兩類:對(duì)稱加密,非對(duì)稱加密;
對(duì)稱加密:加密和解密使用相同密鑰的加密算法。對(duì)稱加密速度快,通常在需要加密大量數(shù)據(jù)時(shí)使用;因?yàn)榧用芎徒饷芏际褂猛粋€(gè)密鑰,把密鑰傳遞到解密者的過程中也會(huì)有風(fēng)險(xiǎn),因此對(duì)稱加密不是很安全的;常用的對(duì)稱加密算法有:DES、3DES、RC2、RC4、RC5、IDEA等
非對(duì)稱加密,加密和解密使用不同密鑰的加密算法;它使用了一對(duì)密鑰,
公鑰和私鑰;使用公鑰加密的數(shù)據(jù),利用私鑰解密;使用私鑰加密的數(shù)據(jù),利用公鑰解密;非對(duì)稱加密通常使用RSA算法(RSA原理探究),RSA的公鑰和私鑰其實(shí)就是一組數(shù)字,數(shù)字長(zhǎng)度越長(zhǎng)加密強(qiáng)度越大;數(shù)字一般是高于768位(二進(jìn)制),不能被輕易破解;RSA算法加密解密其實(shí)就是對(duì)這個(gè)比較大的數(shù)進(jìn)行運(yùn)算;因此RSA非對(duì)稱加密要比對(duì)稱加密慢很多,為了保證效率,RSA非對(duì)稱加密只用于小數(shù)據(jù);
基于對(duì)稱加密和非對(duì)稱加密的優(yōu)缺點(diǎn),HTTPS的加密方案就是:
連接建立過程(TLS握手):
- 客戶端向服務(wù)器請(qǐng)求(發(fā)送TLS版本號(hào)、支持的加密算法、隨機(jī)數(shù)
Client Random); - 服務(wù)器返回非對(duì)稱加密的公鑰(證書)、商定的加密算法、隨機(jī)數(shù)
Server Random給客戶端; - 客戶端驗(yàn)證服務(wù)器返回的證書;
- 證書驗(yàn)證通過,客戶端就會(huì)生成一個(gè)新的隨機(jī)數(shù)
pre-master,用服務(wù)器的公鑰加密該隨機(jī)數(shù)并發(fā)送給服務(wù)器;服務(wù)器收到后,用私鑰解密,得到客戶端發(fā)來的隨機(jī)數(shù)pre-master。
至此,客戶端和服務(wù)端雙方都共享了三個(gè)隨機(jī)數(shù),分別是Client Random/Server Random/pre-master??蛻舳司透鶕?jù)服務(wù)器返回的證書及3個(gè)隨機(jī)數(shù)生成一個(gè)會(huì)話密鑰(對(duì)稱加密); - 客戶端用服務(wù)器返回的公鑰(證書)對(duì)會(huì)話密鑰進(jìn)行非對(duì)稱加密后傳輸給服務(wù)器;
- 服務(wù)器通過私鑰解密得到會(huì)話密鑰;
- 客戶端和服務(wù)器互相傳輸加密的握手消息來驗(yàn)證安全通道是否已完成;

通信過程
- 客戶端使用會(huì)話密鑰對(duì)傳輸?shù)臄?shù)據(jù)進(jìn)行對(duì)稱加密傳輸給服務(wù)器;
- 服務(wù)器使用會(huì)話密鑰對(duì)傳輸?shù)臄?shù)據(jù)進(jìn)行解密;
- 服務(wù)器使用會(huì)話密鑰對(duì)響應(yīng)的數(shù)據(jù)進(jìn)行對(duì)稱加密傳輸給客戶端;
- 客戶端使用會(huì)話密鑰對(duì)傳輸?shù)臄?shù)據(jù)進(jìn)行解密;
總的來說就是:連接建立過程使用非對(duì)稱加密,后續(xù)通信過程使用對(duì)稱加密;
數(shù)據(jù)的完整性
數(shù)據(jù)的加密,有效保證了數(shù)據(jù)不被竊聽(很難得到原始的數(shù)據(jù)),但傳輸?shù)臄?shù)據(jù)在傳輸過程中有可能被篡改或替換;比如:
傳輸?shù)脑紨?shù)據(jù)是123456,經(jīng)過加密后數(shù)據(jù)是abcdef;客戶端將abcdef這個(gè)數(shù)據(jù)傳輸給服務(wù)器,傳輸過程中中間人能拿到abcdef這個(gè)數(shù)據(jù),但因?yàn)闆]有密鑰很難解密出原始數(shù)據(jù)123456;但是,中間人還是能對(duì)得到的abcdef這個(gè)加密數(shù)據(jù)進(jìn)行處理,比如將這個(gè)數(shù)據(jù)改為xxxxx;這樣服務(wù)器得到的數(shù)據(jù)就是被篡改后的xxxxx;同樣,服務(wù)器返回?cái)?shù)據(jù)給客戶端時(shí)也會(huì)被篡改;這樣其實(shí)也是不安全的;
解決方案是進(jìn)行數(shù)字簽名:使用Hash算法將任意長(zhǎng)度的字符串轉(zhuǎn)化為固定長(zhǎng)度的字符串,該過程不可逆,可用來作數(shù)據(jù)完整性校驗(yàn);
具體可參考淺談Hash
數(shù)字簽名的簡(jiǎn)要過程(服務(wù)器-->客戶端為例,客戶端-->服務(wù)器類似):
- 服務(wù)器使用Hash算法對(duì)數(shù)據(jù)提取定長(zhǎng)摘要
- 服務(wù)器使用私鑰對(duì)摘要進(jìn)行加密,作為數(shù)字簽名
- 服務(wù)器將數(shù)字簽名連同加密的數(shù)據(jù)一同傳輸給客戶端
- 客戶端使用公鑰對(duì)數(shù)字簽名進(jìn)行解密,得到摘要A
- 客戶端對(duì)解密后的傳輸數(shù)據(jù)也使用Hash算法得到定長(zhǎng)摘要B
- 對(duì)比摘要A和摘要B,如果不一致則數(shù)據(jù)已被篡改
通信雙方身份的真實(shí)性
以上加密過程,最核心的就是非對(duì)稱加密的公鑰和私鑰;如果這個(gè)密鑰都是攻擊者提供的,那傳輸?shù)臄?shù)據(jù)在攻擊者那里也是相當(dāng)于裸露的;如何確保密鑰是不被冒充的呢?HTTPS使用了數(shù)字證書(簽名),數(shù)字證書就是身份認(rèn)證機(jī)構(gòu)CA(Certificate Authority)加在數(shù)字身份證上的一個(gè)簽名,證書的合法性可以向CA驗(yàn)證;證書的制作方法是公開的,任何人都可以自己制作證書,但只有權(quán)威的證書頒發(fā)機(jī)構(gòu)的證書能通過CA認(rèn)證;
數(shù)字證書主要包含以下信息:
- 證書頒發(fā)機(jī)構(gòu)
- 證書頒發(fā)機(jī)構(gòu)簽名
- 證書版本、有效期
- 證書綁定的服務(wù)器域名
- 簽名使用的加密算法(非對(duì)稱算法,如RSA)
- 公鑰
數(shù)字證書的作用,是用來認(rèn)證公鑰持有者的身份,以防止第三方進(jìn)行冒充;簡(jiǎn)單說,證書就是用來告訴客戶端,服務(wù)端是否合法。
為了讓服務(wù)端的公鑰被信任,服務(wù)端的證書都由CA簽名,CA就是網(wǎng)絡(luò)世界里的公安局,具有極高的可信度,所以由它來給各個(gè)公鑰簽名,信任的一方簽發(fā)的證書,那必然證書也是被信任的。
數(shù)字證書簽發(fā)和驗(yàn)證流程
- CA簽發(fā)證書的過程
首先CA會(huì)把持有者的公鑰、頒發(fā)者、用途、有效時(shí)間等信息打包;然后對(duì)這些信息進(jìn)行Hash計(jì)算。然后CA會(huì)使用自己的私鑰將該Hash值加密,生成Certificate Signature,即CA對(duì)證書做了簽名。最后將Certificate Signature添加在文件證書上,形成了數(shù)字證書;
- 客戶端校驗(yàn)服務(wù)端的數(shù)字證書的過程:
客戶端會(huì)使用同樣的Hash算法獲取該證書的Hash值A(chǔ);
通常瀏覽器和操作系統(tǒng)中集成了CA的公鑰信息,瀏覽器收到證書后可以使用CA的公鑰解密Certificate Signature內(nèi)容,得到一個(gè)Hash值B;
最后比較A和B,如果值相同,則為可信賴的證書,否則認(rèn)為證書不可信。
客戶端和服務(wù)器連接過程中,收到服務(wù)器返回的證書后,會(huì)先向CA驗(yàn)證證書的合法性(根據(jù)證書的簽名、綁定的域名等信息),如果校驗(yàn)不通過中止連接,并提示證書不安全。
HTTPS抓包原理
<unknown>的原因
首先我們看下默認(rèn)情況下,使用Charles抓包HTTPS的情況:

正常情況下,得到的結(jié)果都是<unknown>;這是因?yàn)槲覀兦懊嬷v的HTTPS的安全性的作用;
點(diǎn)擊<unknown>查看具體信息:

(ps: 截圖中也可以看到 TLS版本號(hào),協(xié)商的加密算法等信息;這個(gè)和上述流程對(duì)應(yīng)上了)
可以看到,報(bào)錯(cuò)原因是SSL握手失敗,也就是在TLS/SSL連接建立時(shí)就已經(jīng)失敗,還沒有到數(shù)據(jù)通信這步來;
進(jìn)一步查看TLS Alert Code得到更詳細(xì)的信息:
handshake_failure (40) - Unable to negotiate an acceptable set of security parameters, this probably means there are no cipher suites in common
大概意思是,密鑰參數(shù)沒有協(xié)商一致;
這是因?yàn)镃harles為了能竊聽、篡改HTTPS通信數(shù)據(jù),在客戶端與服務(wù)端連接建立過程中(參考上面HTTPS連接建立過程的第2步),當(dāng)服務(wù)器返回非對(duì)稱加密的公鑰(證書)、商定的加密算法、隨機(jī)數(shù)Server Random給客戶端時(shí),Charles攔截到服務(wù)器返回給客戶端的公鑰(證書)替換成自己的公鑰(證書)再發(fā)送給客戶端; 準(zhǔn)備以此來冒充通信的雙方;
但是前面我們也分析過了,HTTPS會(huì)通過數(shù)字證書驗(yàn)證通信雙方身份的真實(shí)性;Charles的證書并不能通過CA驗(yàn)證,證書未驗(yàn)證通過那客戶端后續(xù)流程就不會(huì)進(jìn)行:包括生成一個(gè)新的隨機(jī)數(shù)pre-master、生成非對(duì)稱密鑰等流程;最終導(dǎo)致連接失??;
由于是TLS握手都沒成功,不止是Charles失敗,原先的客戶端和服務(wù)器也不能建立正常連接,客戶端網(wǎng)絡(luò)請(qǐng)求也都是失??;
Charles的策略
Charles如何解決證書驗(yàn)證的問題呢?
根據(jù)官方教程,需要我們使用者在手機(jī)上安裝Charles根證書并設(shè)置為信任:

配置好后,就能和HTTP一樣抓包使用了;
為什么手機(jī)安裝了Charles根證書后就能正常抓包呢?結(jié)合數(shù)字證書驗(yàn)證的原理,CA起到了決定性作用;一般常用的CA證書(公鑰)是事先內(nèi)嵌在手機(jī)系統(tǒng)里的,如果不是內(nèi)嵌的CA私鑰簽名的都驗(yàn)證不通過;Charles根證書就類似Charles CA,用戶手動(dòng)安裝到系統(tǒng)并設(shè)置信任,相當(dāng)于系統(tǒng)里多了一個(gè)CA;后續(xù)Charles的weizao的經(jīng)過Charles根證書(私鑰)簽名的公鑰就能驗(yàn)證通過,握手成功,通信過程也能竊聽、篡改;
Charles抓包的完整流程
- 當(dāng)客戶端和服務(wù)器建立連接時(shí),Charles會(huì)攔截到服務(wù)器返回的證書(服務(wù)器公鑰)
- 然后動(dòng)態(tài)生成一張weizao證書(Charles公鑰/假公鑰)發(fā)送給客戶端
- 客戶端收到Charles證書后,進(jìn)行驗(yàn)證;因?yàn)橹拔覀兪謾C(jī)設(shè)置了信任,所以驗(yàn)證通過;(只要手機(jī)不信任這種證書,HTTPS還是能確保安全的)
- 客戶端生成會(huì)話密鑰,使用Charles證書對(duì)會(huì)話密鑰進(jìn)行加密再傳輸給服務(wù)器
- Charles攔截到客戶端傳輸?shù)臄?shù)據(jù),使用自己的Charles私鑰進(jìn)行解密得到會(huì)話密鑰
- 連接成功后,客戶端和服務(wù)器通信,客戶端對(duì)傳輸?shù)臄?shù)據(jù)使用會(huì)話密鑰加密并使用公鑰對(duì)數(shù)據(jù)摘要進(jìn)行數(shù)字簽名,一同傳輸給服務(wù)器;
- Charles攔截到通信的數(shù)據(jù),使用之前獲得的會(huì)話密鑰解密就能得到原始數(shù)據(jù);
- Charles同樣也能篡改通信的數(shù)據(jù):將篡改后的數(shù)據(jù)重新加密并重新生成摘要并使用之前獲得的公鑰進(jìn)行數(shù)字簽名,替換原本的簽名,再傳輸給服務(wù)器;
- 服務(wù)器收取到數(shù)據(jù),按正常流程解密驗(yàn)證;
- 服務(wù)器返回響應(yīng)數(shù)據(jù)時(shí),Charles也是類似攔截過程

防抓包
從上文可以知道,抓包主要的原理就是中間人替換了原本的證書;防抓包就可以通過針對(duì)證書的校驗(yàn)來實(shí)現(xiàn);
AFN有封裝類似校驗(yàn)證書的功能,接下來主要介紹AFN+SSL Pinning的方式對(duì)證書進(jìn)行校驗(yàn);
- 獲取服務(wù)器的HTTPS證書,并把證書加到項(xiàng)目Bundle中;
- AFN代碼設(shè)置Policy
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode: AFSSLPinningModeCertificate];
securityPolicy.allowInvalidCertificates = YES;
securityPolicy.validatesDomainName = NO;
[AFHTTPSessionManager manager].securityPolicy = securityPolicy;
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode: AFSSLPinningModeCertificate];
這句代碼中,會(huì)去Bundle中獲取所有的cer證書文件:

AFSSLPinningMode有3種模式:
// 驗(yàn)證證書的模式
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone, // 不做驗(yàn)證:證書是信任機(jī)構(gòu)簽發(fā)的就會(huì)通過,若是自己服務(wù)器生成的證書,不會(huì)通過
AFSSLPinningModeCertificate, // 證書驗(yàn)證:驗(yàn)證證書的域名/有效期等信息,對(duì)比服務(wù)端返回的證書和客戶端返回的是否一致
AFSSLPinningModePublicKey, // 公鑰驗(yàn)證(只驗(yàn)證證書里的公鑰,不驗(yàn)證證書的有效期等信息)
};
在證書配置好及代碼設(shè)置好后,再使用抓包軟件就無(wú)法查看更改接口了;
AFN的HTTPS校驗(yàn)核心代碼在evaluateServerTrust方法內(nèi)
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain:(NSString *)domain {
if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
return NO;
}
NSMutableArray *policies = [NSMutableArray array];
if (self.validatesDomainName) {
// 驗(yàn)證域名時(shí),返回用于評(píng)估SSL證書鏈的策略對(duì)象。
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else {
// Returns a policy object for the default X.509 policy.
[policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
}
// 設(shè)置驗(yàn)證策略
SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);
if (self.SSLPinningMode == AFSSLPinningModeNone) {
// 如果支持自簽名則驗(yàn)證通過 否則驗(yàn)證serverTrust是否有效、有效則驗(yàn)證通過
// SSLPinningMode=AFSSLPinningModeNone allowInvalidCertificates=YES 表示任何證書都能驗(yàn)證通過
// AFServerTrustIsValid(serverTrust) 證書是否是系統(tǒng)信任的證書
return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
} else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
return NO;
}
switch (self.SSLPinningMode) {
case AFSSLPinningModeNone:
default:
return NO;
case AFSSLPinningModeCertificate: {
NSMutableArray *pinnedCertificates = [NSMutableArray array];
for (NSData *certificateData in self.pinnedCertificates) {
[pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
}
// 設(shè)置了參與校驗(yàn)錨點(diǎn)證書之后,假如驗(yàn)證的數(shù)字證書是這個(gè)錨點(diǎn)證書的子節(jié)點(diǎn),即驗(yàn)證的數(shù)字證書是由錨點(diǎn)證書對(duì)應(yīng)CA或子CA簽發(fā)的,或是該證書本身,則信任該證書
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
if (!AFServerTrustIsValid(serverTrust)) {
return NO;
}
// obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA)
// 服務(wù)器證書和客戶端的是否相同,如果有一個(gè)相同則通過
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
return YES;
}
}
return NO;
}
case AFSSLPinningModePublicKey: {
// 比較證書當(dāng)中公鑰(PublicKey)部分來進(jìn)行驗(yàn)證,通過SecTrustCopyPublicKey方法獲取本地證書和服務(wù)器證書,然后進(jìn)行比較,如果有一個(gè)相同,則通過
NSUInteger trustedPublicKeyCount = 0;
NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
for (id trustChainPublicKey in publicKeys) {
for (id pinnedPublicKey in self.pinnedPublicKeys) {
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
trustedPublicKeyCount += 1;
}
}
}
return trustedPublicKeyCount > 0;
}
}
return NO;
}