SSL Pinning on Android

简介

众所周知,我们在做手机端开发的时候,网络访问如果不做加密的话,请求数据很容易被抓包工具获取,从而造成安全隐患。所以我们怎么避免数据被别人通过抓包工具抓取呢?

SSL Pinning不是一个新概念,尽管很多开发者使用SSL来保证数据传输的安全性,但基本上都是未设置受信任证书,所以无法防止中间人攻击,数据还是可以被抓包。

证书锁定(SSL/TLS Pinning)顾名思义,将服务器提供的SSL/TLS证书内置到移动端开发的APP客户端中,当客户端发起请求时,通过比对内置的证书和服务器端证书的内容,以确定这个连接的合法性。

方法一

获取服务器证书摘要

创建一个脚本文件,certs.sh,文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
certs=`openssl s_client -servername $1 -host $1 -port 443 -showcerts </dev/null 2>/dev/null | sed -n '/Certificate chain/,/Server certificate/p'`
rest=$certs
while [[ "$rest" =~ '-----BEGIN CERTIFICATE-----' ]]
do
cert="${rest%%-----END CERTIFICATE-----*}-----END CERTIFICATE-----"
rest=${rest#*-----END CERTIFICATE-----}
echo `echo "$cert" | grep 's:' | sed 's/.*s:\(.*\)/\1/'`
echo "$cert" | openssl x509 -pubkey -noout |
openssl rsa -pubin -outform der 2>/dev/null |
openssl dgst -sha256 -binary | openssl enc -base64
done

使用方法如下,可以看到获取的证书摘要信息:

1
2
3
4
5
6
# ./certs.sh www.jllydj.gov.cn

/CN=www.jllydj.gov.cn
2o2MOiraYxPymc3kSEYyLx8IbdlToM0D5Je9PK/lSlk=
/C=US/O=DigiCert Inc/OU=www.digicert.com/CN=Encryption Everywhere DV TLS CA - G1
GI75anSEdkuHj05mreE0Sd9jE6dVqUIzzXRHHlZBVbI=

通过okhttp设置CertificatePinner
1
2
3
4
5
6
7
CertificatePinner certPinner = new CertificatePinner.Builder()
.add("www.jllydj.gov.cn",
"sha256/2o2MOiraYxPymc3kSEYyLx8IbdlToM0D5Je9PK/lSlk=")
.add("www.jllydj.gov.cn",
"sha256/GI75anSEdkuHj05mreE0Sd9jE6dVqUIzzXRHHlZBVbI=")
.build();
builder.certificatePinner(certPinner);

方法二

Android okHttp 如何实现SSL Pinning
  • 加载可信任的Ca证书

    1
    2
    3
    4
    CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");

    InputStream caInput = getResources().openRawResource(R.raw.ca);
    Certificate ca = certificateFactory.generateCertificate(caInput);
  • 通过Ca创建一个包含可信任证书的keystore文件

    1
    2
    3
    4
    String keyStoreType = KeyStore.getDefaultType();
    KeyStore keyStore = KeyStore.getInstance(keyStoreType);
    keyStore.load(null, null);
    keyStore.setCertificateEntry("ca", ca);
  • Create a custom TrustManager from the trusted CAs in the keystore

    1
    2
    3
    String algorithm = TrustManagerFactory.getDefaultAlgorithm();
    TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(algorithm);
    trustManagerFactory.init(keyStore);
  • 设置到okhttp中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    // Install the all-trusting trust manager
    final SSLContext sslContext = SSLContext.getInstance("SSL");
    sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
    // Create an ssl socket factory with our all-trusting manager
    final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
    builder.sslSocketFactory(sslSocketFactory, (X509TrustManager) trustManagerFactory.getTrustManagers()[0]);
    builder.hostnameVerifier(new HostnameVerifier() {
    @Override
    public boolean verify(String hostname, SSLSession session) {
    return true;
    }
    });

这样就完成了android客户端的SSL PINning设置。

问题

  • 方法一存在的问题
    服务器SSL证书到期前,测试环境更换证书,在更换配置OK后,发现APP停止服务了。所有的请求全部都失败,也就是SSL-PINNING证书到期后会导致APP拒绝服务。

  • 改进:
    可以将证书的publicKey部分提取到客户端硬编码,每次建立TLS连接时候,匹配公钥。
    服务器在更换续费证书时不要更换私钥就行了。推荐需要实现防用户自己抓包的APP采取本方案。

常用SSLpinning绕过方法以及原理介绍

在前面我们已经介绍了SSL pinning的原理以及具体的实现,那么我们想要绕过SSL pinning的话,只需要将证书校验的过程绕过就可以。那么我们可以通过hook的方式来修改证书校验过程,这样就能成功的绕过SSL pinning了。

本人常用且比较熟悉的hook工具有两种,分别是frida和Xposed,这两种工具中都有hook所有https证书校验方法的模块。

其中我使用的比较多的是基于Xposed框架下的Justtrustme模块。该模块对上述所说的https实现证书校验的过程都添加了hook代码,至于具体代码细节我这里就不详细说明了,感兴趣的话可以自己去查看源码

也可以使用国内大神写的太极框架,太极 阳是不需要root的,太极 magic是需要root的。太极框架兼容Xposed框架。https://www.taichi-app.com/#/index

参考资料

分享到