在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连接实现都是用这种忽略验证的方式实现的。

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

客户端访问https站点(自定义证书)

客户端访问服务器端的几种方式(Https)按照 Glassfish4.1和Tomcat配置Https访问 设置好服务器端的https访问后,客户端可以通过三种方式访问服务器端。 浏览器方式。用户在浏览...
  • u012948976
  • u012948976
  • 2015年12月31日 21:41
  • 2373

轻松把玩HttpClient之配置ssl,采用设置信任自签名证书实现https

转载地址:http://blog.csdn.net/xiaoxian8023/article/details/49866397 在上篇文章《HttpClient配置ssl实现https简单示例—...
  • wanglha
  • wanglha
  • 2016年04月13日 10:52
  • 1035

使用CA自签名证书搭建HTTPS网站

在自己倒腾https网站的时候用自定义的CA给自己的网站做自签名的问题一直困扰了我好久,下面是我自己测试成功的案例,网上有很多类似的问题,在这里储备一份供自己和他人参考使用。 安装linux,apac...
  • shion0305
  • shion0305
  • 2017年06月27日 11:42
  • 607

java ssl https 连接详解 生成证书

我们先来了解一下什么理HTTPS 1. HTTPS概念         1)简介           HTTPS(全称:Hypertext Transfer Protocol ove...
  • a351945755
  • a351945755
  • 2014年04月02日 10:05
  • 29903

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

转载自http://blog.csdn.net/raptor/article/details/18898937 因为这部分才是本文的重点,要说得详细一点,所以单独做成一篇来说。 安全地使用自定义证...
  • ElonLink
  • ElonLink
  • 2016年07月28日 21:06
  • 444

Java HTTPS客户端如何处理证书

在SunJSSE中,有一个信任管理器类负责决定是否信任远端的证书,这个类有如下的处理规则: 1)若系统属性javax.net.ssl.trustStore指定了TrustStore文件,那么信任管理器...
  • fw0124
  • fw0124
  • 2015年09月10日 12:45
  • 9821

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

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

JAVA SSL HTTPS 连接详解 生成证书

一、 HTTPS概念1. 简介HTTPS(全称:Hypertext Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTT...
  • liuquan0071
  • liuquan0071
  • 2015年12月15日 17:35
  • 7244

为nginx配置https并自签名证书

为nginx配置https并自签名证书一、把证书准备好。 步骤与使用OpenSSL自签发服务器https证书所述大同小异。在这里再重复一次。 1、制作CA证书: ca.key CA私钥:open...
  • xizaihui
  • xizaihui
  • 2016年11月15日 22:30
  • 3934

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

在请求安全性高的接口时,我们可能会使用到HTTPS接口,HTTPS可以理解为HTTP+TLS,关于HTTPS具体是怎么工作的,可以看这篇文章:http://gold.xitu.io/entry/56e...
  • laizuling
  • laizuling
  • 2016年04月15日 16:34
  • 2604
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:在Android应用中使用自定义证书的HTTPS连接(上)
举报原因:
原因补充:

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