上次寫了iOS開發(fā)--在AFNetworking中實(shí)現(xiàn) SSL pinning的文章
有興趣的可以順著網(wǎng)線爬過去看看哈
書接上一回
我手頭上一個(gè)Android APP因?yàn)楣δ懿皇呛軓?fù)雜,也不會(huì)涉及到上傳下載的功能,所以第一手Android開發(fā)團(tuán)隊(duì)用的是volley框架來進(jìn)行網(wǎng)絡(luò)通信。
Volley
Volley is an HTTP library that makes networking for Android apps easier and, most importantly, faster.
網(wǎng)上沖浪了一波也沒有找到資料說volley有支持ssl pinning的功能
這個(gè)時(shí)候最該想到的就是開發(fā)文檔爸爸which最靠譜兒
Android developers 網(wǎng)絡(luò)安全配置文檔提到??
添加網(wǎng)絡(luò)安全配置
借助網(wǎng)絡(luò)安全配置功能,應(yīng)用可以在一個(gè)安全的聲明性配置文件中自定義其網(wǎng)絡(luò)安全設(shè)置,而無需修改應(yīng)用代碼。您可以針對(duì)特定網(wǎng)域和特定應(yīng)用配置這些設(shè)置。
- 自定義信任錨:針對(duì)應(yīng)用的安全連接自定義哪些證書授權(quán)機(jī)構(gòu) (CA) 值得信賴。例如,信任特定的自簽名證書或限制應(yīng)用信任的公共 CA 集。
- 證書固定:限制應(yīng)用僅安全連接到特定的證書。
跟隨開發(fā)文檔
4個(gè)步驟實(shí)現(xiàn)ssl pinning讓你的app更安全
要向您的應(yīng)用添加網(wǎng)絡(luò)安全配置文件,請(qǐng)按以下步驟操作:
1. 獲取服務(wù)器證書
我們需要pem 或 der 格式的自簽名or(非)公共 CA 證書,可以跟服務(wù)器端索取。
如果你拿到的是.cer 證書,可以使用一下命令進(jìn)行轉(zhuǎn)換
openssl x509 -inform der -in 你的cer證書名字.cer -out 自定義輸出的pem證書名字.pem

好,我們拿到證書了。
2. 將證書導(dǎo)入項(xiàng)目
將目錄調(diào)整為Project模式顯示
在app->src-res 目錄下新建raw文件夾
直接將pem證書拖進(jìn)去

3. 新建network_security_config.xml
新建app->src-res/xml/network_security_config.xml:
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">example.com</domain>
<!-- 配置自定義 CA-->
<!-- 假設(shè)您要連接到使用自簽名 SSL 證書的主機(jī),或者連接到其 SSL 證書是由您信任的非公共 CA(如公司的內(nèi)部 CA)簽發(fā)的主機(jī)。-->
<trust-anchors>
<certificates src="@raw/api"/>
</trust-anchors>
<!-- 固定證書-->
<!-- 一般情況下,應(yīng)用信任所有預(yù)裝 CA。如果有預(yù)裝 CA 簽發(fā)欺詐性證書,則應(yīng)用將面臨被中間人攻擊的風(fēng)險(xiǎn)。有些應(yīng)用通過限制信任的 CA 集或通過固定證書,選擇限制其接受的證書集。-->
<!-- 此外,可以設(shè)置證書固定的到期時(shí)間,在該時(shí)間之后不再固定證書。這有助于防止尚未更新的應(yīng)用出現(xiàn)連接性問題。不過,設(shè)置證書固定的到期時(shí)間可能會(huì)繞過證書固定。-->
<pin-set expiration="2021-01-01">
<!-- 要固定證書,您可以通過按公鑰的哈希值(X.509 證書的 SubjectPublicKeyInfo)提供證書集。然后,只有至少包含一個(gè)已固定公鑰的證書鏈才有效。-->
<pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxZhBCoQYcRhJ3Y=</pin>
<!-- backup pin -->
<!-- 請(qǐng)注意,固定證書時(shí),您應(yīng)始終包含一個(gè)備份密鑰,這樣,當(dāng)您被強(qiáng)制切換到新密鑰或更改 CA 時(shí)(固定到某個(gè) CA 證書或該 CA 的中間證書時(shí)),應(yīng)用的連接性不會(huì)受到影響。否則,您必須推送應(yīng)用更新以恢復(fù)連接性。-->
<pin digest="SHA-256">fwza0LRMXouZHRC8Ei+4Pyuld3UKgO/04cDM1oE=</pin>
</pin-set>
</domain-config>
</network-security-config>
關(guān)于怎樣獲得證書公鑰的哈希值
將你的hostname拷貝到這個(gè)網(wǎng)站按submit就能看到


配置文件你還可以這樣寫:
限制可信 CA 集
如果應(yīng)用不想信任系統(tǒng)信任的所有 CA,則可以自行指定,縮減要信任的 CA 集。這樣可防止應(yīng)用信任任何其他 CA 簽發(fā)的欺詐性證書。
限制可信 CA 集的配置與針對(duì)特定網(wǎng)域信任自定義 CA 相似,不同的是,前者要在資源中提供多個(gè) CA。
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config>
<domain includeSubdomains="true">secure.example.com</domain>
<domain includeSubdomains="true">cdn.example.com</domain>
<trust-anchors>
<certificates src="@raw/trusted_roots"/>
</trust-anchors>
</domain-config>
</network-security-config>
以 PEM 或 DER 格式將可信 CA 添加到 res/raw/trusted_roots。請(qǐng)注意,如果使用 PEM 格式,文件必須僅包含 PEM 數(shù)據(jù),沒有額外的文本。您還可以提供多個(gè) <certificates> 元素,而不是只提供一個(gè)元素。
信任其他 CA
應(yīng)用可能需要信任系統(tǒng)不信任的其他 CA,出現(xiàn)此情況的原因可能是系統(tǒng)還未包含此 CA,或 CA 不符合添加到 Android 系統(tǒng)中的要求。應(yīng)用可以通過為一個(gè)配置指定多個(gè)證書源來實(shí)現(xiàn)此目的。
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config>
<trust-anchors>
<certificates src="@raw/extracas"/>
<certificates src="system"/>
</trust-anchors>
</base-config>
</network-security-config>
配置用于調(diào)試的 CA
調(diào)試通過 HTTPS 連接的應(yīng)用時(shí),您可能需要連接到?jīng)]有為生產(chǎn)服務(wù)器提供 SSL 證書的本地開發(fā)服務(wù)器。若要無需應(yīng)用代碼而支持此操作,您可以通過使用 debug-overrides 來指定僅在 android:debuggable 為 true 時(shí)才信任的僅調(diào)試 CA。通常,IDE 和編譯工具會(huì)自動(dòng)為非發(fā)布版本設(shè)置此標(biāo)記。
這比一般的條件代碼更安全,因?yàn)槌鲇诎踩紤],應(yīng)用商店不接受被標(biāo)記為可調(diào)試的應(yīng)用。
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<debug-overrides>
<trust-anchors>
<certificates src="@raw/debug_cas"/>
</trust-anchors>
</debug-overrides>
</network-security-config>
4. 在AndroidManifest.xml中指向上述網(wǎng)絡(luò)安全配置文件network_security_config.xml
在application節(jié)點(diǎn)上面添加??
android:networkSecurityConfig="@xml/network_security_config"
搞定!
如果你用的是okhttp
那就更簡(jiǎn)單了
創(chuàng)建一個(gè)CertificatePinner對(duì)象add一個(gè)假的哈希值,返回的exception會(huì)提供真正公鑰的哈希值
這里要注意??的是哈希值的長(zhǎng)度是固定的,所以造假的哈希值 "sha256/"后面一定要整28個(gè)字符,否則報(bào)的exception會(huì)不一樣
public void okHttpRequest() {
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add("www.baidu.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.build();
OkHttpClient client = (new OkHttpClient.Builder()).certificatePinner(certificatePinner).build();
//創(chuàng)建Request請(qǐng)求,這里是get
Request request = new Request.Builder().url("https://www.baidu.com").get().build();
//通過客戶端創(chuàng)建Call
Call call = client.newCall(request);
//進(jìn)行異步請(qǐng)求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.d("OkHttp", e.getMessage());
}
@Override
public void onResponse(Call call, okhttp3.Response response) throws IOException {
Log.d("OkHttp", response.body().string());
}
});
}

獲得真正的公鑰哈希值之后,重新給CertificatePinner對(duì)象add上去
public void okHttpRequest() {
CertificatePinner certificatePinner = new CertificatePinner.Builder()
.add("www.baidu.com", "sha256/YBo/npMPiC3PCrMqVUOvC+PTwfJ9iwLSapvdzSs4=")
.add("www.baidu.com", "sha256/IQBnNBEiFuhj+8x6X8XLgh01V9Ic3IRQLNFFc7v4=")
.add("www.baidu.com", "sha256/K87oWBWM9UZfyddvDfoxL+8lUB2ptGtn0fv6G2Q=")
.add("www.baidu.com", "sha256/YBo/npMPiC3PCrMqVUOvC+PTfJ9iwLSapvdzSs41")
.build();
OkHttpClient client = (new OkHttpClient.Builder()).certificatePinner(certificatePinner).build();
//創(chuàng)建Request請(qǐng)求,這里是get
Request request = new Request.Builder().url("https://www.baidu.com").get().build();
//通過客戶端創(chuàng)建Call
Call call = client.newCall(request);
//進(jìn)行異步請(qǐng)求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Looper.prepare();
Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_LONG).show();
Looper.loop();
Log.d("OkHttp", e.getMessage());
}
@Override
public void onResponse(Call call, okhttp3.Response response) throws IOException {
Looper.prepare();
Toast.makeText(MainActivity.this, response.body().string(), Toast.LENGTH_LONG).show();
Looper.loop();
Log.d("OkHttp", response.body().string());
}
});
}
Done!
這里要強(qiáng)調(diào)的是
www.baidu.com只是用來舉例的,我在iOS和Android分別創(chuàng)建了demo,用各種辦法都pin不了百度這個(gè)網(wǎng)站,Charles和fiddle依然能抓到app請(qǐng)求的數(shù)據(jù),原因可能是百度的證書我是直接在Chrome下的,anyway,如果你的項(xiàng)目上需要做ssl pinning,請(qǐng)直接向后臺(tái)索取。
效果截圖
這是我項(xiàng)目上的app做了ssl pinning的結(jié)果


本文參考資料(感謝??)
網(wǎng)絡(luò)安全配置(developers)
Volley網(wǎng)絡(luò)請(qǐng)求框架使用
How can I implement SSL Certificate Pinning while using React Native
寫作初心
梳理,積累,分享,交流
靴靴你能看到這里
歡迎交流
下一篇見 ?(?)?