上次写了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
好,我们拿到证书了。
2. 将证书导入项目
将目录调整为Project模式显示
在app->src-res 目录下新建raw文件夹
直接将pem证书拖进去
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:debuggable 为 true
时才信任的仅调试 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的结果
本文参考资料(感谢🙏)
网络安全配置(developers)
Volley网络请求框架使用
How can I implement SSL Certificate Pinning while using React Native
写作初心
梳理,积累,分享,交流
靴靴你能看到这里
欢迎交流
下一篇见 ᕕ(ᐛ)ᕗ