美文网首页程序员
Android安全防护--Volley/OkHttp SSL P

Android安全防护--Volley/OkHttp SSL P

作者: Beautifu1Mooo | 来源:发表于2020-05-28 15:06 被阅读0次

    上次写了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

    写作初心

    梳理,积累,分享,交流

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

    相关文章

      网友评论

        本文标题:Android安全防护--Volley/OkHttp SSL P

        本文链接:https://www.haomeiwen.com/subject/uymvahtx.html