你的 Android HTTPS 真的安全吗? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
ZCPgyer
V2EX    蒲公英

你的 Android HTTPS 真的安全吗?

  •  
  •   ZCPgyer 2016-09-28 16:22:43 +08:00 9220 次点击
    这是一个创建于 3366 天前的主题,其中的信息可能已经有所发展或是发生改变。

    随着数据量级和维度的不断增加,数据安全已经成为移动互联网行业重点关注的领域。尤其是数据传输过程,是相对比较容易被拦截和嗅探的,因此谷歌对数据传输安全提出了更高的要求,推动 Android 开发者使用 HTTPS 来提升安全能力。但是现实中, Android 开发人员对 HTTPS 的使用却存在一些问题。以下是 TalkingData 的研发总监卢健给大家做的分享。

    背景

    Google Play 的开发者政策中心对“隐私和安全”有如下描述:

    如果您的应用会处理用户个人数据或敏感数据(包括个人身份信息、财务和付款信息、身份验证信息、电话簿或联系人数据、麦克风和相机传感器数据,以及敏感的设备数据),则必须:

    • 同时在 Play Developer Console 中的指定位置以及通过 Play 分发的应用内提供隐私权政策。
    • 以安全无虞的方式处理用户数据,包括使用新型加密技术(例如通过 HTTPS )传输数据。

    随着数据量级和维度的不断增加,数据安全已经成为移动互联网行业重点关注的领域。尤其是数据传输过程,是相对比较容易被拦截和嗅探的,因此谷歌对数据传输安全提出了更高的要求,推动 Android 开发者使用 HTTPS 来提升安全能力。

    但是现实中, Android 开发人员对 HTTPS 的使用却存在一些问题,包括:

    1.多数 HTTPS 是基于 HttpClient 来实现,但 Android 已不再默认支持 HttpClient 开发库,需要切换;

    2.为了方便,开发人员一般会默认信任所有域名,这是个较大的安全隐患;

    3.也是为了方便,证书校验也采用默认的设置,没有正确校验自签名证书;

    关键还是在于如何正确校验证书和域名。

    介绍 Android HTTPS 的文章有很多,比如谷歌官方 Android Develop Training 中有一篇文章讲 Securitywith HTTPS and SSL ,从基本概念和一个简单的 HTTPS 案例开始,帮助大家理解使用 HTTPS 可能遇到的问题以及应对方案,知识性很强。当然,阅读之前,最好已经掌握以下基础知识:

    Java Security 基础知识 HTTPS 的通信原理 Android 网络基础 证书,证书文件格式 SSL TLS 秘钥管理, JSSE 等

    HTTPS 的简单例子

    这里是 Google 提供的 HTTPS 例子:

     URL url = newURL("https://wikipedia.org"); URLConnection urlCOnnection=url.openConnection(); InputStream in=urlConnection.getInputStream(); copyInputStreamToOutputStream(in, System.out); 

    以上代码表明,可以用 HTTPS URL 直接获取地址内容。隐含的意思是,服务器证书需要配置 CA 证书,不能是自签名证书,甚至不能是不知名的证书机构签发的证书,否则就会有异常。

    关于证书,有几个概念说明:

    CA 证书:证书授权中心签发的证书,在访问这类网站的时候,浏览器地址栏的左端一般都有一个绿色盾牌。 不知名机构签发的证书:不是国际公认的签发机构,比如 12306 用的证书是中铁数字证书认证中心签发的。 自签名证书: Android 应用程序开发最可能使用到,一般由服务器管理者生成。

    平常大家使用自签名证书的情况比较多,因为自签名证书不仅免费,而且有效时间可以比较长。 但是使用自签名证书的时候,需要移动端开发人员自己实现校验代码,增加了开发的难度。尤其是如果开发人员没有经验,未对证书和域名做正确校验,则可能引入安全隐患。 这里重点讨论一下如何校验自签名证书和域名。

    如何校验自签名证书

    Google 文章中关于如何让 HttpsURLConnection 信任自签名证书有如下代码(我们补充了中文注释):

     // Load CAs from an InputStream // (could be from a resource or ByteArrayInputStreamor ...) // X.509 是 Android 唯一支持的证书格式 CertificateFactory cf =CertificateFactory.getInstance("X.509"); // From[https://www.washington.edu/itconnect/security/ca/load-der.crt]( https://www.washington.edu/itconnect/security/ca/load-der.crt) InputStream caInput = newBufferedInputStream(new FileInputStream("load-der.crt")); // 证书的加载也可以是字符串的方式,建议使用字符串,并且对字符串做些额外的处理 // InputStream caInput=newByteArrayInputStream(cerString.getBytes()); Certificate ca; try { ca =cf.generateCertificate(caInput); System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN()); } finally { caInput.close(); } // Create a KeyStore containing our trusted CAs // KeyStore 默认类型是 BKS ,虽然 Android 的文档中的例子写了 JKS ,但是 Android 并不支持 JKS String keyStoreType =KeyStore.getDefaultType(); KeyStore keyStore = KeyStore.getInstance(keyStoreType); keyStore.load(null, null); // 加载一个默认的秘钥仓库,仓库是空的 keyStore.setCertificateEntry("ca",ca); // Create a TrustManager that trusts the CAs inour KeyStore // TrustManager 是证书校验的关键,不使用系统默认校验方式时,需要开发者自己实现接口,完成校验代码 String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();// 默认算法 PKIX TrustManagerFactory tmf =TrustManagerFactory.getInstance(tmfAlgorithm); tmf.init(keyStore); // Create an SSLContext that uses ourTrustManager // Android 不仅支持 TLS ,还有 TLSv1.2 等, TLSv1.2 需要 API levels 20+ // 更多选择可以参考[https://developer.android.com/re ... /ssl/SSLSocket.html]( https://developer.android.com/reference/javax/net/ssl/SSLSocket.html) SSLContext cOntext=SSLContext.getInstance("TLS"); context.init(null, tmf.getTrustManagers(),null); // Tell the URLConnection to use aSocketFactory from our SSLContext URL url = newURL("https://certs.cac.washington.edu/CAtest/"); HttpsURLConnection urlCOnnection= (HttpsURLConnection)url.openConnection(); // 证书校验的关键,设置 SSLSocketFactory // 执行 TrustManagerImpl 中 checkServerTrusted 方法完成服务器证书校验 urlConnection.setSSLSocketFactory(context.getSocketFactory()); InputStream in =urlConnection.getInputStream(); copyInputStreamToOutputStream(in, System.out); 

    以上代码,可以解决证书信任问题。但同时需要注意的是,这里是基于 Android 默认的信任检查来解决的。因为我们没有对 TrustManager 做任何修改,如果仍然遇到证书校验不通过的情况,则需要重新实现 TrustManager ,请用以下代码代替“ tmf.getTrustManagers()”:

     TrustManager tm = new X509TrustManager() { @Override public X509Certificate[] getAcceptedIssuers() {return null; } @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throwsCertificateException {} @Override public void checkClientTrusted(X509Certificate[] chain, String authType) throwsCertificateException { // 方法直接返回, 将不对服务器证书做任何校验 // 确认服务器端证书颁发者和代码中的证书主体一致 if (!chain[0].getIssuerDN().equals(cert.getSubjectDN())) { } // 确认服务器端证书被代码中证书公钥签名 chain[0].verify(cert.getPublicKey()); // 确认服务器端证书没有过期 chain[0].checkValidity(); } }; context.init(null, new TrustManager[]{tm},null); 

    context.init(null, new TrustManager[]{tm},null);通过重新实现 TrustManager ,一定可以搞定证书校验。最好不要信任所有证书,这样会大大降低安全能力。

    如何校验域名

    在 HttpsURLConnection 中校验域名是比较简单的,这里 Google 提供了为 URLConnection 替换验证过程的例子:

     // Create an HostnameVerifier that hardwiresthe expected hostname. // Note that is different than the URL'shostname: // example.com versus example.org // 这里强调的是 URL 中的域名和证书中不一致,所以默认的校验不能通过 HostnameVerifier hostnameVerifier = newHostnameVerifier() { @Override publicboolean verify(String hostname, SSLSession session) { // 这段代码中 hostname 应该是 example.org // 获取默认的 HostnameVerifier HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier(); // 如果直接返回 true ,等于不做域名校验 return hv.verify("example.com", session); } }; // Tell the URLConnection to use ourHostnameVerifier URL url = newURL("https://example.org/"); HttpsURLConnection urlCOnnection=(HttpsURLConnection)url.openConnection(); urlConnection.setHostnameVerifier(hostnameVerifier); InputStream in =urlConnection.getInputStream(); copyInputStreamToOutputStream(in, System.out); 

    正确使用域名校验很重要,直接返回 true 显然不安全。回调参数 hostname 是访问的域名, session 可以通过 getPeerCertificates 获取到证书的相关信息,如果需要自己写代码完成域名校验,需要根据实际开发情况正确校验。

    总结

    用 HttpsURLConnection 来校验证书和域名的方法如下(不明确指定校验方式时,系统会采用默认的方式):

     HttpsURLConnection urlCOnnection=(HttpsURLConnection)url.openConnection(); urlConnection.setSSLSocketFactory(context.getSocketFactory()); urlConnection.setHostnameVerifier(hostnameVerifier); 

    校验的顺序是先校验证书,再校验域名。为了能够更好的理解 Android HTTPS 证书校验和域名校验的默认实现,可以参考 Android 的源代码:

    • 证书校验的默认实现: 类: TrustManagerImpl.java

    Git 获取源码: git clonehttps://android.googlesource.com/platform/external/conscrypt

    • 域名校验的默认实现: 类: OkHostnameVerifier.java

    Git 获取源码: git clonehttps://android.googlesource.com/platform/external/okhttp

    这些知识也不算复杂,但是确实不少移动开发者容易忽视的。希望通过以上分享,能帮助开发者给移动用户提供一个安全的服务环境。


    蒲公英专家测试近日也推出了针对 App 客户端进行包括上文提到的 HTTPS 安全问题的漏洞和安全风险测试,对服务端(服务器、通信)进行深入的渗透测试。端到端的评估安全风险,并提供修复建议。

    蒲公英拥有拥有众多专业、资深安全行业专家,多年安全行业渗透测试经验,为您的产品保驾护航。让您不再担忧您 App 的安全问题,详情可前往蒲公英安全性测试查看详情。

    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5506 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 20ms UTC 01:49 PVG 09:49 LAX 17:49 JFK 20:49
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86