Android实现https项目实战

相信很多客户端开发人员特别是Android开发人员对https如何在程序中使用存有疑惑,项目中有的说“什么都不用校验校验都在服务端”、“ios都不用校验,系统自带处理机制”……之类的,不管是使用Volley、OkHttp还是其他网络框架,不做校验或使用不安全校验确实能是客户端正常访问https的接口服务地址,但是如此以来给app特别是Android版本的应用程序带来了极大安全隐患。
网上查找解决方案,千篇一律,思路都说的很好,具体怎么实现呢?
下面是个人对Android实现https配置的经验及代码,分享学习,仅做参考,有问题请留言。

客户端https认证方式

  1. 信任所有——不做处理,没有起到校验作用,有风险
  2. 单向认证——自定义信任规则,校验服务端证书
  3. 单向认证——使用预埋证书,校验服务端证书(自签名证书)
  4. 双向认证——使用bks证书和密码管理客户端证书(双向认证),使用预埋证书,校验服务端证书(自签名证书)

    网上查到大多数代码类似如下,此种方式即为不做校验处理,风险很高:

 /**
     * 为了解决客户端不信任服务器数字证书的问题,网络上大部分的解决方案都是让客户端不对证书做任何检查,
     * 这是一种有很大安全漏洞的办法
     */
    public static X509TrustManager UnSafeTrustManager = new X509TrustManager() {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        //TODO 此处应该加入自定义的校验规则
        }

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

 // 创建TLS类型的SSLContext对象, that uses our TrustManager
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new TrustManager[]{manager}, null);
//下面以OkHttp设置https为例,其他网络框架一样设置setSslSocketFactory();
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.sslSocketFactory(sslContext.getSocketFactory());
OkHttpClient okHttpClient = builder.build();

下面试我的实现方式:单向认证——自定义信任规则,校验服务端证书+使用预埋证书校验服务端证书公钥有效性

/**
     * 自定义校验规则:1、校验服务端证书有效性;2、使用本地预埋证书校验服务端证书公钥(需要提前把服务端cer证书放入Android功能的assets目录下,此种方式要考虑都证书有效期,如果过期需要app端及时替换证书——更具自身情况可以去掉预埋证书key校验代码)
     */
    private class SafeTrustManager implements X509TrustManager {
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
            LogUtils.d("checkServerTrusted 校验服务端证书");
            if (chain == null) {
                throw new IllegalArgumentException("Check server X509Certificate is null");
            }
            if (chain.length < 0) {
                throw new IllegalArgumentException("Check server X509Certificate is empty");
            }

            //读取本地预埋服务端证书获取其publicKey与服务中携带的key比对
            InputStream is_local = null;
            try {
                is_local = new BufferedInputStream(getAssets().open("替换为自己的证书名称.cer"));//提前把证书放入assets中
            } catch (IOException e) {
                e.printStackTrace();
                LogUtils.e(e.getMessage());
            }
            X509Certificate serverCert = (X509Certificate) CertificateFactory.getInstance("X.509").generateCertificate(is_local);

            for (X509Certificate certificate : chain) {
                // 检查证书是否过期,签名是否通过等
                certificate.checkValidity();
                // 校验服务端证书公钥,与app预埋证书公钥作比对
                try {
                    LogUtils.i("publicKey==" + serverCert.getPublicKey());
                    certificate.verify(serverCert.getPublicKey());
                } catch (NoSuchAlgorithmException e) {
                    e.printStackTrace();
                    LogUtils.e(e.getMessage());
                } catch (InvalidKeyException e) {
                    e.printStackTrace();
                    LogUtils.e(e.getMessage());
                } catch (NoSuchProviderException e) {
                    e.printStackTrace();
                    LogUtils.e(e.getMessage());
                } catch (SignatureException e) {
                    e.printStackTrace();
                    LogUtils.e(e.getMessage());
                }
            }
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }//自定义trustManager完成

 // 创建TLS类型的SSLContext对象, that uses our TrustManager
            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, new TrustManager[]{new SafeTrustManager()}, null);
//下面以OkHttp设置https为例,其他网络框架一样设置setSslSocketFactory();
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.sslSocketFactory(sslContext.getSocketFactory());
OkHttpClient okHttpClient = builder.build();

如此就实现了自定义的trustManager校验规则,还是需要注意预埋证书是有有效期限制的,请慎用,一旦证书失效会导致服务请求不通——建议app添加版本更新接口,而且该接口不要使用强校验的https请求,使用普通请求,如果证书失效至少能保证版本更新接口正常访问便于用户更新新的apk版本。

另外,配置完校验规则,如果使用阿里聚安全等加固检测网站检测经常会报https hostname校验的高危漏洞,所以最好再添加对服务域名的强校验,而不是采用信任所有地址的方式(builder.hostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);//此写法等于没有做校验)

下面是我的校验方式:

/**
     * 验证服务端证书域名是否匹配——有可能与服务请求地址不一致
     */
    private class SafeHostnameVerifier implements HostnameVerifier {
        @Override
        public boolean verify(String hostname, SSLSession session) {
            //注意此处有可能与服务请求地址域名不一致,请于申请服务端证书的人员确认申请域名
            LogUtils.d("校验服务域名");
            return hostname.equals("*ver.zp.com");
//            return true;
        }
    }

//以OkHttp为例设置方式——使用服务域名强校验
 builder.hostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);
        builder.hostnameVerifier(new SafeHostnameVerifier());

总结为下:
客户端实现https配置:添加自定义校验规则、校验服务域名

更多关于Android https的问题及HttpUrlConnection使用https方式 请移步:
http://blog.csdn.net/u011084603/article/details/73873677

关于webView中设置Https的方法可移步:http://www.cnblogs.com/liyiran/p/7011317.html

获取证书两种方法

  1. 服务器组直接给。如测试 12306 网站的时候,进入网页,12306 会提供根证书的下载
  2. 通过网页获取。 Chrome 浏览器,按 F12 选择 Security 选择 Certificate Error 的 View
    certificate 在弹出的证书,选择详细信息
    在详细信息页面,点击复制到文件,一路下一步,然后选择保存证书的地方,就把证书成功导出来了。

证书的读取
从 assert 中读取文件
InputStream is = getAssets().open(“root.cer”);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值