美文网首页Android HttpsAndroid Https
Android 项目https网络请求封装及遇到的一些问题

Android 项目https网络请求封装及遇到的一些问题

作者: 黄海佳 | 来源:发表于2017-02-28 11:19 被阅读741次

    因为最近ios需要用到https,所以公司的项目都从http的请求转成了https的双向认证,这里我关于安卓端https相关的知识点以及在请求过程中遇到的一些问题。

    Paste_Image.png
    一、https的介绍以及相关的专有名词
    https

    简单来说,HTTPS就是“安全版”的HTTP, HTTPS = HTTP + SSL。HTTPS相当于在应用层和TCP层之间加入了一个SSL(或TLS),SSL层对从应用层收到的数据进行加密。TLS/SSL中使用了RSA非对称加密,对称加密以及HASH算法。

    HTTPS和HTTP的区别

    https协议需要到CA申请证书。
    http是超文本传输协议,信息是明文传输;https 则是具有安全性的ssl加密传输协议。
    http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
    http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

    http默认使用80端口,https默认使用443端口
    Https的劣势

    对数据进行加解密决定了它比http慢需要进行非对称的加解密,且需要三次握手,首次连接比较慢点。

    CA证书:

    证书授权中心签发的证书,在访问这类网站的时候,浏览器地址栏的左端一般都有一个绿色盾牌。

    自签名证书:

    Android应用程序开发最可能使用到,一般由服务器管理者生成。平常大家使用自签名证书的情况比较多,因为自签名证书不仅免费,而且有效时间可以比较长。

    TLS:(Transport Layer Security,传输层安全协议)

    用于两个应用程序之间提供保密性和数据完整性。TLS 1.0是IETF(Internet Engineering Task Force,Internet工程任务组)制定的一种新的协议,它建立在SSL 3.0协议规范之上,是SSL 3.0的后续版本,可以理解为SSL 3.1,它是写入了 RFC的。

    该协议由两层组成: TLS 记录协议(TLS Record)和 TLS 握手协议(TLS Handshake)。
    SSL(Secure Socket Layer,安全套接字层)

    为Netscape所研发,用以保障在Internet上数据传输之安全,利用数据加密(Encryption)技术,可确保数据在网络上之传输过程中不会被截取。它已被广泛地用于Web浏览器与服务器之间的身份认证和加密数据传输。SSL协议位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持。

    SSL协议可分为两层:

    1)SSL记录协议(SSL Record Protocol):它建立在可靠的传输协议(如TCP)之上,为高层协议提供数据封装、压缩、加密等基本功能的支持。
    2)SSL握手协议(SSL Handshake Protocol):它建立在SSL记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。

    Paste_Image.png
    二、https的单双向认证

    服务器证书需要配置CA证书,不能是自签名证书,甚至不能是不知名的证书机构签发的证书,否则就会有异常。先说说https的认证原理,如图

    Paste_Image.png
    相关格式说明
    JKS:数字证书库。

    JKS里有KeyEntry和CertEntry,在库里的每个Entry都是靠别名(alias)来识别的。

    P12:是PKCS12的缩写。

    同样是一个存储私钥的证书库,由.jks文件导出的,用户在PC平台安装,用于标示用户的身份。

    CER:俗称数字证书。

    目的就是用于存储公钥证书,任何人都可以获取这个文件 。

    BKS:

    由于Android平台不识别。keystore和.jks格式的证书库文件,因此Android平台引入一种的证书库格式,BKS。

    有些人可能有疑问,为什么Tomcat只有一个server.keystore文件,而客户端需要两个库文件?

    因为有时客户端可能需要访问过个服务,而服务器的证书都不相同,因此客户端需要制作一个truststore来存储受信任的服务器的证书列表。因此为了规范创建一个truststore.jks用于存储受信任的服务器证书,创建一个client.jks来存储客户端自己的私钥。对于只涉及与一个服务端进行双向认证的应用,将server.cer导入到client.jks中也可。

    1 单向认证

    如果你的项目的网络框架是okhttp,那么使用https还是挺简单的,因为okhttp默认支持HTTPS。

    /**
         * HttpsUrlConnection 方式,支持指定**.crt证书验证,此种方式Android官方建议
         * 
         * @throws CertificateException
         * @throws IOException
         * @throws KeyStoreException
         * @throws NoSuchAlgorithmException
         * @throws KeyManagementException
         * @throws NoSuchProviderException
         */
        public void initHttpsConnection() {
            try {
                InputStream in = getAssets().open("server.cer");
                CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
                Certificate  cer = certificateFactory.generateCertificate(in);
                System.out.println("ca=" + ((X509Certificate) cer).getSubjectDN());
    
                // Create a KeyStore containing our trusted CAs
                KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
                keystore.load(null);
                keystore.setCertificateEntry("ca", cer);
    
                // Create a TrustManager that trusts the CAs in our KeyStore
                String algorithm = TrustManagerFactory.getDefaultAlgorithm();
                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(algorithm);
                trustManagerFactory.init(keystore);
    
                // Create an SSLContext that uses our TrustManager
                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, trustManagerFactory.getTrustManagers(), null);
    
                URL url = new URL(URL);
                HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
                conn.setSSLSocketFactory(sslContext.getSocketFactory());
                conn.setHostnameVerifier(DO_NOT_VERIFY);
                conn.setRequestMethod("POST");// 设置请求类型为post
                conn.setConnectTimeout(10000);// 设置超时时间
                conn.setReadTimeout(50000);
                conn.setDoInput(true);
                conn.setDoOutput(true);
                InputStream input = conn.getInputStream();
    
                BufferedReader reader = new BufferedReader(new InputStreamReader(input));
                StringBuffer result = new StringBuffer();
                String line;
                while ((line = reader.readLine()) != null) {
                    result.append(line);
                    Log.d("TAG", line);
                }
    
            } catch (Exception e) {
                Log.e(this.getClass().getName(), e.getMessage());
            }
        }
    
        static HostnameVerifier DO_NOT_VERIFY = new HostnameVerifier() {
            // 信任所有主机
            @Override
            public boolean verify(String hostname, SSLSession session) {
                return true;
            }
        };
    
    三、谷歌官网 HttpsURLConnection 信任自签名证书代码示例
    Paste_Image.png
    // 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);
    
    如何校验域名

    在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

    四、https证书的制作

    很明显要测https,必须要有证书。客户端持有服务端的公钥证书,并持有自己的私钥,服务端持有客户的公钥证书,并持有自己私钥。

    https认证原理:

    1)建立连接的时候,客户端利用服务端的公钥证书来验证服务器是否上是目标服务器。
    2)服务端利用客户端的公钥来验证客户端是否是目标客户端。(请参考RSA非对称加密以及HASH校验算法)
    3)服务端给客户端发送数据时,需要将服务端的证书发给客户端验证,验证通过才运行发送数据。
    4)同样,客户端请求服务器数据时,也需要将自己的证书发给服务端验证,通过才允许执行请求。

    证书制作步骤如下:
    1 生成客户端keystore

    keytool -genkeypair -alias client -keyalg RSA -validity 3650 -keypass 123456 -storepass 123456 -keystore client.jks

    2 生成服务端keystore

    keytool -genkeypair -alias server -keyalg RSA -validity 3650 -keypass 123456 -storepass 123456 -keystore server.keystore
    //注意:CN必须与IP地址匹配,否则需要修改host

    3 导出客户端证书

    keytool -export -alias client -file client.cer -keystore client.jks -storepass 123456

    4 导出服务端证书

    keytool -export -alias server -file server.cer -keystore server.keystore -storepass 123456

    5 重点:证书交换

    将客户端证书导入服务端keystore中,再将服务端证书导入客户端keystore中, 一个keystore可以导入多个证书,生成证书列表。

    • 生成客户端信任证书库(由服务端证书生成的证书库):
      keytool -import -v -alias server -file server.cer -keystore truststore.jks -storepass 123456
    • 将客户端证书导入到服务器证书库(使得服务器信任客户端证书):
      keytool -import -v -alias client -file client.cer -keystore server.keystore -storepass 123456
    6 生成Android识别的BKS库文件

    用Portecle工具转成bks格式,最新版本是1.10。
    下载链接:https://sourceforge.net/projects/portecle/
    运行protecle.jar将client.jks和truststore.jks分别转换成client.bks和truststore.bks,然后放到android客户端的assert目录下
     
    File -> open Keystore File -> 选择证书库文件 -> 输入密码 -> Tools -> change keystore type -> BKS -> save keystore as -> 保存即可
     
    这个操作很简单,如果不懂可自行百度。
     
    我在Windows下生成BKS的时候会报错失败,后来我换到CentOS用OpenJDK1.7立马成功了,如果在这步失败的同学可以换到Linux或Mac下操作,
    将生成的BKS拷贝回Windows即可。

    7 配置Tomcat服务器

    修改server.xml文件,配置8443端口
    <Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
    maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
    clientAuth="true" sslProtocol="TLS"
    keystoreFile="${catalina.base}/key/server.keystore" keystorePass="123456"
    truststoreFile="${catalina.base}/key/server.keystore" truststorePass="123456"/>
     
    备注: - keystoreFile:指定服务器密钥库,可以配置成绝对路径,本例中是在Tomcat目录中创建了一个名为key的文件夹,仅供参考。
    - keystorePass:密钥库生成时的密码
    - truststoreFile:受信任密钥库,和密钥库相同即可
    - truststorePass:受信任密钥库密码

    8 Android App编写BKS读取创建证书自定义的SSLSocketFactory
    private final static String CLIENT_PRI_KEY = "client.bks";
    private final static String TRUSTSTORE_PUB_KEY = "truststore.bks";
    private final static String CLIENT_BKS_PASSWORD = "123456";
    private final static String TRUSTSTORE_BKS_PASSWORD = "123456";
    private final static String KEYSTORE_TYPE = "BKS";
    private final static String PROTOCOL_TYPE = "TLS";
    private final static String CERTIFICATE_FORMAT = "X509";
     
    public static SSLSocketFactory getSSLCertifcation(Context context) {
      SSLSocketFactory sslSocketFactory = null;
      try {
        // 服务器端需要验证的客户端证书,其实就是客户端的keystore
        KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);// 客户端信任的服务器端证书
        KeyStore trustStore = KeyStore.getInstance(KEYSTORE_TYPE);//读取证书
        InputStream ksIn = context.getAssets().open(CLIENT_PRI_KEY);
        InputStream tsIn = context.getAssets().open(TRUSTSTORE_PUB_KEY);//加载证书
        keyStore.load(ksIn, CLIENT_BKS_PASSWORD.toCharArray());
        trustStore.load(tsIn, TRUSTSTORE_BKS_PASSWORD.toCharArray());
        ksIn.close();
        tsIn.close();
        //初始化SSLContext
        SSLContext sslContext = SSLContext.getInstance(PROTOCOL_TYPE);
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(CERTIFICATE_FORMAT);
        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(CERTIFICATE_FORMAT);
        trustManagerFactory.init(trustStore);
        keyManagerFactory.init(keyStore, CLIENT_BKS_PASSWORD.toCharArray());
        sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); 
     
        sslSocketFactory = sslContext.getSocketFactory();
     
      } catch (KeyStoreException e) {...}//省略各种异常处理,请自行添加
      return sslSocketFactory;
    }
    
    9 Android App获取SSLFactory实例进行网络访问
    private void fetchData() {
      OkHttpClient okHttpClient = new OkHttpClient.Builder()
          .sslSocketFactory(SSLHelper.getSSLCertifcation(context))//获取SSLSocketFactory
          .hostnameVerifier(new UnSafeHostnameVerifier())//添加hostName验证器
          .build();
     
      Retrofit retrofit = new Retrofit.Builder()
           .baseUrl("https://10.2.8.56:8443")//填写自己服务器IP
           .addConverterFactory(GsonConverterFactory.create())//添加 json 转换器
           .addCallAdapterFactory(RxJavaCallAdapterFactory.create())//添加 RxJava 适配器
           .client(okHttpClient)
           .build();
     
      IUser userIntf = retrofit.create(IUser.class);
       
      userIntf.getUser(user.getPhone())
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread()) 
            .subscribe(new Subscriber<UserBean>() {
                    //省略onCompleted、onError、onNext
            }
      });
    }
    private class UnSafeHostnameVerifier implements HostnameVerifier {
      @Override
      public boolean verify(String hostname, SSLSession session) {
          return true;//自行添加判断逻辑,true->Safe,false->unsafe
      }
    }
    
    okttp设置证书的方法
    private staticSSLSocketFactorygetSSLSocketFactory_Certificate(Context context, String keyStoreType,intkeystoreResId)
    
    throwsCertificateException, KeyStoreException, IOException, NoSuchAlgorithmException, KeyManagementException {
    
    CertificateFactory cf = CertificateFactory.getInstance("X.509");
    
    InputStream caInput = context.getResources().openRawResource(keystoreResId);
    
    Certificate ca = cf.generateCertificate(caInput);
    
    caInput.close();
    
    if(keyStoreType ==null|| keyStoreType.length() ==0) {
    
    keyStoreType = KeyStore.getDefaultType();
    
    }
    
    KeyStore keyStore = KeyStore.getInstance(keyStoreType);
    
    keyStore.load(null,null);
    
    keyStore.setCertificateEntry("ca", ca);
    
    String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();
    
    TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);
    
    tmf.init(keyStore);
    
    TrustManager[] wrappedTrustManagers =getWrappedTrustManagers(tmf.getTrustManagers());
    
    SSLContext sslContext = SSLContext.getInstance("TLS");
    
    sslContext.init(null, wrappedTrustManagers,null);
    
    returnsslContext.getSocketFactory();
    
    }
    

    调用时把服务器生成的.cer证书放到raw目录下(其他地方自己搞),然后调用:

    SSLSocketFactory sslSocketFactory =getSSLSocketFactory_Certificate(context,"BKS", R.raw.XXX);
    //new一个OkHttpClient
    OkHttpClient okHttpClient = new OkHttpClient();
    okHttpClient.setSslSocketFactory(sslSocketFactory);
    

    Retrofit构造的adapter使用自定义okHttpClient

    RestAdapter.Builder builder =newRestAdapter.Builder()
    
    .setEndpoint(serverUrl)
    
    .setClient(newOkClient(okHttpClient));
    
    五、https遇到的一些坑
    1 Https网络请求的时候经常碰到handshake aborted。解决办法如下
    package com.guiying.common.http;
    
    import android.annotation.SuppressLint;
    import android.content.Context;
    import android.os.Build;
    import android.support.annotation.RawRes;
    
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.InetAddress;
    import java.net.Socket;
    import java.net.UnknownHostException;
    import java.security.KeyManagementException;
    import java.security.KeyStore;
    import java.security.KeyStoreException;
    import java.security.NoSuchAlgorithmException;
    import java.security.UnrecoverableKeyException;
    import java.security.cert.Certificate;
    import java.security.cert.CertificateException;
    import java.security.cert.CertificateFactory;
    import java.security.cert.X509Certificate;
    
    import javax.net.ssl.HostnameVerifier;
    import javax.net.ssl.KeyManager;
    import javax.net.ssl.KeyManagerFactory;
    import javax.net.ssl.SSLContext;
    import javax.net.ssl.SSLSession;
    import javax.net.ssl.SSLSocket;
    import javax.net.ssl.SSLSocketFactory;
    import javax.net.ssl.TrustManager;
    import javax.net.ssl.TrustManagerFactory;
    import javax.net.ssl.X509TrustManager;
    
    /**
     * HttpsUtils来自于https://github.com/guiying712/AndroidModulePattern/blob/master/common/src/main/java/com/guiying/common/http/HttpsUtil.java;
     * 其他参考的文章有:http://android.jobbole.com/83787/;
     *
     * Android 4.X 对TLS1.1、TLS1.2的支持参考了http://blog.csdn.net/joye123/article/details/53888252
     */
    public class HttpsUtil {
    
        /**
         * 包装的 SSL(Secure Socket Layer)参数类
         */
        public static class SSLParams {
            public SSLSocketFactory sSLSocketFactory;
            public X509TrustManager trustManager;
        }
    
        /**
         * @param context        上下文
         * @param certificatesId "XXX.cer" 文件 (文件位置res/raw/XXX.cer)
         * @param bksFileId      "XXX.bks"文件(文件位置res/raw/XXX.bks)
         * @param password       The certificate's password.
         * @return SSLParams
         */
        public static SSLParams getSslSocketFactory(Context context, @RawRes int[] certificatesId, @RawRes int bksFileId, String password) {
            if (context == null) {
                throw new NullPointerException("context == null");
            }
            SSLParams sslParams = new SSLParams();
            try {
                TrustManager[] trustManagers = prepareTrustManager(context, certificatesId);
                KeyManager[] keyManagers = prepareKeyManager(context, bksFileId, password);
    
                //创建TLS类型的SSLContext对象,that uses our TrustManager
                SSLContext sslContext = SSLContext.getInstance("TLS");
    
                X509TrustManager x509TrustManager;
                if (trustManagers != null) {
                    x509TrustManager = new MyTrustManager(chooseTrustManager(trustManagers));
                } else {
                    x509TrustManager = new UnSafeTrustManager();
                }
                //用上面得到的trustManagers初始化SSLContext,这样sslContext就会信任keyStore中的证书
                sslContext.init(keyManagers, new TrustManager[]{x509TrustManager}, null);
    
                //通过sslContext获取SSLSocketFactory对象
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
                    /*Android 4.X 对TLS1.1、TLS1.2的支持*/
                    sslParams.sSLSocketFactory = new Tls12SocketFactory(sslContext.getSocketFactory());
                    sslParams.trustManager = x509TrustManager;
                    return sslParams;
                }
    
                sslParams.sSLSocketFactory = sslContext.getSocketFactory();
                sslParams.trustManager = x509TrustManager;
                return sslParams;
            } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
                throw new AssertionError(e);
            }
        }
    
    
        /**
         * 主机名校验方法
         */
        public static HostnameVerifier getHostnameVerifier() {
            return new HostnameVerifier() {
                @Override
                public boolean verify(String hostname, SSLSession session) {
                    return hostname.equalsIgnoreCase(session.getPeerHost());
                }
            };
        }
    
    
        private static TrustManager[] prepareTrustManager(Context context, int[] certificatesId) {
            if (certificatesId == null || certificatesId.length <= 0) {
                return null;
            }
    
            try {
                //创建X.509格式的CertificateFactory
                CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
                // 创建一个默认类型的KeyStore,存储我们信任的证书
                KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
                keyStore.load(null);
                int index = 0;
                for (int certificateId : certificatesId) {
                    //从本地资源中获取证书的流
                    InputStream cerInputStream = context.getResources().openRawResource(certificateId);
                    String certificateAlias = Integer.toString(index++);
    
                    //certificate是java.security.cert.Certificate,而不是其他Certificate
                    //证书工厂根据证书文件的流生成证书Certificate
                    Certificate certificate = certificateFactory.generateCertificate(cerInputStream);
                    //将证书certificate作为信任的证书放入到keyStore中
                    keyStore.setCertificateEntry(certificateAlias, certificate);
                    try {
                        if (cerInputStream != null)
                            cerInputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
    
                //TrustManagerFactory是用于生成TrustManager的,这里创建一个默认类型的TrustManagerFactory
                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                //用我们之前的keyStore实例初始化TrustManagerFactory,这样trustManagerFactory就会信任keyStore中的证书
                trustManagerFactory.init(keyStore);
                return trustManagerFactory.getTrustManagers();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
    
        private static KeyManager[] prepareKeyManager(Context context, @RawRes int bksFileId, String password) {
    
            try {
                KeyStore clientKeyStore = KeyStore.getInstance("BKS");
                clientKeyStore.load(context.getResources().openRawResource(bksFileId), password.toCharArray());
                KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
                keyManagerFactory.init(clientKeyStore, password.toCharArray());
                return keyManagerFactory.getKeyManagers();
    
            } catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException | CertificateException | IOException e) {
                e.printStackTrace();
            }
            return null;
        }
    
    
        private static X509TrustManager chooseTrustManager(TrustManager[] trustManagers) {
            for (TrustManager trustManager : trustManagers) {
                if (trustManager instanceof X509TrustManager) {
                    return (X509TrustManager) trustManager;
                }
            }
            return null;
        }
    
    
        /**
         * 客户端不对证书做任何检查;
         * 客户端不对证书做任何验证的做法有很大的安全漏洞。
         */
        private static class UnSafeTrustManager implements X509TrustManager {
    
            @SuppressLint("TrustAllX509TrustManager")
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType)
                    throws CertificateException {
            }
    
            @SuppressLint("TrustAllX509TrustManager")
            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType)
                    throws CertificateException {
            }
    
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[]{};
            }
        }
    
    
        private static class MyTrustManager implements X509TrustManager {
            private X509TrustManager defaultTrustManager;
            private X509TrustManager localTrustManager;
    
            private MyTrustManager(X509TrustManager localTrustManager) throws NoSuchAlgorithmException, KeyStoreException {
                //TrustManagerFactory是用于生成TrustManager的,创建一个默认类型的TrustManagerFactory
                TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
                trustManagerFactory.init((KeyStore) null);
                defaultTrustManager = chooseTrustManager(trustManagerFactory.getTrustManagers());
                this.localTrustManager = localTrustManager;
            }
    
    
            @SuppressLint("TrustAllX509TrustManager")
            @Override
            public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
    
            }
    
            @Override
            public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
                try {
                    defaultTrustManager.checkServerTrusted(chain, authType);
                } catch (CertificateException ce) {
                    localTrustManager.checkServerTrusted(chain, authType);
                }
            }
    
    
            @Override
            public X509Certificate[] getAcceptedIssuers() {
                return new X509Certificate[0];
            }
        }
    
    
        /**
         * 自行实现SSLSocketFactory ,实现Android 4.X 对TLSv1.1、TLSv1.2的支持
         */
        private static class Tls12SocketFactory extends SSLSocketFactory {
    
            private static final String[] TLS_SUPPORT_VERSION = {"TLSv1.1", "TLSv1.2"};
    
            final SSLSocketFactory delegate;
    
            private Tls12SocketFactory(SSLSocketFactory base) {
                this.delegate = base;
            }
    
            @Override
            public String[] getDefaultCipherSuites() {
                return delegate.getDefaultCipherSuites();
            }
    
            @Override
            public String[] getSupportedCipherSuites() {
                return delegate.getSupportedCipherSuites();
            }
    
            @Override
            public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
                return patch(delegate.createSocket(s, host, port, autoClose));
            }
    
            @Override
            public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
                return patch(delegate.createSocket(host, port));
            }
    
            @Override
            public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException, UnknownHostException {
                return patch(delegate.createSocket(host, port, localHost, localPort));
            }
    
            @Override
            public Socket createSocket(InetAddress host, int port) throws IOException {
                return patch(delegate.createSocket(host, port));
            }
    
            @Override
            public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
                return patch(delegate.createSocket(address, port, localAddress, localPort));
            }
    
            private Socket patch(Socket s) {
                //代理SSLSocketFactory在创建一个Socket连接的时候,会设置Socket的可用的TLS版本。
                if (s instanceof SSLSocket) {
                    ((SSLSocket) s).setEnabledProtocols(TLS_SUPPORT_VERSION);
                }
                return s;
            }
        }
    
    
    }
    
    2 SSLHandshakeException

    导致SSLHandshakeException是由于签署证书的CA不被系统所信任。一种原因是CA机构比较新,还没被android系统证书库内置。或者你的android版本比较旧,证书库不全。
    可能有以下几个原因:

    1.签名证书的CA机构不知名
    2.使用了自签名的证书
    3.服务端配置问题

    这种情况的解决方案和使用了自签名证书的方案是一样的,重写Google默认的证书校验逻辑。

    六、最后

    虽然https算是加密成功了,但是客户端里面放私钥并不安全。 很容易被拿出来. 所以银行都用u盾解决。网上有一些关于客户端安全性的说法,粘出来给大家看下

    1. 手动输入密码. 如社交类, 电商
    1. 通过jni, 花指令方式, 或者经过一系列复杂运算绕几圈得出密钥---增加破解难度
    2. 银行那种用硬件。
    3. 加强服务器接口的安全性, 能保证接口泄露了也不会造成大的危害. 极端点甚至可以公开接口鼓励第三方实现.

    相关文章

      网友评论

      • Evil_98c0:为什么我照抄的有代码变红,找不到类跟方法
        我 加载了okhttp 2.5.0.jar 和okio1.6,0.jar 和retrofit2.0.0.jar

      本文标题:Android 项目https网络请求封装及遇到的一些问题

      本文链接:https://www.haomeiwen.com/subject/jexxgttx.html