Android安全防护--Volley/OkHttp SSL Pinning(证书固定)可以这样做

上次写了iOS开发--在AFNetworking中实现 SSL pinning的文章
有兴趣的可以顺着网线爬过去看看哈
书接上一回
我手头上一个Android APP因为功能不是很复杂,也不会涉及到上传下载的功能,所以第一手Android开发团队用的是volley框架来进行网络通信。

Volley
Volley is an HTTP library that makes networking for Android apps easier and, most importantly, faster.

网上冲浪了一波也没有找到资料说volley有支持ssl pinning的功能
这个时候最该想到的就是开发文档爸爸which最靠谱儿
Android developers 网络安全配置文档提到👇

添加网络安全配置
借助网络安全配置功能,应用可以在一个安全的声明性配置文件中自定义其网络安全设置,而无需修改应用代码。您可以针对特定网域和特定应用配置这些设置。

  • 自定义信任锚:针对应用的安全连接自定义哪些证书授权机构 (CA) 值得信赖。例如,信任特定的自签名证书或限制应用信任的公共 CA 集。
  • 证书固定:限制应用仅安全连接到特定的证书。

跟随开发文档

4个步骤实现ssl pinning让你的app更安全

要向您的应用添加网络安全配置文件,请按以下步骤操作:

1. 获取服务器证书

我们需要pem 或 der 格式的自签名or(非)公共 CA 证书,可以跟服务器端索取。
如果你拿到的是.cer 证书,可以使用一下命令进行转换

openssl x509 -inform der -in 你的cer证书名字.cer -out 自定义输出的pem证书名字.pem 
api.pem

好,我们拿到证书了。

2. 将证书导入项目

将目录调整为Project模式显示
在app->src-res 目录下新建raw文件夹
直接将pem证书拖进去


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-->
<!--        假设您要连接到使用自签名 SSL 证书的主机,或者连接到其 SSL 证书是由您信任的非公共 CA(如公司的内部 CA)签发的主机。-->
        <trust-anchors>
            <certificates src="@raw/api"/>
        </trust-anchors>
<!--        固定证书-->
<!--        一般情况下,应用信任所有预装 CA。如果有预装 CA 签发欺诈性证书,则应用将面临被中间人攻击的风险。有些应用通过限制信任的 CA 集或通过固定证书,选择限制其接受的证书集。-->
        <!--        此外,可以设置证书固定的到期时间,在该时间之后不再固定证书。这有助于防止尚未更新的应用出现连接性问题。不过,设置证书固定的到期时间可能会绕过证书固定。-->
        <pin-set expiration="2021-01-01">
            <!--        要固定证书,您可以通过按公钥的哈希值(X.509 证书的 SubjectPublicKeyInfo)提供证书集。然后,只有至少包含一个已固定公钥的证书链才有效。-->
            <pin digest="SHA-256">7HIpactkIAq2Y49orFOOQKurWxZhBCoQYcRhJ3Y=</pin>
            <!-- backup pin -->
            <!--        请注意,固定证书时,您应始终包含一个备份密钥,这样,当您被强制切换到新密钥或更改 CA 时(固定到某个 CA 证书或该 CA 的中间证书时),应用的连接性不会受到影响。否则,您必须推送应用更新以恢复连接性。-->
            <pin digest="SHA-256">fwza0LRMXouZHRC8Ei+4Pyuld3UKgO/04cDM1oE=</pin>
        </pin-set>
    </domain-config>
</network-security-config>
关于怎样获得证书公钥的哈希值

将你的hostname拷贝到这个网站按submit就能看到

配置文件你还可以这样写:
限制可信 CA 集

如果应用不想信任系统信任的所有 CA,则可以自行指定,缩减要信任的 CA 集。这样可防止应用信任任何其他 CA 签发的欺诈性证书。
限制可信 CA 集的配置与针对特定网域信任自定义 CA 相似,不同的是,前者要在资源中提供多个 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。请注意,如果使用 PEM 格式,文件必须仅包含 PEM 数据,没有额外的文本。您还可以提供多个 <certificates> 元素,而不是只提供一个元素。

信任其他 CA

应用可能需要信任系统不信任的其他 CA,出现此情况的原因可能是系统还未包含此 CA,或 CA 不符合添加到 Android 系统中的要求。应用可以通过为一个配置指定多个证书源来实现此目的。

    <?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>
配置用于调试的 CA

调试通过 HTTPS 连接的应用时,您可能需要连接到没有为生产服务器提供 SSL 证书的本地开发服务器。若要无需应用代码而支持此操作,您可以通过使用 debug-overrides 来指定仅在 android:debuggabletrue 时才信任的仅调试 CA。通常,IDE 和编译工具会自动为非发布版本设置此标记。

这比一般的条件代码更安全,因为出于安全考虑,应用商店不接受被标记为可调试的应用。

    <?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中指向上述网络安全配置文件network_security_config.xml

application节点上面添加👇

android:networkSecurityConfig="@xml/network_security_config"  

搞定!

如果你用的是okhttp

那就更简单了

创建一个CertificatePinner对象add一个假的哈希值,返回的exception会提供真正公钥的哈希值
这里要注意⚠️的是哈希值的长度是固定的,所以造假的哈希值 "sha256/"后面一定要整28个字符,否则报的exception会不一样

    public void okHttpRequest() {

        CertificatePinner certificatePinner = new CertificatePinner.Builder()
                .add("www.baidu.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAA=")
                .build();
        OkHttpClient client = (new OkHttpClient.Builder()).certificatePinner(certificatePinner).build();

        //创建Request请求,这里是get
        Request request = new Request.Builder().url("https://www.baidu.com").get().build();

        //通过客户端创建Call
        Call call = client.newCall(request);
        //进行异步请求
        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对象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();

        //创建Request请求,这里是get
        Request request = new Request.Builder().url("https://www.baidu.com").get().build();

        //通过客户端创建Call
        Call call = client.newCall(request);
        //进行异步请求
        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!

这里要强调的是www.baidu.com只是用来举例的,我在iOS和Android分别创建了demo,用各种办法都pin不了百度这个网站,Charles和fiddle依然能抓到app请求的数据,原因可能是百度的证书我是直接在Chrome下的,anyway,如果你的项目上需要做ssl pinning,请直接向后台索取。

效果截图

这是我项目上的app做了ssl pinning的结果


Before.png
After.png

本文参考资料(感谢🙏)

网络安全配置(developers)
Volley网络请求框架使用
How can I implement SSL Certificate Pinning while using React Native

写作初心

梳理,积累,分享,交流

靴靴你能看到这里
欢迎交流
下一篇见 ᕕ(ᐛ)ᕗ

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,529评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,015评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,409评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,385评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,387评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,466评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,880评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,528评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,727评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,528评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,602评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,302评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,873评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,890评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,132评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,777评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,310评论 2 342