Android安全防護(hù)--Volley/OkHttp SSL Pinning(證書固定)可以這樣做

上次寫了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 
api.pem

好,我們拿到證書了。

2. 將證書導(dǎo)入項(xiàng)目

將目錄調(diào)整為Project模式顯示
在app->src-res 目錄下新建raw文件夾
直接將pem證書拖進(jìn)去


image.png
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:debuggabletrue 時(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é)果


Before.png
After.png

本文參考資料(感謝??)

網(wǎng)絡(luò)安全配置(developers)
Volley網(wǎng)絡(luò)請(qǐng)求框架使用
How can I implement SSL Certificate Pinning while using React Native

寫作初心

梳理,積累,分享,交流

靴靴你能看到這里
歡迎交流
下一篇見 ?(?)?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容