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:



    
        example.com


        
            
        


        
        
            
            7HIpactkIAq2Y49orFOOQKurWxZhBCoQYcRhJ3Y=
            
            
            fwza0LRMXouZHRC8Ei+4Pyuld3UKgO/04cDM1oE=
        
    

关于怎样获得证书公钥的哈希值

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

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

如果应用不想信任系统信任的所有 CA,则可以自行指定,缩减要信任的 CA 集。这样可防止应用信任任何其他 CA 签发的欺诈性证书。
限制可信 CA 集的配置与针对特定网域信任自定义 CA 相似,不同的是,前者要在资源中提供多个 CA。

    
    
        
            secure.example.com
            cdn.example.com
            
                
            
        
    

以 PEM 或 DER 格式将可信 CA 添加到 res/raw/trusted_roots。请注意,如果使用 PEM 格式,文件必须仅包含 PEM 数据,没有额外的文本。您还可以提供多个 元素,而不是只提供一个元素。

信任其他 CA

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

    
    
        
            
                
                
            
        
    
配置用于调试的 CA

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

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

    
    
        
            
                
            
        
    
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 Pinning(证书固定)可以这样做)