Android HTTPS自签名和CA证书验证(基于OkHttp)

  • HTTPS介绍
  • CA证书
  • 自签名证书
  • 问题描述
  • 域名校验
  • OkHttp设置
  • 总结

HTTPS介绍

HTTPS是一种通过计算机网络进行安全通信的传输协议,经由HTTP进行通信,利用SSL/TLS建立全信道,加密数据包。HTTPS使用的主要目的是提供对网站服务器的身份认证,同时保护交换数据的隐私与完整性。简单来说,HTTPS就是“安全版”的HTTP, HTTPS = HTTP + SSL。HTTPS相当于在应用层和TCP层之间加入了一个SSL/TLS,SSL层对从应用层收到的数据进行加密。TLS/SSL中使用了RSA非对称加密,对称加密以及HASH算法。

PS:TLS是传输层安全协议,前身是SSL协议,它是一种新的协议,建立在SSL 3.0协议规范之上,是SSL 3.0的后续版本,可以理解为SSL 3.1,它是写入了 RFC。TLS是基于 X.509 认证,他假定所有的数字证书都是由一个层次化的数字证书认证机构 CA发出的。

CA证书

描述:如果现在 A 要与远端的 B 建立安全的连接进行通信:

  1. 直接使用对称加密通信,那么密钥无法安全的送给 B 。
  2. 直接使用非对称加密,B 使用 A 的公钥加密,A 使用私钥解密。但是因为B无法确保拿到的公钥就是A的公钥,因此也不能防止中间人攻击。

为了解决上述问题,引入了一个第三方,也就是上面所说的CA(Certificate Authority):  
CA 用自己的私钥签发数字证书,数字证书中包含A的公钥。然后 B 可以用 CA 的根证书中的公钥来解密 CA 签发的证书,从而拿到A的公钥。那么又引入了一个问题,如何保证 CA 的公钥是合法的呢?答案就是现代主流的浏览器会内置 CA 的证书。

中间证书:
现在大多数CA不直接签署服务器证书,而是签署中间CA,然后用中间CA来签署服务器证书。这样根证书可以离线存储来确保安全,即使中间证书出了问题,可以用根证书重新签署中间证书。另一个原因是为了支持一些很古老的浏览器,有些根证书本身,也会被另外一个很古老的根证书签名,这样根据浏览器的版本,可能会看到三层或者是四层的证书链结构,如果能看到四层的证书链结构,则说明浏览器的版本很老,只能通过最早的根证书来识别
校验过程:
实际上,在 HTTPS 握手开始后,服务器会把整个证书链发送到客户端,给客户端做校验。校验的过程是要找到这样一条证书链,链中每个相邻节点,上级的公钥可以校验通过下级的证书,链的根节点是设备信任的锚点或者根节点可以被锚点校验。那么锚点对于浏览器而言就是内置的根证书啦(注:根节点并不一定是根证书)。校验通过后,视情况校验客户端,以及确定加密套件和用非对称密钥来交换对称密钥。从而建立了一条安全的信道。

自签名证书

数字证书除了由第三方私钥签名(比如由ca机构签名),其实也可以用自己的私钥签名。
用自己的私钥给自己的公钥签名的证书称为自签名证书。自签名证书可以自我验证,即可以拿证书的公钥解证书的签名。由第三方私钥签名的证书就不是自签名证书,验证时需要由存放第三方公钥的证书验证。

问题描述

Android对于Https的支持在系统层面上已经帮我们封装的很好了,系统会内置合法ca机构的根证书,只要服务器证书是由这些机构或者是其中间机构签发的那么系统会自动做安全校验,我们不需要做任何事情,直接请求https就可以。

但是如果服务器证书是自签名的,就需要我们对请求逻辑做一定的改造,不然你发起的所有请求都会失败,因为系统在做ssl证书校验时会认为这是一个非法请求直接阻断掉。

接下来探讨下一个案例:A公司旗下运营着某某app产品,周末这个app某个模块功能突然就拿不到数据,正在度假的小黄啊马上被传召回来公司加班解决问题,小黄开始打测试包调试哦,发现数据很正常啊,因为这块代码不是小黄负责,忙活了很久,这时网络中心的人突然说了句:周五我们换了服务器端证书(证书过期了),这个会不会有影响啊?小黄:“。。。”,小黄毕竟有点实力的,过一会就查出了问题所在:原来客户端本地内置了份证书,生产环境对其进行了校验,而测试环境则是忽略了证书的校验。这时领导认为这种方案有点问题,希望客户端不依赖证书(网络框架使用的是OkHttp3以上)。

从这个案例可以看出A公司本来的做法应该是这样的:
测试环境用的是自签名证书,该环境下信任所有证书设置:对于非CA机构颁发的证书和自签名证书,可以忽略证书校验

  /*** 创建信任所有证书的套接字工厂** @return*/@SuppressLint("TrulyRandom")public static SSLSocketFactory createTrustAllSSLSocketFactory() {SSLSocketFactory sSLSocketFactory = null;try {SSLContext sc = SSLContext.getInstance("TLS");sc.init(null, new TrustManager[]{new TrustAllManager()}, new SecureRandom());sSLSocketFactory = sc.getSocketFactory();} catch (Exception ignored) {}return sSLSocketFactory;}/*** 信任所有的证书*/public static class TrustAllManager implements X509TrustManager {@SuppressLint("TrustAllX509TrustManager")@Overridepublic void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {}@SuppressLint("TrustAllX509TrustManager")@Overridepublic void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {//            //检查所有证书
//            try {//                TrustManagerFactory factory = TrustManagerFactory.getInstance("X509");
//                factory.init((KeyStore) null);
//                for (TrustManager trustManager : factory.getTrustManagers()) {//                    ((X509TrustManager) trustManager).checkServerTrusted(chain, authType);
//                }
//                //获取网络中的证书信息
//                X509Certificate certificate = chain[0];
//                // 证书拥有者
//                String subject = certificate.getSubjectDN().getName();
//                // 证书颁发者
//                String issuer = certificate.getIssuerDN().getName();
//
//                LogManager.getLogger().e("HHHTEST:-->", "证书拥有者:" + subject);
//                LogManager.getLogger().e("HHHTEST:-->", "证书颁发者:" + issuer);
//
//            } catch (Exception e) {//                e.printStackTrace();
                Log.e("HHHTEST", "Exception:" + e.getMessage());
//                LogManager.getLogger().e("HHHTEST:-->", "Exception:" + e.getMessage());
//            }}@Overridepublic X509Certificate[] getAcceptedIssuers() {return new X509Certificate[0];}}

如果不这样设置,直接访问该https域名是会报错的:

javax.net.ssl.SSLHandshakeException:  java.security.cert.CertPathValidatorException:   Trust anchor for certification path not found.

正式环境的证书设置:客户端内置服务器中间证书(根证书),请求时对其进行身份校验(单向验证)

/*** 检验内置证书** @param context* @return*/public static SSLSocketFactory getSSlFactory(Context context) {try {CertificateFactory cf = CertificateFactory.getInstance("X.509");//把证书打包在asset文件夹中 BuildConfig.AUTH_CERT:证书名称InputStream caInput = new BufferedInputStream(context.getAssets().open(BuildConfig.AUTH_CERT));Certificate ca;try {ca = cf.generateCertificate(caInput);LogManager.getLogger().d("Longer", "ca=" + ((X509Certificate) ca).getSubjectDN());LogManager.getLogger().d("Longer", "key=" + ((X509Certificate) ca).getPublicKey());} finally {caInput.close();}// Create a KeyStore containing our trusted CAsString keyStoreType = KeyStore.getDefaultType();KeyStore keyStore = KeyStore.getInstance(keyStoreType);keyStore.load(null, null);keyStore.setCertificateEntry("ca", ca);// Create a TrustManager that trusts the CAs in our KeyStoreString tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);tmf.init(keyStore);// Create an SSLContext that uses our TrustManagerSSLContext s = SSLContext.getInstance("TLSv1", "AndroidOpenSSL");s.init(null, tmf.getTrustManagers(), null);return s.getSocketFactory();} catch (CertificateException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();} catch (NoSuchAlgorithmException e) {e.printStackTrace();} catch (KeyStoreException e) {e.printStackTrace();} catch (KeyManagementException e) {e.printStackTrace();} catch (NoSuchProviderException e) {e.printStackTrace();}return null;}

这种做法安全系数还是比较高的:在客户端内置服务器的证书,我们在校验服务端证书的时候只比对和App内置的证书是否完全相同,如果不同则断开连接。那么此时再遭遇中间人攻击劫持我们的请求时由于黑客服务器没有相应的证书,此时HTTPS请求校验不通过,则无法与黑客的服务器建立起连接。但是这需要考虑证书的有效期,升级等,如果证书过期了,app也需要更新,并且还存在旧版本客户端无法访问的问题,这就有点头大了。

PS:其实还有个不错的的信任方式是把签发我们服务器的证书的根证书导出打包到 APK 中,然后用上述的方式做信任处理。仔细思考一下,这未尝不是一种好的方式。只要日后换证书还用这家 CA 签发,既不用担心失效,安全性又有了一定的提高。因为比起信任100多个根证书,只信任一个风险会小很多。正如最开始所说,信任锚点未必需要根证书,因此同样上面的代码也可以用于自签名证书的信任。

域名校验

Android 内置的 SSL 的实现是引入了Conscrypt 项目,而 HTTP(S)层则是使用的OkHttp。而 SSL 层只负责校验证书的真假,对于所有基于SSL 的应用层协议,需要自己来校验证书实体的身份。

    /*** 信任域名*/public static class TrustXHostnameVerifier implements HostnameVerifier {@Overridepublic boolean verify(String hostname, SSLSession session) {//BASE_URL 域名boolean verify = HttpsURLConnection.getDefaultHostnameVerifier().verify(BASE_URL, session);LogManager.getLogger().e("HHHTEST", "hostname:" + hostname);LogManager.getLogger().e("HHHTEST", "verify:" + verify);return verify;}}

verify方法中对比了请求的IP和服务器的IP是否一致,一致则返回true表示校验通过,否则返回false,检验不通过,断开连接。对于网上有些处理是直接返回true,即不对请求的服务器IP做校验,我们不推荐这样使用。而且现在谷歌应用商店已经对此种做法做了限制,禁止在verify方法中直接返回true的App上线。

OkHttp设置

测试环境

  mOkHttpClient = new OkHttpClient().newBuilder().connectTimeout(10, TimeUnit.SECONDS)//设置超时时间.readTimeout(10, TimeUnit.SECONDS)//设置读取超时时间.writeTimeout(10, TimeUnit.SECONDS)//设置写入超时时间.sslSocketFactory(SSLUtils.createTrustAllSSLSocketFactory()).hostnameVerifier(new SSLUtils.TrustXHostnameVerifier()).build();

正式环境

            mOkHttpClient = new OkHttpClient().newBuilder().connectTimeout(10, TimeUnit.SECONDS)//设置超时时间.readTimeout(10, TimeUnit.SECONDS)//设置读取超时时间.writeTimeout(10, TimeUnit.SECONDS)//设置写入超时时间.sslSocketFactory(SSLUtils.getSSlFactory(context)).hostnameVerifier(new SSLUtils.TrustXHostnameVerifier()).build();

对于上述两种方法都是有点问题的,我们需要考虑安全性和证书过期,升级的问题。正式的https域名的申请是需要CA机构认证的,所以我们可以综合这两种方法,不设置SSLSocketFactory,因为OkHttp默认是支持Https的,默认的 SSLSocketFactory 校验服务器的证书时,会信任Android设备内置的100多个根证书(CA证书),对于此种情况,虽然可以正常访问到服务器,但是仍然存在安全隐患。假如黑客自家搭建了一个服务器并申请到了CA证书,由于我们客户端没有内置服务器证书,默认信任所有CA证书(客户端可以访问所有持有由CA机构颁发的证书的服务器),那么黑客仍然可以发起中间人攻击劫持我们的请求到黑客的服务器,实际上就成了我们的客户端和黑客的服务器建立起了连接。

总结

  1. 在正式环境,公司应该都是用的ca证书,所以线上环境的https请求直接用系统的默认实现就可以。
  2. 在测试环境,出于成本考虑有些公司的https接口会用自签名证书,为了方便测试比如抓包什么的,所以我们可以在测试环境统一信任所有证书。
  3. 如果有些应用对安全性要求实在很高,那么再采用信任指定证书的方式。

Android https 自签名和CA证书验证(基于OkHttp)相关推荐

  1. SSL/TLS协议的运行原理浅析—https通信过程及CA证书诠释

    互联网是开放环境,通信双方都是未知身份,这为协议的设计带来了很大的难度.而且,协议还必须能够经受所有匪夷所思的攻击,这使得SSL TLS协议变得异常复杂.理清https原理与CA证书体系 互联网的通信 ...

  2. HTTPS加密过程和TLS证书验证

    HTTPS加密过程和TLS证书验证 HTTPS 是在 HTTP 和 TCP 之间建立了一个安全层,HTTP 与 TCP 通信的时候,必须先进过一个安全层,对数据包进行加密,然后将加密后的数据包传送给 ...

  3. BurpSuite抓https的包/BurpSuite CA证书下载

    BurpSuite抓https的包/BurpSuite CA证书下载 Burp Suite要抓HTTPS的包的话,是需要有Burp Suite的CA证书的 为什么要证书这里就不说了,下面是具体步骤 1 ...

  4. ssl握手过程和ca证书验证

    转载:https://www.cnblogs.com/cposture/p/9029014.html SSL 认证 可以将 SSL 服务器与客户端之间的通信配置为使用单向或双向 SSL 认证. 单向 ...

  5. JAVA使用HttpURLConnection请求HTTPS网站,不需要证书验证的DEMO教程

    最近在获取一个购物网站商品,发现浏览器和postman每次都可以请求到内容,但是java代码尝试了各种方式都是Connection refused: connect,一开始以为是java代码的问题,后 ...

  6. HttpClient访问https,设置忽略SSL证书验证

    访问https时报错:sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provid ...

  7. 小程序ssl报错java,抖音(字节跳动)小程序遇到request(https)请求报错,SSL证书验证失败...

    今天做抖音小程序时遇到的奇怪问题: 本地测试都正常,真机调试时也正常,准备上线了,把http改成https,调用tt.request()时就报错. tt.request()报错如下: "re ...

  8. Https优化方案(优化证书验证篇--OCSP)

    一句话概括就是:OCSP 是server 把自己的站点证书和中间证书以及根证书打包一起下发到客户端,省去客户端查询的过程. OCSP实时查询会增加客户端的性能开销.因此,可以考虑通过OCSP stap ...

  9. ca根证书校验 java_JAVA-Android-根据CA证书验证X509Certificate(颁发者证书)

    最后能够使用以下过程验证证书.我希望这对别人有帮助-- public void validateCertificate() throws Exception { try { String issuer ...

最新文章

  1. GNN笔记:傅里叶变换
  2. php formdata 多个图片保存_PHP-FPM是什么?
  3. Web前端培训分享:Web前端到底是什么?
  4. mysql left join两个表,mysql left join 多个表
  5. android驱动测试,Android: 通过 cucumber 驱动 monkey 做稳定性测试
  6. 【DTOJ】2701:问候
  7. JSP-08-第三方控件的使用
  8. NetAssist.exe网络调试工具
  9. 科沃斯扫地机器人阿尔法_科沃斯阿尔法智能清洁扫地机器人配置详解
  10. 部署在IIS上的网站程序以管理员权限运行 设置方法
  11. 51单片机一些软件的使用
  12. 小牛电动Q2营收、利润双增,李一男身价却在6个月内减少8亿美元
  13. 转回到Edison上开发
  14. 计算机如何连接wifi台式,回答如何将台式计算机连接到WiFi
  15. 分享29个超赞的响应式Web设计
  16. 如何在CTEX上对论文进行排版
  17. 国内安卓统一推送通道
  18. 【PIC】单片机基本概述
  19. 刚兑换的电子优惠券竟被提前消费,原来是黑客做起倒爷生意
  20. 百药食坊-团队项目开始介绍

热门文章

  1. matlab调频调幅立体声接收机,基于simulink调幅调频发射接收机的设计.doc
  2. 学计算机在职硕士,计算机在职研究生的学习方式有哪些?
  3. 教师查询系统C语言,C语言教师管理系统代码
  4. 图解PKCS#1(合)
  5. 如何看中兴70后程序员从公司跳楼
  6. 成都计算机职业学院排名,成都市计算机专业职业院校排名
  7. python神经网络分析案例_python神经网络实战
  8. ensp 交换机与路由器ospf_华为三层交换机和路由器建立OSPF关系
  9. android中app分享小程序卡片及跳转回app
  10. WordNet相关API介绍及语义相似度计算方法