谈谈Android https的安全使用

前言

https利用PKI体系对web通信进行认证和加密,认证和加密的重要性不言而喻。在实现https过程中,证书校验这一环节起到至关重要的作用,但经常会出现各种问题。本文梳理了Android应用中校验https证书过程会出现的各种情况

首先简单说下证书校验逻辑:

如下图,网站使用的https证书存在某个证书路径下,最顶层的是全球公认的根证书(Root CA),接着是根证书CA签发的二级证书,然后是网站证书,即证书链结构。

(目前市场上全球信任的支持浏览器的 SSL证书主要有 3 种: EV SSL证书、OV SSL证书和 DV SSL证书)

其中根证书有很多个(也有不可靠、被攻击的根证书),证书层级也可以不止三层。

网站证书是否可信,必须要证明其在某个可信根证书链中。

 

Android app的https通信中,存在各种方式对待https证书

1、app忽略https证书错误

app在开发测试过程中使用测试版服务器资源,其https证书可能使用openssl自行生成,不存在于任何可信根证书链中,app加载https资源时会提升证书错误,于是研发便在代码层忽略证书错误

》忽略webview中的https证书错误

@Override
public void onReceivedSslError(WebView view,
        SslErrorHandler handler, SslError error) {
    handler.proceed();//忽略错误证书, 危险
    //handler.cancel();
}

》忽略URLConnection中的https通信错误

//httpClient or UrlConnection使用自定义的X509TrustManager, 且getAcceptedIssuers返回为空, 
会信任所有https, 危险

public class MySSLSocketFactory extends SSLSocketFactory {
    SSLContext sslContext = SSLContext.getInstance("TLS");
//TrustManager自定义
    public MySSLSocketFactory(KeyStore truststore)
            throws NoSuchAlgorithmException, KeyManagementException,
                KeyStoreException, UnrecoverableKeyException {
        super(truststore);
        TrustManager tm = new X509TrustManager() {

            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return null;
            }

            @Override
            public void checkClientTrusted(X509Certificate[] chain,
                    String authType) throws CertificateException {
            
            }

            @Override
            public void checkServerTrusted(X509Certificate[] chain,
                    String authType) throws CertificateException {
            }
        };

        sslContext.init(null, new TrustManager[] { tm }, null);
    }

    @Override
    public Socket createSocket() throws IOException {
        return sslContext.getSocketFactory().createSocket();
    }

    @Override
    public Socket createSocket(Socket socket, String host, int port,
            boolean autoClose) throws IOException, UnknownHostException {
        return sslContext.getSocketFactory().createSocket(socket, host, port,
                autoClose);
    }

    public static SSLSocketFactory getSocketFactory() {
        try {
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            trustStore.load(null, null);
            SSLSocketFactory factory = new MySSLSocketFactory(trustStore);
            return factory;
        } catch (Exception e) {
            e.getMessage();
            return null;
        }
    }
}

//schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));
//schReg.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));
HttpsURLConnection.setDefaultSSLSocketFactory(MySSLSocketFactory.getSocketFactory());
//HttpsURLConnection.setDefaultSSLSocketFactory(SSLSocketFactory.getSocketFactory());//安全的写法

代码中忽略了证书错误,导致“网络中间人”使用任何证书都可以进行https通信嗅探,https形同http

 

2、代码中实现SSL Pinning(证书绑定)

顶级根证书存在太多,而且有不可靠、被攻击的根证书。这样即使证书校验通过,也可能是“中间人”攻击者签发的证书,仍然可以嗅探通信

SSL Pinning:使用指定的N个证书链进行https证书校验过程,或指定证书hash值与网站证书对比

在TrustManager的checkServerTrusted函数中对比证书hash

这里提供另外一种使用本地证书库的方式

public class MySSLSocketFactory1 extends SSLSocketFactory {

    public MySSLSocketFactory1(KeyStore truststore)
            throws NoSuchAlgorithmException, KeyManagementException,
                KeyStoreException, UnrecoverableKeyException {
        super(truststore);
    }

    public static SSLSocketFactory getSocketFactory(Context context) {
        InputStream input = null;
        try {
//          //cer,der证书
//          input = context.getResources().openRawResource(R.raw.fiddler);//
//          CertificateFactory cerFactory = CertificateFactory.getInstance("X.509");
//            Certificate cer = cerFactory.generateCertificate(input);
//            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
//            keyStore.load(null, null);
//            keyStore.setCertificateEntry("ca", cer);
            
            //密钥库
            KeyStore keyStore = KeyStore.getInstance("BKS");
            InputStream in = context.getResources().openRawResource(R.raw.my);
            try {
                keyStore.load(in, "123123".toCharArray());
            } finally {
                in.close();
            }
            
            SSLSocketFactory factory = new MySSLSocketFactory1(keyStore);

            return factory;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            if (input != null) {
                try {
                    input.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                input = null;
            }
        }
    }
}

 

Android各个框架如何实现SSL pinning,参考https://medium.com/@appmattus/android-security-ssl-pinning-1db8acb6621e

(另外dns提供了更直接的解决方案,浏览器也有相应的HPKP解决方案

DNS CAA:通过DNS配置,对外声明哪个CA可以签发自己域名的SSL证书。防止中间ca作恶

https://blog.qualys.com/ssllabs/2017/03/13/caa-mandated-by-cabrowser-forum

 

3、配置文件中增加ssl pinning

代码中实现较麻烦,官方支持在Android 7/N(API24)版本直接在配置文件中控制证书校验

AndroidManifest.xml

res/xml/network_security_config.xml

还可以配置可信CA,强制https通信等

官方参考https://developer.android.com/training/articles/security-config

 

4、忽略用户/管理员安装证书

SSL Pinning在https证书过期阶段处理比较麻烦,一般app仅使用https通信,并不绑定证书

没有绑定证书,可以通过在手机上安装代理(fiddler)的证书,然后对app的抓包分析

但是Android 7.0(minSdkVersion>24)之后,Android系统默认不接受用户安装的证书,如下

想要分析app网络数据,需要root之后写入证书,或使用低版本手机,或者重打包app

(安装证书到system store:先安装到user store /data/misc/keystore,然后将文件复制到/etc/security/cacerts

https://nelenkov.blogspot.com/2011/12/ics-trust-store-implementation.html)

 

结语

使用https是必然趋势,且使用SSL pinning也是必然趋势,为了更安全

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值