• Android通信安全之HTTPS


    Android通信安全之HTTPS

    目录

    Android通信安全之HTTPS

    Https

    起因

    问题描述

    自定义X509TrustManager

    自定义HostnameVerifier

    修复方案

    解决方案一

    解决方案2


     

     

     

    本文章向大家介绍Android通信安全之HTTPS,主要内容包括Https、起因、问题描述、自定义HostnameVerifier、修复方案、解决方案2、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

     

     

     

    Https

    HTTPS(全称:Hyper Text Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。 它是一个URI scheme(抽象标识符体系),句法类同http:体系。用于安全的HTTP数据传输。https:URL表明它使用了HTTP,但HTTPS存在不同于HTTP的默认端口及一个加密/身份验证层(在HTTP与TCP之间)。这个系统的最初研发由网景公司(Netscape)进行,并内置于其浏览器Netscape Navigator中,提供了身份验证与加密通讯方法。现在它被广泛用于万维网上安全敏感的通讯,例如交易支付方面。(注:本段来自百度百科)

     

     

    问题描述

    对于数字证书相关概念、Android 里 https 通信代码就不再复述了,直接讲问题。缺少相应的安全校验很容易导致中间人攻击,而漏洞的形式主要有以下3种:

    自定义X509TrustManager

    在使用HttpsURLConnection发起 HTTPS 请求的时候,提供了一个自定义的X509TrustManager,未实现安全校验逻辑,下面片段就是当时新浪微博 sdk 内部的代码片段。如果不提供自定义X509TrustManager,代码运行起来可能会报异常(原因下文解释),初学者就很容易在不明真相的情况下提供了一个自定义的X509TrustManager,却忘记正确地实现相应的方法。本文重点介绍这种场景的处理方式。这里引用部分相关代码:

    1. TrustManager tm = new X509TrustManager() {
    2. public void checkClientTrusted(X509Certificate[] chain, String authType)
    3. throws CertificateException {
    4. //do nothing,接受任意客户端证书
    5. }
    6. public void checkServerTrusted(X509Certificate[] chain, String authType)
    7. throws CertificateException {
    8. //do nothing,接受任意服务端证书
    9. }
    10. public X509Certificate[] getAcceptedIssuers() {
    11. return null;
    12. }
    13. };
    14. sslContext.init(null, new TrustManager[] { tm }, null);

    自定义HostnameVerifier

    在握手期间,如果 URL 的主机名和服务器的标识主机名不匹配,则验证机制可以回调此接口的实现程序来确定是否应该允许此连接。如果回调内实现不恰当,默认接受所有域名,则有安全风险。

    1. HostnameVerifier hnv = new HostnameVerifier() {
    2. @Override
    3. public boolean verify(String hostname, SSLSession session) {
    4. // Always return true,接受任意域名服务器
    5. return true;
    6. }
    7. };
    8. HttpsURLConnection.setDefaultHostnameVerifier(hnv);

    如上,如果不做任何的教研就是有风险的。

    1. SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
    2. sf.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);

    修复方案

    分而治之,针对不同的漏洞点分别描述,这里就讲的修复方案主要是针对非浏览器App,非浏览器 App 的服务端通信对象比较固定,一般都是自家服务器,可以做很多特定场景的定制化校验。如果是浏览器 App,校验策略就有更通用一些。前面说到,当发起 HTTPS 请求时,可能抛起一个异常,以上面说到的代码来看:

    1. try {
    2. URL url = new URL("https://certs.cac.washington.edu/CAtest/");
    3. URLConnection urlConnection = url.openConnection();
    4. InputStream in = urlConnection.getInputStream();
    5. copyInputStreamToOutputStream(in, System.out);
    6. } catch (MalformedURLException e) {
    7. e.printStackTrace();
    8. } catch (IOException e) {
    9. e.printStackTrace();
    10. }
    11. private void copyInputStreamToOutputStream(InputStream in, PrintStream out) throws IOException {
    12. byte[] buffer = new byte[1024];
    13. int c = 0;
    14. while ((c = in.read(buffer)) != -1) {
    15. out.write(buffer, 0, c);
    16. }
    17. }

    它会抛出一个SSLHandshakeException的异常。这里截取部分异常。

    1. javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
    2. ....//省略n多错误
    3. at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:318)
    4. ... 10 more
    5. Caused by: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
    6. ... 16 more

    解决方案一

    不论是权威机构颁发的证书还是自签名的,打包一份到 app 内部,比如存放在 asset 里。通过这份内置的证书初始化一个KeyStore,然后用这个KeyStore去引导生成的TrustManager来提供验证,具体代码如下:

    1. try {
    2. CertificateFactory cf = CertificateFactory.getInstance("X.509");
    3. // uwca.crt 打包在 asset 中,该证书可以从https://itconnect.uw.edu/security/securing-computer/install/safari-os-x/下载
    4. InputStream caInput = new BufferedInputStream(getAssets().open("uwca.crt"));
    5. Certificate ca;
    6. try {
    7. ca = cf.generateCertificate(caInput);
    8. Log.i("Longer", "ca=" + ((X509Certificate) ca).getSubjectDN());
    9. Log.i("Longer", "key=" + ((X509Certificate) ca).getPublicKey();
    10. } finally {
    11. caInput.close();
    12. }
    13. // Create a KeyStore containing our trusted CAs
    14. String keyStoreType = KeyStore.getDefaultType();
    15. KeyStore keyStore = KeyStore.getInstance(keyStoreType);
    16. keyStore.load(null, null);
    17. keyStore.setCertificateEntry("ca", ca);
    18. // Create a TrustManager that trusts the CAs in our KeyStore
    19. String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
    20. TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
    21. tmf.init(keyStore);
    22. // Create an SSLContext that uses our TrustManager
    23. SSLContext context = SSLContext.getInstance("TLSv1","AndroidOpenSSL");
    24. context.init(null, tmf.getTrustManagers(), null);
    25. URL url = new URL("https://certs.cac.washington.edu/CAtest/");
    26. HttpsURLConnection urlConnection =
    27. (HttpsURLConnection)url.openConnection();
    28. urlConnection.setSSLSocketFactory(context.getSocketFactory());
    29. InputStream in = urlConnection.getInputStream();
    30. copyInputStreamToOutputStream(in, System.out);
    31. } catch (CertificateException e) {
    32. e.printStackTrace();
    33. } catch (IOException e) {
    34. e.printStackTrace();
    35. } catch (NoSuchAlgorithmException e) {
    36. e.printStackTrace();
    37. } catch (KeyStoreException e) {
    38. e.printStackTrace();
    39. } catch (KeyManagementException e) {
    40. e.printStackTrace();
    41. } catch (NoSuchProviderException e) {
    42. e.printStackTrace();
    43. }

    这样访问非“UW Services CA Test Page”就会报SSLHandshakeException。也就是说对于特定证书生成的TrustManager,只能验证与特定服务器建立安全链接,这样就提高了安全性。

    解决方案2

    同方案1,打包一份到证书到 app 内部,但不通过KeyStore去引导生成的TrustManager,而是干脆直接自定义一个TrustManager,自己实现校验逻辑; 校验逻辑主要包括: •服务器证书是否过期 •证书签名是否合法

    1. try {
    2. CertificateFactory cf = CertificateFactory.getInstance("X.509");
    3. // uwca.crt 打包在 asset 中,该证书可以从https://itconnect.uw.edu/security/securing-computer/install/safari-os-x/下载
    4. InputStream caInput = new BufferedInputStream(getAssets().open("uwca.crt"));
    5. final Certificate ca;
    6. try {
    7. ca = cf.generateCertificate(caInput);
    8. Log.i("Longer", "ca=" + ((X509Certificate) ca).getSubjectDN());
    9. Log.i("Longer", "key=" + ((X509Certificate) ca).getPublicKey());
    10. } finally {
    11. caInput.close();
    12. }
    13. // Create an SSLContext that uses our TrustManager
    14. SSLContext context = SSLContext.getInstance("TLSv1","AndroidOpenSSL");
    15. context.init(null, new TrustManager[]{
    16. new X509TrustManager() {
    17. @Override
    18. public void checkClientTrusted(X509Certificate[] chain,
    19. String authType)
    20. throws CertificateException {
    21. }
    22. @Override
    23. public void checkServerTrusted(X509Certificate[] chain,
    24. String authType)
    25. throws CertificateException {
    26. for (X509Certificate cert : chain) {
    27. // Make sure that it hasn't expired.
    28. cert.checkValidity();
    29. // Verify the certificate's public key chain.
    30. try {
    31. cert.verify(((X509Certificate) ca).getPublicKey());
    32. } catch (NoSuchAlgorithmException e) {
    33. e.printStackTrace();
    34. } catch (InvalidKeyException e) {
    35. e.printStackTrace();
    36. } catch (NoSuchProviderException e) {
    37. e.printStackTrace();
    38. } catch (SignatureException e) {
    39. e.printStackTrace();
    40. }
    41. }
    42. }
    43. @Override
    44. public X509Certificate[] getAcceptedIssuers() {
    45. return new X509Certificate[0];
    46. }
    47. }
    48. }, null);
    49. URL url = new URL("https://certs.cac.washington.edu/CAtest/");
    50. HttpsURLConnection urlConnection =
    51. (HttpsURLConnection)url.openConnection();
    52. urlConnection.setSSLSocketFactory(context.getSocketFactory());
    53. InputStream in = urlConnection.getInputStream();
    54. copyInputStreamToOutputStream(in, System.out);
    55. } catch (CertificateException e) {
    56. e.printStackTrace();
    57. } catch (IOException e) {
    58. e.printStackTrace();
    59. } catch (NoSuchAlgorithmException e) {
    60. e.printStackTrace();
    61. } catch (KeyManagementException e) {
    62. e.printStackTrace();
    63. } catch (NoSuchProviderException e) {
    64. e.printStackTrace();
    65. }

    同样上述代码只能访问 certs.cac.washington.edu 相关域名地址,如果访问 其他网址 ,则会在cert.verify(((X509Certificate) ca).getPublicKey());处抛异常,导致连接失败。

    自定义HostnameVerifier,建立匹配规则;业务复杂的话,还可以结合配置中心、白名单、黑名单、正则匹配等多级别动态校验;总体来说逻辑还是比较简单的,反正只要正确地实现那个方法。

    1. HostnameVerifier hnv = new HostnameVerifier() {
    2. @Override
    3. public boolean verify(String hostname, SSLSession session) {
    4. //示例
    5. if("yourhostname".equals(hostname)){
    6. return true;
    7. } else {
    8. HostnameVerifier hv =
    9. HttpsURLConnection.getDefaultHostnameVerifier();
    10. return hv.verify(hostname, session);
    11. }
    12. }
    13. };

    主机名验证策略改成严格模式:

    1. SSLSocketFactory sf = new MySSLSocketFactory(trustStore);
    2. sf.setHostnameVerifier(SSLSocketFactory.STRICT_HOSTNAME_VERIFIER);

    这样就有效的避免了Hook。

     

  • 相关阅读:
    第一章 数据库SQL-Server(及安装管理详细)
    Hadoop面试问题总结
    Python中list列表的常见操作
    小白必看:测试人有必要参考的软件测试工作规范
    推荐系统离线评估方法和评估指标,以及在推荐服务器内部实现A/B测试和解决A/B测试资源紧张的方法。还介绍了如何在TensorFlow中进行模型离线评估实践。
    HTML 颜色
    项目讲解说明
    北京小程序开发如何选择开发团队与开发语言?
    winform 获取指定的form
    牛客小白月赛#56 A~F
  • 原文地址:https://blog.csdn.net/2301_78835635/article/details/133952303