因为最近ios需要用到https,所以公司的项目都从http的请求转成了https的双向认证,这里我关于安卓端https相关的知识点以及在请求过程中遇到的一些问题。
一、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记录协议之上,用于在实际的数据传输开始前,通讯双方进行身份认证、协商加密算法、交换加密密钥等。
二、https的单双向认证
服务器证书需要配置CA证书,不能是自签名证书,甚至不能是不知名的证书机构签发的证书,否则就会有异常。先说说https的认证原理,如图
相关格式说明
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 信任自签名证书代码示例
// 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盾解决。网上有一些关于客户端安全性的说法,粘出来给大家看下
- 手动输入密码. 如社交类, 电商
- 通过jni, 花指令方式, 或者经过一系列复杂运算绕几圈得出密钥---增加破解难度
- 银行那种用硬件。
- 加强服务器接口的安全性, 能保证接口泄露了也不会造成大的危害. 极端点甚至可以公开接口鼓励第三方实现.