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

因为最近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. 加强服务器接口的安全性, 能保证接口泄露了也不会造成大的危害. 极端点甚至可以公开接口鼓励第三方实现.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容