作者:时光 date:2015-05-26

遇到一个在Android上实现https双向认证的需求,之前没有做过,故在网上查阅。发现网上的Demo大体分为两种办法:

  1. 信任所有主机证书
  2. 使用证书认证(自签名)

第一种方法,不安全,故在此不做讨论,主要记录一下第二种的实现。

在操作之前,我们先了解一下https的原理和运行过程。
Android Https详解

那么,我们可以知道,在Android中,我们需要两个东东来进行https的验证:

  1. 客户端要给服务器端认证的证书(xxx.p12)
  2. 客户端验证服务器端的证书库(xxx.keystore PS:此处的证书库在Android中只支持BKS模式)

那么,看一下我们现在已知的材料:

  1. 客户端证书 client.p12
  2. 服务端证书 service.cer

PS:如果你什么都没有,教程在这:Android HTTPS SSL双向验证
那么,客户端证书,已经有了,现在就差客户端验证服务器端的证书库了。 如果是客户端来验证服务端,那么我们需要将服务器端的证书来导入到keystore。要生成keystore,我们需要用到java中自带的 keytool 这个工具。
但是java中自带的keytool只能生成JKS模式的keystore,而Android中需要的模式是BKS,因此我们还需要另外一个工具来进行转化:BouncyCastle 在官网上下载和你的jdk版本对应的jar包,比如我的jdk版本是1.7,就下载了bcprov-jdk15on-152.jar。
下载完成以后,把jar包放到jdk和jre下的lib\ext下,并且在对应的lib\security\java.security中添加如下一段:security.provider.11=org.bouncycastle.jce.provider.BouncyCastleProvider

好的,准备工作完成,下面开始实际操作:

  1. 在D盘下创建文件夹ssl(PS:以此举例,不必非要如此),
  2. 将我们的 服务端证书 service.cer 和下载的bcprov-jdk15on-152.jar放进来
  3. 管理员运行CMD 输入一下代码: keytool -import -v -alias server -file D:\ssl\service.cer
    (-import 引入证书文件,-alias server 起别名, -file D:\ssl\service.cer 引入的文件)
    -keystore D:\ssl\client.truststore
    (-keystore 生成keystore, D:\ssl\client.truststore生成的文件的路径名)
    -provider org.bouncycastle.jce.provider.BouncyCastleProvider
    (使用此包)
    -providerpath “bcprov-jdk15on-152.jar”
    (使用包的路径名)
    -storetype BKS
    (storetype BKS 将由JKS转化为BKS模式)
    -storepass 123456
    (-storepass keystore的密码 此处密码长度应 <=7 )
    运行,就会得到client.truststore,即是我们需要的客户端验证服务器端的证书库。

那么,有了这两个东东,我们就可以继续在Android中进行https操作了。 这里我们使用HttpClient来进行示例:

1.首先我们做一个生成SSLSocketFactory的工具:

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateFactory;
import java.util.Enumeration;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.conn.ssl.SSLSocketFactory;
import android.content.Context;

public class HttpsUtils {
	private static final String KEY_STORE_TYPE_BKS = "BKS";// 证书类型 固定值
	private static final String KEY_STORE_TYPE_P12 = "PKCS12";// 证书类型 固定值
	private static final String KEY_STORE_CLIENT_PATH = "client.p12";// 客户端要给服务器端认证的证书
	private static final String KEY_STORE_TRUST_PATH = "client.truststore";// 客户端验证服务器端的证书库
	private static final String KEY_STORE_PASSWORD = "123456";// 客户端证书密码
	private static final String KEY_STORE_TRUST_PASSWORD = "123456";// 客户端证书库密码
	/**
	 * 获取SslSocketFactory
	 *
	 * @param context
	 *            上下文
	 * @return SSLSocketFactory
	 */
	public static SSLSocketFactory createSSLSocketFactory(Context context) {
		try {
			// 服务器端需要验证的客户端证书
			KeyStore keyStore = KeyStore.getInstance(KEY_STORE_TYPE_P12);
			// 客户端信任的服务器端证书
			KeyStore trustStore = KeyStore.getInstance(KEY_STORE_TYPE_BKS);
			InputStream ksIn = context.getResources().getAssets().open(KEY_STORE_CLIENT_PATH);
			InputStream tsIn = context.getResources().getAssets().open(KEY_STORE_TRUST_PATH);
			try {
				keyStore.load(ksIn, KEY_STORE_PASSWORD.toCharArray());
				trustStore.load(tsIn, KEY_STORE_TRUST_PASSWORD.toCharArray());
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				try {
					ksIn.close();
				} catch (Exception ignore) {
				}
				try {
					tsIn.close();
				} catch (Exception ignore) {
				}
			}
			return new SSLSocketFactory(keyStore, KEY_STORE_PASSWORD, trustStore);
		} catch (KeyManagementException | UnrecoverableKeyException | KeyStoreException | FileNotFoundException | NoSuchAlgorithmException | ClientProtocolException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}
}

2.在请求之前配置下Scheme:

Scheme scheme_forHttps = new Scheme("https", 	HttpsUtils.createSSLSocketFactory(context), 443);	 (PS:此处的端口号https默认是443,如果需要,请改成你所需的服务端的https接口)

SchemeRegistry registry = new SchemeRegistry();
registry.register(scheme_forHttps);
.....

好了,至此,Android上的双向认证就可以使用啦。 Happy Coding.