HTTPS 抓包原理以及 Android 端如何防止抓包

抓包原理

抓包的基本原理就是中間人攻擊 HTTPS 的握手過程胚宦。Mac 上可使用 Charles 進行抓包。本質上就是兩段 HTTPS 連接燕垃,Client <--> Man-In-The-Middle 和 Man-In-The-Middle <--> Server枢劝。使用 Charles 進行抓包,需要 Client 端提前將 Charles 的根證書添加在 Client 的信任列表中卜壕。

HTTPS中間人攻擊

Android 端防止抓包 —— Certificate Pinning

回顧之前的 HTTPS 的握手過程您旁,可以知道 SSL 的核心過程就是客戶端驗證證書鏈合法性——客戶端檢查證書鏈中是否有一個證書或者公鑰存在于客戶端的可信任列表中。
手機系統(tǒng)中內置了上百份不同的根證書轴捎。Certificate Pinning 的原理其實就是 app 中內置需要被信任的特定證書鹤盒,app 在驗證服務器傳過來的證書鏈時,使用這些特定證書來驗證的侦副。

  • 葉子證書侦锯。如果 app 選擇 pinning 葉子證書,那么就可以 100% 保證 ssl 證書鏈的合法性跃洛。但是葉子證書有效期短率触,服務器換證書(因為私鑰泄露、證書到期等原因)的話就客戶端 app 就需要用新的葉子證書驗證汇竭。
  • 中間證書葱蝗。如果 app 選擇 pinning 中間證書,那么客戶端 app 也就選擇了相信簽發(fā)中間證書的機構所簽發(fā)的其他證書细燎。這樣的話两曼,只要服務端使用同一個中間機構簽發(fā)的葉子證書,客戶端 app 就不需要做任何改變玻驻。同時悼凑,中間證書有效期也非常長。
  • 根證書璧瞬。從證書鏈的驗證過程來看户辫,它的效果與中間證書相同,但是信任了更多的中間機構及其簽發(fā)的證書嗤锉。根證書的有效期比中間證書更長渔欢。
    客戶端 app 可以同時 pinning 多個證書,以靈活地適應各種證書驗證策略瘟忱。

pinning 證書還是公鑰奥额?

證書的主要作用是公鑰的載體苫幢,但在實踐中我們更多是去 pinning 公鑰,SubjectPublicKeyInfo(SPKI)垫挨。這是因為很多服務器會去定期旋轉證書韩肝,但是證書旋轉后,證書中的公鑰還是相同的公鑰九榔。

私鑰泄露怎么辦哀峻?

如果私鑰泄露了,那么服務器端就不得不使用新的私鑰做出新的證書帚屉∶战耄客戶端為了預防這種情況漾峡,可以提前 pinning 這些新的證書攻旦。這樣,當服務器替換新的證書時生逸,客戶端 app 就可以不做任何改動牢屋。

如何存放證書或者公鑰?

  • 內置槽袄。客戶端 app 可以將證書或公鑰 hardcode 到代碼中烙无,或者作為資源文件放進 asset 中。這樣做的壞處就是遍尺,老版本的 app 難以替換其中的證書或公鑰截酷。
  • 第一次使用時保存。客戶端 app 第一次訪問服務器進行 ssl 證書鏈驗證時乾戏,將其中的公鑰保存下來迂苛。這種方法適用于客戶端 app 不能提前知道它將要訪問的服務器地址的時候。
  • 網絡下發(fā)鼓择。最靈活的方法是三幻,服務端提供一個證書服務器專門用于向客戶端 app 下發(fā)需要 pinning 的證書或公鑰,客戶端 app 只需要 pinning 這個證書服務器即可呐能。

Pinning Certificate

Android N

從 SDK 24 開始念搬,Android 支持通過 xml 來配置 certificate pinning,見 Network Security Configuration摆出。

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config>
        <domain includeSubdomains="true">example.com</domain>
        <pin-set expiration="2018-01-01">
            <pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxmmSFZhBCoQYcRhJ3Y=</pin>
            <!-- backup pin -->
            <pin digest="SHA-256">fwza0LRMXouZHRC8Ei+4PyuldPDcf3UKgO/04cDM1oE=</pin>
        </pin-set>
    </domain-config>
</network-security-config>

其中 <pin> 節(jié)點接受 SubjectPublicKeyInfo 的 hash 值朗徊。

OkHttp

OkHttp 從 2.1 開始直接支持 Certificate Pinning

HttpsURLConnection

1. Add Certificate To TrustManager

我在項目實踐中發(fā)現有的服務器并不會在 ssl 握手階段將完整的證書鏈傳輸過來——只會傳證書鏈中的根證書和葉子證書偎漫。如果安卓系統(tǒng)中使用 HttpUrlConnection 訪問服務器爷恳,拋出如下類似異常:

javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
at org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:374)
at libcore.net.http.HttpConnection.setupSecureSocket(HttpConnection.java:209)
at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.makeSslConnection(HttpsURLConnectionImpl.java:478)
at libcore.net.http.HttpsURLConnectionImpl$HttpsEngine.connect(HttpsURLConnectionImpl.java:433)
at libcore.net.http.HttpEngine.sendSocketRequest(HttpEngine.java:290)
at libcore.net.http.HttpEngine.sendRequest(HttpEngine.java:240)
at libcore.net.http.HttpURLConnectionImpl.getResponse(HttpURLConnectionImpl.java:282)
at libcore.net.http.HttpURLConnectionImpl.getInputStream(HttpURLConnectionImpl.java:177)
at libcore.net.http.HttpsURLConnectionImpl.getInputStream(HttpsURLConnectionImpl.java:271)

但是瀏覽器對于這種缺失中間證書的服務器卻能驗證通過,主要原因是瀏覽器訪問有完整證書鏈的網站時骑丸,如果發(fā)現證書鏈中有瀏覽器沒有內置的中間證書舌仍,那么瀏覽器會將該證書緩存下來妒貌,這樣瀏覽器訪問其他沒有該中間證書的服務器時,就可以使用這個緩存的中間證書來驗證證書鏈铸豁。
解決安卓上出現這個問題的方法是將這個中間證書通過 app 添加到信任證書列表中灌曙。我們需要將該中間證書加入到 App 運行時所用的 TrustManager 中。

  • 將需要添加的 CA 證書加載到 InputStream
  • 使用這個 InputStream 創(chuàng)建一個 KeyStore
  • 使用這個 KeyStore 初始化一個 TrustManager
  • 使用這個 TrustManager 去初始化一個 SSLContext
  • SSLContext 提供一個 SSLSocketFactor
  • 使用這個 SSLSocketFactory 覆蓋 HttpUrlConnectionSSLSocketFactory
// Load CAs from an InputStream
// (could be from a resource or ByteArrayInputStream or ...)
val cf: CertificateFactory = CertificateFactory.getInstance("X.509");
// From https://www.washington.edu/itconnect/security/ca/load-der.crt
val caInput: InputStream = BufferedInputStream(FileInputStream("load-der.crt"))
val ca: X509Certificate = caInput.use {
    ca.generateCertificate(it) as X509Certificate
}
System.out.println("ca=" + ca.subjectDN)

// Create a KeyStore containing our trusted CAs
val keyStoreType = KeyStore.getDefaultType()
val keyStore = KeyStore.getInstance(keyStoreType).apply {
    load(null, null)
    setCertificateEntry("ca", ca)
}

// Create a TrustManager that trusts the CAs inputStream our KeyStore
val tmfAlgorithm: String = TrustManagerFoctory.getDefaultAlgorithm()
val tmf: TrustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm).apply {
    init(keyStore)
}

// Create an SSLContext that uses our TrustManager
val context: SSLContext = SSLContext.getInstance("TLS").apply {
    init(null, tmf.trustManagers, null)
}

// Tell the URLConnection to use a SocketFactory from our SSLContext
val url = URL("https://certs.cac.washington.edu/CAtest/")
val urlConnection = url.openConnection() as HttpsURLConnection
urlConnection.sslSocketFactory = context.socketFactory
val inputStream: InputStream = urlConnection.inputStream
copyInputStreamToOutputStream(inputStream, System.out)
2. Certificate Pinning With HttpsUrlConnection

使用 X509TrustManagerExtensions 可以將證書 pinning 到 app 中节芥。X509TrustManagerExtensions.checkServerTrusted() 允許開發(fā)者在系統(tǒng)對證書鏈驗證通過后在刺,再次使用自己的方法驗證證書鏈。

private void validatePinning(X509TrustManagerExtensions trustManagerExt, HttpsURLConnection conn, Set<String> validPins) throws SSLException {
    String certChainMsg = "";
    try {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        List<X509Certificate> trustedChain = trustedChain(trustManagerExt, conn);
        for (X509Certificate cert : trustedChain) {
            byte[] publicKey = cert.getPublicKey().getEncoded();
            md.update(publicKey, 0, publicKey.length);
            String pin = Base64.encodeToString(md.digest(), Base64.NO_WRAP);
            certChainMsg = " sha256/" + pin + " : " + cert.getSubjectDN().toString() + "\n";
            if (validPins.contains(pin)) {
                return;
            }
        }
    } catch(NoSuchAlgorithmException e) {
        thrown new SSLException(e);
    }
    throw new SSLPeerUnverifiedException("Certificate pinning failure\n" + "Peer certificate  chain:\n" + certChainMsg);
}

private List<X509Certificate> trustedChain(X509TrustManagerExtensions trustManagerExt, HttpsURLConnection conn) throws SSLException {
    Certificate[] serverCerts = conn.getServerCertificates();
    X509Certificate[] untrustedCerts = Arrays.copyOf(serverCerts, serverCerts.length, X509Certificate[].class);
    String host = conn.getURL().getHost();
    try {
        return trustManagerExt.checkServerTrusted(untrustedCerts, "RSA", host);
    } catch(CertificateException e) {
        throw new SSLException(e);
    }
}

使用方法如下:

TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
// Find first X509TrustManagerFactory in the TrustManagerFactory
X509TrustManager x509TrustManager = null;
for (TrustManager trustManager : trustManagerFactory.getTrustManagers()) {
    if (trustManager instanceof X509TrustManager) {
        x509TrustManager = (X509TrustManager) trustManager;
        break;
    }
}
X509TrustManagerExtensions trustManagerExt = new X509TrustManagerExtensions(x509TrustManager);
...
URL url = new URL("https://www.xxx.com");
HttpsURLConnection urlConnection = (HttpsURLConnection) url.openConnection();
urlConnection.connect();

Set<String> validPins = Collections.singleton("4hw5tz+scE+TW+mlai5YipDfFWn1dqvfLG+nU7tq1V8=");
validatePinning(trustManagerExt, urlConnection, validPins);
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末头镊,一起剝皮案震驚了整個濱河市蚣驼,隨后出現的幾起案子,更是在濱河造成了極大的恐慌相艇,老刑警劉巖颖杏,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異坛芽,居然都是意外死亡留储,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進店門咙轩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來获讳,“玉大人,你說我怎么就攤上這事活喊∝はィ” “怎么了?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵钾菊,是天一觀的道長帅矗。 經常有香客問我,道長结缚,這世上最難降的妖魔是什么损晤? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮红竭,結果婚禮上尤勋,老公的妹妹穿的比我還像新娘。我一直安慰自己茵宪,他們只是感情好最冰,可當我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著稀火,像睡著了一般暖哨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上凰狞,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天篇裁,我揣著相機與錄音沛慢,去河邊找鬼。 笑死达布,一個胖子當著我的面吹牛团甲,可吹牛的內容都是我干的。 我是一名探鬼主播黍聂,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼躺苦,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了产还?” 一聲冷哼從身側響起匹厘,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎脐区,沒想到半個月后愈诚,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡坡椒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年扰路,在試婚紗的時候發(fā)現自己被綠了尤溜。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倔叼。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖宫莱,靈堂內的尸體忽然破棺而出丈攒,到底是詐尸還是另有隱情,我是刑警寧澤授霸,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布巡验,位于F島的核電站,受9級特大地震影響碘耳,放射性物質發(fā)生泄漏显设。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一辛辨、第九天 我趴在偏房一處隱蔽的房頂上張望捕捂。 院中可真熱鬧,春花似錦斗搞、人聲如沸指攒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽允悦。三九已至,卻和暖如春虑啤,著一層夾襖步出監(jiān)牢的瞬間隙弛,已是汗流浹背架馋。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留全闷,地道東北人绩蜻。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像室埋,于是被迫代替她去往敵國和親办绝。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內容