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

转载 2016年05月31日 20:09:36

原文:http://blog.csdn.net/raptor/article/details/18896375

前言

由于移动设备使用的网络环境各种各样,而且常常接入不安全的公共WIFI——如果你对公共WIFI环境的安全性没有警惕性的话,就难怪你开发出不安全的程序,把你的用户置于危险境地——这话一点都不夸张。

而要想在不安全的网络环境下安全地使用网络,最好的办法就是通过VPN连接到安全网络环境中去。但这并不总是能够保证的。所以需要应用开发者在开发的时候尽量减少用户的安全风险。

通过HTTPS连接网络是一种常用的方法。但是在实际使用中存在几个困难:

* 使用商业证书的成本
* 使用自定义证书不被系统承认
* 忽略证书验证则可能被“中间人攻击”

本文将针对这些问题讨论技术解决方案。

因为最近又开始试用Android Studio,所以这里的Demo是用Android Studio 0.4.2写的。完整的Demo代码可以在bitbucket获得:https://bitbucket.org/raptorz/democert

基本的HTTP连接方式

首先来看基本的HTTP连接方式实现,程序的项目框架是直接用向导生成后略作修改。主要就是增加一个异步网络调用的任务,任务内容大致为:

  1. HttpUriRequest request = new HttpGet(url);  
  2. HttpClient client = DemoHttp.getClient();  
  3. try {  
  4.     HttpResponse httpResponse = client.execute(request);  
  5.     int responseCode = httpResponse.getStatusLine().getStatusCode();  
  6.     String message = httpResponse.getStatusLine().getReasonPhrase();  
  7.     HttpEntity entity = httpResponse.getEntity();  
  8.     if (responseCode == 200 && entity != null)  
  9.     {  
  10.         return parseString(entity);  
  11.     }  
  12. }  
  13. finally {  
  14.     client.getConnectionManager().shutdown();  
  15. }  
  16. return "";  

上面这个函数功能就是创建一个HttpClient去GET url的内容,如果HTTP返回值为200(即无错误),则返回响应内容。

重点就在DemoHttp.getClient()里,对于基本的HTTP连接,以下是实现部分代码:

  1. public static HttpClient getClient() {  
  2.     BasicHttpParams params = new BasicHttpParams();  
  3.     HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);  
  4.     HttpProtocolParams.setContentCharset(params, HTTP.DEFAULT_CONTENT_CHARSET);  
  5.     HttpProtocolParams.setUseExpectContinue(params, true);  
  6.   
  7.     SchemeRegistry schReg = new SchemeRegistry();  
  8.     schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));  
  9.   
  10.     ClientConnectionManager connMgr = new ThreadSafeClientConnManager(params, schReg);  
  11.     return new DefaultHttpClient(connMgr, params);  
  12. }  

实际的实现代码当然比上面这两段多得多了,Java就是这么麻烦,一点小事都要写一大堆代码,为节约篇幅就不全部列出了,参见bitbucket上的完整代码吧。

顺便说一句,写网络通讯应用别忘记在Manifest.xml里加上相应的权限,否则会出一些很奇怪的错误。

HTTP连接的主要问题在于在传输过程中的内容都是明文,只要在同一网段内使用嗅探程序即可获得网内其它设备与服务器之间的通讯内容,完全没有安全性。

使用系统承认的商业证书的HTTPS连接方式

在上面的例子中,如果尝试用https连接的话,会报错称不支持https: Scheme 'https' not registered。最简单的解决办法就是参照HTTP的方式,加入对HTTPS的支持:

  1. schReg.register(new Scheme("https", SSLSocketFactory.getSocketFactory(), 443));  

关键代码就这么一句。

现在就可以像打开HTTP链接一样打开有效的HTTPS连接了,比如: https://www.google.com.hk 。但可耻的 12306 的HTTPS却不行,因为它使用了不被系统承认的自定义证书:No peer certificate 。

这个方案使用了HTTPS连接,传输内容经过加密,嗅探程序已经无法获得通讯内容。而服务器的证书经过合法签名,被系统所承认,正常通讯也没有问题。

但是需要花钱买证书。

使用自定义证书并忽略验证的HTTPS连接方式

如果不想花钱,那么就只能用OPENSSL自己做一个证书,但问题在于,这个证书得不到系统的承认,后果同 12306 。为了解决这个问题,一个办法是跳过系统校验。

要跳过系统校验,就不能再使用系统标准的SSLSocketFactory了,需要自定义一个。然后为了在这个自定义SSLSocketFactory里跳过校验,还需要自定义一个TrustManager,在其中忽略所有校验,即TrustAll。

以下就是SSLTrustAllSocketFactory和SSLTrustAllManager的实现:

  1. public class SSLTrustAllSocketFactory extends SSLSocketFactory {  
  2.   
  3.     private static final String TAG = "SSLTrustAllSocketFactory";  
  4.     private SSLContext mCtx;  
  5.   
  6.     public class SSLTrustAllManager implements X509TrustManager {  
  7.   
  8.         @Override  
  9.         public void checkClientTrusted(X509Certificate[] arg0, String arg1)  
  10.                 throws CertificateException {  
  11.         }  
  12.   
  13.         @Override  
  14.         public void checkServerTrusted(X509Certificate[] arg0, String arg1)  
  15.                 throws CertificateException {  
  16.         }  
  17.   
  18.         @Override  
  19.         public X509Certificate[] getAcceptedIssuers() {  
  20.             return null;  
  21.         }  
  22.   
  23.     }  
  24.   
  25.     public SSLTrustAllSocketFactory(KeyStore truststore)  
  26.             throws Throwable {  
  27.         super(truststore);  
  28.         try {  
  29.             mCtx = SSLContext.getInstance("TLS");  
  30.             mCtx.init(nullnew TrustManager[] { new SSLTrustAllManager() },  
  31.                     null);  
  32.             setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);  
  33.         } catch (Exception ex) {  
  34.         }  
  35.     }  
  36.   
  37.     @Override  
  38.     public Socket createSocket(Socket socket, String host,  
  39.                                int port, boolean autoClose)  
  40.             throws IOException, UnknownHostException {  
  41.         return mCtx.getSocketFactory().createSocket(socket, host, port, autoClose);  
  42.     }  
  43.   
  44.     @Override  
  45.     public Socket createSocket() throws IOException {  
  46.         return mCtx.getSocketFactory().createSocket();  
  47.     }  
  48.   
  49.     public static SSLSocketFactory getSocketFactory() {  
  50.         try {  
  51.             KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());  
  52.             trustStore.load(nullnull);  
  53.             SSLSocketFactory factory = new SSLTrustAllSocketFactory(trustStore);  
  54.             return factory;  
  55.         } catch (Throwable e) {  
  56.             Log.d(TAG, e.getMessage());  
  57.             e.printStackTrace();  
  58.         }  
  59.         return null;  
  60.     }  
  61.   
  62. }  

最后在注册scheme时使用这个Factory:

  1. schReg.register(new Scheme("https", SSLTrustAllSocketFactory.getSocketFactory(), 443));  

这样就可以成功打开 12306 的内容了。

不过这个方案虽然用了HTTPS,通讯的内容也经过了加密,嗅探程序也无法知道内容。但是通过更麻烦一些的“中间人攻击”,还是可以窃取通讯内容的:

在网内配置一个DNS,把目标服务器域名解析到本地的一个地址,然后在这个地址上用一个中间服务器作代理,它使用一个假的证书与客户端通讯,然后再由这个代理作为客户端连到实际的服务器,用真的证书与服务器通讯。这样所有的通讯内容就会经过这个代理。而因为客户端不校验证书,所以它用来加密的证书实际上是代理提供的假证书,那么代理就可以完全知道通讯内容。这个代理就是所谓的“中间人”。

但是不幸的是,网上搜到的大部分关于自定义证书的HTTPS连接实现都是用这种忽略验证的方式实现的。

所以我们需要更安全的方式。详见下篇。

相关文章推荐

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

对于初次接触https有一定的帮助,本文属于转载篇。 原文地址:http://blog.csdn.net/raptor/article/details/18896375 前言 ...

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

因为这部分才是本文的重点,要说得详细一点,所以单独做成一篇来说。 安全地使用自定义证书的HTTPS连接方式 终极解决方案是:把证书编译到应用中去,由应用自己来验证证书。 生成KeyS...

在Android应用中使用自定义证书,CER转BKS

转换方法参考:http://blog.csdn.net/raptor/article/details/18898937首先要下载特定版本的JCE Provider包 http://www.bounc...

使用自定义证书并忽略验证的HTTPS连接Post请求方式的封装

使用自定义证书并忽略验证的HTTPS连接Post请求方式的封装 使用自定义证书并忽略验证的HTTPS连接方式 解决证书不被系统承认的方法,就是跳过系统校验。要跳过系统校验,就不能再使用系统标...

创建Material Design风格的Android应用--使用自定义动画

动画在Material Design设计中给用户反馈放用户点击时,并且在程序用户界面中提供连贯的视觉。Material主题为按钮(Button)和activity的转换提供了一些默认的动画,在andr...

android应用程序中使用自定义Toast控件

自定义Toast对于原生Toast的美中不足得到了美化。可以将自定义的ImageView和TextView控件添加到其中 先看看截图吧。 通过自定义实现之后,这是基本效果。 可以...

Android应用使用自定义字体

android 系统本身内置了一些字体,可以在程序中使用,并且支持在xml配置textView的时候进行修改字体的样式。支持字段为android:textStyle ,android:typeface...

日常开发——Android请求自定义证书的https

有时候为了app的数据安全,开发者会考虑使用https来进行数据传输。在安卓上,原生的HttpsURLConnection和WebView只支持那些得到安卓系统承认的证书的站点。如果请求那些使用未通过...

Android下OkHttp请求自定义HTTPS证书接口设置

在请求安全性高的接口时,我们可能会使用到HTTPS接口,HTTPS可以理解为HTTP+TLS,关于HTTPS具体是怎么工作的,可以看这篇文章:http://gold.xitu.io/entry/56e...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)