日常的 APP 为了不被别人通过抓包软件抓取与服务器通信的流量,所以会在流量的安全传输上做文章,最安全的做法当然是使用私有协议 (即协议格式不公开的协议), 例如抖音的 quic 协议,咸鱼的 spdy 协议等等,利用这些私有协议包装流量,从而使抓包工具无法识别出这串流量的实际内容是什么,所以就只能放行这段流量,面对私有协议倘若利用 reqable , fiddler , charles 等去抓包是抓不到的,而唯一能够抓到这段流量的就是 wireshark , 并且还需要对这段流量编写特定的脚本解析才可以知道这段流量所携带的实际数据

除了私有协议之外,想让流量上的安全还可以使用 SSL pinning 的方法来阻止中间人抓包的发生,如何进行 HTTPS 双向认证以及使用 frida 对 SSL pinning 进行绕过是本文所要讨论的重点

# SSL pinning

虽然 HTTPS 采用了公钥和证书的加密和验证方式,但依然存在 MIM (中间人攻击) 的可能性,因为证书颁发机构可能被黑客入侵,而 HTTPS 只验证了证书的合法性,并没有验证当前服务器是否是我们要访问的服务器,所以需要客户端去验证服务端证书的合法性,这就是 SSL pinning , 它还是 HTTPS 单向认证的范畴之内。

我们可以看一下目前抓包工具 Charles 所采取的抓包方法

图片

为了保证我们当前访问的服务器就是我们需要访问的服务器,防止中间人攻击的发生,需要通过 SSL pinning 的方式来实现,其包括 Certificate Pinning 和 Public Key Pinning 两种。

# 证书锁定

证书锁定需要把服务器的证书提前下载并内置到客户端中,当请求发起时,通过比对证书内容来确定连接的合法性,但由于证书存在过期时间,因此当服务器端证书更换时,需同时更换客户端证书。

既然是要锁定证书,那么我们客户端上应该事先存在一个证书,我们才能锁定这个证书来验证我们真正的服务端,而不是代理工具伪造的服务端。

如果是锁定证书,那通常情况下会将证书放置在 app/asset 目录下。

具体操作:将 APP 代码内置仅接受指定域名的证书,而不接受操作系统或者浏览器内置的 CA 根证书对应的任何证书。

通过这种授权方式,保障了 APP 与服务端通信的唯一性和安全性,因此移动端 APP 与服务端(例如 API 网关)之间的通信可以保证绝对的安全。

# 公钥锁定

公钥锁定则需提取证书中的公钥内置到客户端中,通过比对公钥值来验证连接的合法性,由于证书更换依然可以保证公钥一致,所以公钥锁定不存在客户端频繁更换证书的问题,并且之后我们所使用的 SSL pinning 也同样是公钥锁定。

# HTTPS 双向认证原理

双向验证,顾名思义就是客户端验证服务器端证书的正确性,服务器端也验证客户端的证书正确性,即

  • 服务端使用 ca.crt 校验客户端的 client.crtclient.key
  • 客户端使用 ca.crt 校验服务端的 server.crtserver.key

img

详细的 HTTPS 连接过程如下图所示,其中的 root.crtca.crt

image

  1. 客户端发起建立 HTTPS 连接请求,将 SSL 协议版本的信息发送给服务端;
  2. 服务器端将本机的公钥证书(server.crt)发送给客户端;
  3. 客户端读取公钥证书(server.crt),取出了服务端公钥;
  4. 客户端将客户端公钥证书(client.crt)发送给服务器端;
  5. 服务器端使用根证书(root.crt)解密客户端公钥证书,拿到客户端公钥;
  6. 客户端发送自己支持的加密方案给服务器端;
  7. 服务器端根据自己和客户端的能力,选择一个双方都能接受的加密方案,使用客户端的公钥加密后发送给客户端;
  8. 客户端使用自己的私钥解密加密方案,生成一个随机数 R,使用服务器公钥加密后传给服务器端;
  9. 服务端用自己的私钥去解密这个密文,得到了密钥 R
  10. 服务端和客户端在后续通讯过程中就使用这个密钥 R 进行通信了。

# 自签名证书

# 证书准备

在制作证书前,我们先来了解一下每一个证书的格式

  • JKS:数字证书库。JKS 里有 KeyEntry 和 CertEntry,在库里的每个 Entry 都是靠别名(alias)来识别的。
  • P12:是 PKCS12 的缩写。同样是一个存储私钥的证书库,由 .jks 文件导出的,用户在 PC 平台安装,用于标示用户的身份。
  • CER:俗称数字证书,目的就是用于存储公钥证书,任何人都可以获取这个文件 。
  • BKS:由于 Android 平台不识别 .keystore 和 .jks 格式的证书库文件,因此 Android 平台引入一种新的证书库格式,BKS。

整个双向认证的流程需要六个证书文件:

  • 服务器端公钥证书:server.crt
  • 服务器端私钥文件:server.key
  • 根证书:root.crt
  • 客户端公钥证书:client.crt
  • 客户端私钥文件:client.key
  • 客户端集成证书(包括公钥和私钥,用于安卓访问场景):client.bks

# 证书制作

因为自签名证书完全免费,所以这里我们使用自签名证书来作为我们的客户端 / 服务端证书

image

# 生成自签名根证书

  1. 创建根证书私钥:
openssl genrsa -out root.key 1024
  1. 创建根证书请求文件:
openssl req -new -out root.csr -key root.key

这一步以及下面生成服务器请求文件和客户端请求文件的过程都都要注意以下两点:

  • 根证书的 Common Name 填写 root 就可以,所有客户端和服务器端的证书这个字段需要填写域名,一定要注意的是,根证书的这个字段和客户端证书、服务器端证书不能一样
  • 其他所有字段的填写,根证书、服务器端证书、客户端证书需保持一致最后的密码可以直接回车跳过
  1. 创建根证书:
openssl x509 -req -in root.csr -out root.crt -signkey root.key -CAcreateserial -days 3650

经过上面三个命令行,我们最终可以得到一个签名有效期为 10 年的根证书 root.crt,后面我们可以用这个根证书去颁发服务器证书和客户端证书。

# 生成自签名服务器端证书

  1. 生成服务器端证书私钥:
openssl genrsa -out server.key 1024
  1. 生成服务器证书请求文件,过程和注意事项参考根证书,本节不详述:
openssl req -new -out server.csr -key server.key
  1. 生成服务器端公钥证书
openssl x509 -req -in server.csr -out server.crt -CA root.crt -CAkey root.key -CAcreateserial -days 3650

经过上面的三个命令,我们得到:

  • server.key:服务器端的密钥文件

  • server.crt:有效期十年的服务器端公钥证书,使用根证书和服务器端私钥文件一起生成

# 生成自签名客户端证书

  1. 生成客户端证书密钥:
openssl genrsa -out client.key 1024
  1. 生成客户端证书请求文件
openssl req -new -out client.csr -key client.key
  1. 生客户端证书
openssl x509 -req -in client.csr -out client.crt -CA root.crt -CAkey root.key -CAcreateserial -days 3650
  1. 生客户端 bks 格式证书

我们首先查看自己的 java 版本,例如我现在的 java 版本是 jdk1.7

PS C:\Users\oacia> java --version
java 17.0.8 2023-07-18 LTS

然后前往 [https://www.bouncycastle.org/latest_releases.html) 下载对应版本的 bcprov , 我的 jdk1.7 应该下载的 jar 包是 bcprov-jdk15to18-177.jar

随后输入下列命令即可将 crt 格式的证书转换为 bks 格式的证书

keytool -import -alias client -file .\client.crt -keystore .\client.bks -storetype BKS -provider org.bouncycastle.jce.provider.BouncyCastleProvider -providerpath .\bcprov-jdk15to18-177.jar -storepass 123456

同理,我们可以将将根证书 root.crt 也转换为 root.bks , 因为在 Android 中智能识别 bks 格式的证书

## 总结

通过上述过程自定义证书的制作,我们已经得到了所需要的证书

  • android 客户端
    • client.bks : 这是客户端证书,需要发送给服务端进行校验
    • root.bks : 这是根证书,用来校验服务端证书的合法性
  • python 服务端
    • server.crtserver.key : 这是服务端证书,需要发送给客户端进行校验
    • root.crt : 这是根证书,用来校验客户端证书的合法性

# Android 客户端

# 参考资料

  • 关于单向认证与双向认证
  • 阿里云 HTTPS 双向认证
更新于 阅读次数