Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 300|回复: 0

[默认分类] Android Https的安全使用

[复制链接]
  • TA的每日心情
    开心
    2021-12-13 21:45
  • 签到天数: 15 天

    [LV.4]偶尔看看III

    发表于 2018-6-27 10:50:27 | 显示全部楼层 |阅读模式


      一.Https介绍


      HTTP协议是没有加密的明文传输协议,如果APP采用HTTP传输数据,则会泄露传输内容,可能被中间人劫持,修改传输的内容。
      



      HTTPS是HTTP over SSL,HTTP是应用层协议,TCP是传输层协议,在应用层和传输层之间,增加了一个安全套接层SSL。
      安全套接字层 (SSL)现在技术上称为传输层安全协议(TLS)。
      SSL/TLS层负责客户端和服务器之间的加解密算法协商、密钥交换、通信连接的建立。


      



      
       在典型的 HTTPS 使用场景中,会使用一个包含公钥及与其匹配的私钥的证书配置服务器。作为 SSL 客户端与服务器握手的一部分,服务器将通过使用公钥加密签署其证书来证明自己具有私钥。
       不过,任何人都可以生成他们自己的证书和私钥,因此,一个简单的握手只能说明服务器知道与证书公钥匹配的私钥,除此之外什么都证明不了。解决此问题的一个方法是让客户端拥有其信任的一个或多个证书集。如果证书不在此集合中,则不会信任服务器。
       但这个简单的方法有几个缺点。服务器应能够随时间的推移升级到更强的密钥(“密钥旋转”),使用新的公钥替换证书中的公钥。遗憾的是,客户端应用现在必须根据服务器配置发生的变化进行更新。如果服务器不在应用开发者的控制下(例如,如果服务器是一个第三方网络服务),则很容易出现问题。如果应用必须与网络浏览器或电子邮件应用等任意服务器通信,那么,此方法也会带来问题。
       为弥补这些缺点,通常使用来自知名颁发者(证书颁发机构(CA))发放的证书配置服务器。主机平台一般包含其信任的知名 CA 的列表。从 Android 4.2 开始,Android 目前包含在每个版本中更新的 100 多个 CA。CA 具有一个证书和一个私钥,这点与服务器相似。为服务器发放证书时,CA 使用其私钥签署服务器证书。然后,客户端可以验证该服务器是否具有平台已知的 CA 发放的证书。
       不过,在解决一些问题的同时,使用 CA 也会引发其他问题。因为 CA 为许多服务器发放证书,因此,您仍需要某种方式来确保您与您需要的服务器通信。为解决这个问题,CA 发放的证书通过类似 gmail.com 等具体名称或 *.Google.com 等通配型主机集识别服务器。
       以下示例会让这些概念更具体。下面的代码段来自命令行,
    1. openssl
    复制代码
    工具的
    1. s_client
    复制代码
    命令将查看维基百科( Wikipedia) 的服务器证书信息。它指定端口 443,因为此端口是 HTTPS的默认端口。此命令将
    1. openssl
    2. s_client
    复制代码
    的输出发送到
    1. openssl x509
    复制代码
    ,后者将根据 X.509 标准 格式化与证书有关的信息。具体而言,此命令获取subject和issuer信息,分别包含服务器名称信息 和 可认证 CA 的颁发结构。如下:
      
    1. $ openssl s_client -connect wikipedia.org:443 | openssl x509 -noout -subject -issuer
    2. subject= /serialNumber=sOrr2rKpMVP70Z6E9BT5reY008SJEdYv/C=US/O=*.wikipedia.org/OU=GT03314600/OU=See www.rapidssl.com/resources/cps (c)11/OU=Domain Control Validated - RapidSSL(R)/CN=*.wikipedia.org
    3. issuer= /C=US/O=GeoTrust, Inc./CN=RapidSSL CA
    复制代码
      你会看到证书是由 RapidSSL CA 为与 *.wikipedia.org 匹配的服务器发放的。

    file:///C:/Users/Administrator/Documents/My%20Knowledge/temp/82419978-ffb9-4b83-9eba-7fd1073904d4/128/index_files/f6dbfd63-060b-44fa-92a7-6ce1b66ab855.png

       
      HTTPS通信所用到的证书由CA提供,需要在服务器中进行相应的设置才能生效。另外在我们的客户端设备中,只要访问的HTTPS的网站所用的证书是知名CA根证书签发的,如果这些CA又在浏览器或者操作系统的根信任列表中,就可以直接访问。而如
      http://12306.cn
      网站,它的证书是非可信CA提供的,是自己签发的,所以在用谷歌浏览器打开时,会提示“您的连接不是私密连接”,证书是非可信CA颁发的:


      



      file:///C:/Users/Administrator/Documents/My%20Knowledge/temp/82419978-ffb9-4b83-9eba-7fd1073904d4/128/index_files/27e286d4-2d8c-49c0-a048-9787987b2142.png
      
       
       所以在
       http://12306.cn
       的网站首页会提示“
       为保障您顺畅购票,请下载安装
       根证书
       ”,操作系统安装后,就不会再有上图的提示了。
      
      
       
      


      二. Https使用


      1. 访问知名CA发放证书的Https服务器
    1. URL url = new URL("https://wikipedia.org");
    2. URLConnection urlConnection = url.openConnection();
    3. InputStream in = urlConnection.getInputStream();
    4. copyInputStreamToOutputStream(in, System.out);
    复制代码


      假设要访问一个由知名 CA 发放证书的网络服务器,那么,可以使用上述简单代码发起安全的请求,因为Android平台已经包含了该CA。
      



      而使用私有CA签发的证书的服务器使用上述方式就无法访问。


      



      2.访问使用未知CA或私有CA签发证书的服务器


      如果要使用私有CA或未知CA签发的证书,因为Android系统还不信任该CA的证书,会出现异常javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found。
       


      



      对服务器证书进行校验
      必须重写校验证书链TrustManager中的方法
      checkServerTrusted()
      。


    1.     private void requestFromServer(final Context context, String https_url) throws NoSuchAlgorithmException, KeyManagementException, IOException {
    2.         TrustManager[] trustAllCerts = new TrustManager[]{
    3.                 new X509TrustManager() {
    4.                     @Override
    5.                     public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    6.                     }
    7.                     @Override
    8.                     public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    9.                         //校验服务器证书
    10.                         if (chain == null) {
    11.                             throw new IllegalArgumentException("Check Server X509Certificates is null");
    12.                         }
    13.                         if (chain.length < 0) {
    14.                             throw new IllegalArgumentException("Check Server X509Certificates is empty");
    15.                         }
    16.                         for (X509Certificate cert : chain) {
    17.                             cert.checkValidity();
    18.                             try {
    19.                                 String certName = "abc.crt"; //一般将下载的证书放到项目中的assets目录下
    20.                                 InputStream certInput = new BufferedInputStream(context.getAssets().open(certName));
    21.                                 CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
    22.                                 X509Certificate serverCert = (X509Certificate) certificateFactory.generateCertificate(certInput);
    23.                                 cert.verify(serverCert.getPublicKey());
    24.                             } catch (IOException e) {
    25.                                 e.printStackTrace();
    26.                             } catch (NoSuchAlgorithmException e) {
    27.                                 e.printStackTrace();
    28.                             } catch (InvalidKeyException e) {
    29.                                 e.printStackTrace();
    30.                             } catch (NoSuchProviderException e) {
    31.                                 e.printStackTrace();
    32.                             } catch (SignatureException e) {
    33.                                 e.printStackTrace();
    34.                             }
    35.                         }
    36.                     }
    37.                     @Override
    38.                     public X509Certificate[] getAcceptedIssuers() {
    39.                         return new X509Certificate[0];
    40.                     }
    41.                 }
    42.         };
    43.         SSLContext sslContext = SSLContext.getInstance("TLS");
    44.         sslContext.init(null, trustAllCerts, null);
    45.         HostnameVerifier hostnameVerifier = new HostnameVerifier() {
    46.             @Override
    47.             public boolean verify(String hostname, SSLSession session) {
    48.                 //校验服务器证书的域名是否相符(若不校验服务器证书域名则直接return true)
    49.                 HostnameVerifier hv = HttpsURLConnection.getDefaultHostnameVerifier();
    50.                 Boolean result = hv.verify("*.xxx.com", session);
    51.                 return result;
    52.             }
    53.         };
    54.         URL url = new URL(https_url);
    55.         HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection();
    56.         httpsURLConnection.setSSLSocketFactory(sslContext.getSocketFactory());
    57.         httpsURLConnection.setHostnameVerifier(hostnameVerifier);
    58. //        httpsURLConnection.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); //信任所有主机(注意:此处SSLSocketFactory与sslContext.getSocketFactory()返回的是不同的类)
    59.         InputStream in = httpsURLConnection.getInputStream();
    60.         //网络返回的数据处理...
    61.     }
    复制代码


      



      另一种实现方式


      通过保存在assets路径下的证书文件获取特定的证书,用该 CA 创建
    1. KeyStore
    复制代码
      ,然后用后者创建和初始化
    1. TrustManager
    复制代码
      。
      TrustManager
       是系统用于从服务器验证证书的工具,可以使用一个或多个 CA 从
      KeyStore
       创建,而创建的
      TrustManager
       
      将仅信任这些 CA。
      如果是新的
      TrustManager
       
      ,此示例将初始化一个新的
    1. SSLContext
    复制代码
      ,后者可以提供一个
    1. SSLSocketFactory
    复制代码
      ,您可以通过
    1. HttpsURLConnection
    复制代码
       用它来替换默认的
      SSLSocketFactory
      。这样一来,网络请求时将使用您的 CA 验证证书,
      系统能够验证您的服务器证书是否来自值得信任的颁发者。


    1. [i] /**
    2. [/i][i] * 获取校验服务器端证书的SocketFactory
    3. [/i][i] *
    4. [/i][i] * [/i][b][i]@param [/i][/b][i]context
    5. [/i][i] [/i][i]* [/i][b][i]@param [/i][/b][i]certName [/i][i]保存在assets路径下的服务器端证书文件名,如abc.crt
    6. [/i][i] * [/i][b][i]@return
    7. [/i][/b][b][i] [/i][/b][i]*/
    8. [/i][i] [/i]public static javax.net.ssl.SSLSocketFactory getSocketFactory(Context context, String certName) throws IOException, CertificateException,
    9.             KeyStoreException, NoSuchAlgorithmException, KeyManagementException {
    10.         InputStream certInput = new BufferedInputStream(context.getAssets().open(certName));
    11.         //以X.509格式获取证书
    12.         CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
    13.         Certificate cert = certificateFactory.generateCertificate(certInput);
    14.         //生成一个包含服务器端证书的KeyStore
    15.         String keyStoreType = KeyStore.getDefaultType();
    16.         KeyStore keyStore = KeyStore.getInstance(keyStoreType);
    17.         keyStore.load(null, null);
    18.         keyStore.setCertificateEntry("cert", cert);
    19.         //用包含服务器端证书的KeyStore生成一个TrustManager
    20.         String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
    21.         TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(tmfAlgorithm);
    22.         trustManagerFactory.init(keyStore);
    23.         //生成一个使用我们TrustManager的SSLContext
    24.         SSLContext sslContext = SSLContext.getInstance("TLS");
    25.         sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
    26.         return sslContext.getSocketFactory();
    27.     }
    28.     public static void test(final Context context) {
    29.         new Thread() {
    30.             @Override
    31.             public void run() {
    32.                 super.run();
    33.                 String https_url = "https://www.xxx.com";
    34.                 try {
    35.                     URL url = new URL(https_url);
    36.                     HttpsURLConnection httpsURLConnection = (HttpsURLConnection) url.openConnection();
    37.                     httpsURLConnection.setSSLSocketFactory(getSocketFactory(context, "abc.cer"));
    38.                     InputStream in = httpsURLConnection.getInputStream();
    39.                     ...
    40.                     
    41.                 } catch (MalformedURLException e) {
    42.                     e.printStackTrace();
    43.                 } catch (IOException e) {
    44.                     e.printStackTrace();
    45.                 } catch (CertificateException e) {
    46.                     e.printStackTrace();
    47.                 } catch (NoSuchAlgorithmException e) {
    48.                     e.printStackTrace();
    49.                 } catch (KeyStoreException e) {
    50.                     e.printStackTrace();
    51.                 } catch (KeyManagementException e) {
    52.                     e.printStackTrace();
    53.                 }
    54.             }
    55.         }.start();
    56.     }
    复制代码


      



      3.使用OKHttp访问https服务器


    1. private void requestFromServer(final Context context, String https_url) throws NoSuchAlgorithmException, KeyManagementException, IOException {
    2.         X509TrustManager[] trustAllCerts = new X509TrustManager[]{
    3.                 new X509TrustManager() {
    4.                     @Override
    5.                     public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    6. [code]                        //不校验客户端证书
    复制代码
                       }

                        @Override
                        public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                            //不校验服务器证书
                        }

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

            SSLContext sslContext = SSLContext.getInstance("TLS");
            sslContext.init(null, trustAllCerts, null);

            HostnameVerifier hostnameVerifier = new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    //不校验服务器证书的域名
                    return true;
                }
            };

            OkHttpClient okHttpClient = new OkHttpClient.Builder().hostnameVerifier(hostnameVerifier)
                    .sslSocketFactory(sslContext.getSocketFactory(), trustAllCerts[0]).build();

            Request request = new Request.Builder().url(https_url).build();

            Call call = okHttpClient.newCall(request);
            call.enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                   
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {

                }
            });

        }[/code]


      上述方法中未校验服务器证书和域名,可以参考前面的方法进行具体校验。


      



      4.WebView访问https服务器


      webview加载H5页面,在webView.setWebViewClient(webviewClient)时重载WebViewClient的onReceivedSslError(),如果出现证书错误会回调该方法,在其中直接调用handler.proceed()会忽略错误继续加载证书有问题的页面,如果调用handler.cancel()可以终止加载证书有问题的页面,证书出现问题了,可以提示用户风险,让用户选择加载与否。
      



    1.     public class MyWebViewClient extends WebViewClient {
    2.         ...
    3.         @Override
    4.         public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
    5.             handler.proceed();
    6.         }
    7.     }
    复制代码


      



      参考


      [1]
      https://developer.android.google.cn/training/articles/security-ssl.html?hl=zh-cn#Concepts


      [2]
      https://developer.android.google.cn/training/articles/security-ssl.html
      



      [3]
      https://zhuanlan.zhihu.com/p/22816331
      
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2025-2-24 08:42 , Processed in 0.296640 second(s), 36 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表