在Android应用中使用自定义证书的HTTPS连接(下)

转载自http://blog.csdn.net/raptor/article/details/18898937

因为这部分才是本文的重点,要说得详细一点,所以单独做成一篇来说。

安全地使用自定义证书的HTTPS连接方式

终极解决方案是:把证书编译到应用中去,由应用自己来验证证书。

生成KeyStore

要验证自定义证书,首先要把证书编译到应用中去,这需要JSSE提供的keytool工具来生成KeyStore文件。参考《Java 安全套接字编程以及 keytool 使用最佳实践》,我试过了用JKS格式,但是结果连接失败,报错:Wrong version of key store。后来看了SO的这个帖才知道必须使用BKS的1.46版。更详细的内容参考这篇《Using a Custom Certificate Trust Store on Android》。

这里所谓的证书,实际上就是公钥,你可以从web服务器配置的.crt文件或.pem文件里获得。比如12306就直接提供了公钥证书下载,真是“服务周到”啊。

还 有一个比较简单的办法就是直接从浏览器里获得。比如用 FireFox 打开 https 链接,在地址栏顶部的小锁上点一下,然后点“更多信息……”-“查看证书”-“详细内容”-“导出”,即可将网站的X.509证书导出为一个文本文件。不 过需要注意的是,这种方法只对某些HTTPS服务器有效——通常是使用自签名证书或是使用类似StarCom免费证书服务器,但是对 12306 或 google 这种的就无效了,具体原因不明。

另外,不论是浏览器导出,还是服务器端获得,都是公钥证书,有两种格式:纯文本的.crt格式或是二进制的.cer格式。两种都可以用。

然后,你需要一个特定版本的JCE Provider,就是上面说过的那个SO帖里给的:http://www.bouncycastle.org/download/bcprov-jdk15on-146.jar 。注意,bouncycastle官网上目前发布的1.50版我试了一下不可用,不知道是不是我打开的方式不对,总之用这个1.46版的是没错的。

把这两个文件放在一起,然后在这个目录下运行以下命令:

keytool -importcert -v -trustcacerts -alias cert12306 -file srca.cer \
-keystore cert12306.bks -storetype BKS \
-providerclass org.bouncycastle.jce.provider.BouncyCastleProvider \
-providerpath ./bcprov-jdk15on-146.jar -storepass pw12306

运行后将显示证书内容并提示你是否确认,按Y回车确认即可。

其中cert12306是个随便取的别名,供keytool管理时方便而已。srca.cer就是从12306网站下载的证书文件。cert12306.bks是生成的keyStore文件,注意,这个文件必须以JAVA变量名的方式命名,比如不能直接叫12306.bks,否则在加载资源时会因为名字不是合格的JAVA变量名而出错。 ./bcprov-jdk15on-146.jar 就是刚才下载的那个JCE Provider。最后pw12306是一个密码,用于确保KeyStore文件本身的安全。

使用自定义keyStore实现连接

以下就是这个方案的实现,基本上和TrustAll差不多,也是需要一个自定义的SSLSocketFactory,不过因为还是需要验证证书的,所以就不需要再定义TrustManager了,用系统内置的即可。不过为了读取KeyStore资源,需要增加一个Context参数。

另外,前面生成的那个cert12306.bks文件要放到 res/raw/ 目录下。

[java]  view plain  copy
  1. public class SSLCustomSocketFactory extends SSLSocketFactory {  
  2.     private static final String TAG = "SSLCustomSocketFactory";  
  3.   
  4.     private static final String KEY_PASS = "pw12306";  
  5.   
  6.     public SSLCustomSocketFactory(KeyStore trustStore) throws Throwable {  
  7.         super(trustStore);  
  8.     }  
  9.   
  10.     public static SSLSocketFactory getSocketFactory(Context context) {  
  11.         try {  
  12.             InputStream ins = context.getResources().openRawResource(R.raw.cert12306);  
  13.   
  14.             KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());  
  15.             try {  
  16.                 trustStore.load(ins, KEY_PASS.toCharArray());  
  17.             }  
  18.             finally {  
  19.                 ins.close();  
  20.             }  
  21.             SSLSocketFactory factory = new SSLCustomSocketFactory(trustStore);  
  22.             return factory;  
  23.         } catch (Throwable e) {  
  24.             Log.d(TAG, e.getMessage());  
  25.             e.printStackTrace();  
  26.         }  
  27.         return null;  
  28.     }  
  29. }  

其中cert12306就是bks资源文件名,pw12306就是前面设置的密码。

同样的,使用这个Factory注册到scheme:

[java]  view plain  copy
  1. schReg.register(new Scheme("https", SSLCustomSocketFactory.getSocketFactory(context), 443));  

现在,也可以成功地连接12306了,而且如此实现,基本上就能达到与用商业证书一样的安全性了。

不过因为现在只使用了12306的证书,没有使用系统证书,所以只能连接12306,连GOOGLE都连接不了了。但这个问题好解决,只需要在连接不同网站时使用不同的HttpClient即可。或者自己实现一个混合验证的SSLSocketFactory。

更加安全的双向认证的HTTPS连接方式

当然,还有一种更安全的HTTPS通讯方式叫做双向认证。相对的,上面所说的全都是指服务端的单向认证:即只有服务器配置了证书,客户端只是使用服务器证书的公钥。而双向认证则是客户端也有一个由服务器签发的证书,这样服务器可以确认连接过来的客户端的身份,网络银行就使用了这种方式。

双向认证的实现方式与本篇的实现方式差不多,只是在trustStore基础之上再增加一个keyStore,其内容为客户端证书,当然这个证书就是同时包含公钥和私钥的。因为搭建一个双向认证的服务器比较麻烦,一般应用也没必要,所以这里不再详细说明。

最后再次放出本文例子程序的完整代码(Android Studio 0.4.2项目):https://bitbucket.org/raptorz/democert

你可能感兴趣的:(在Android应用中使用自定义证书的HTTPS连接(下))