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

前言

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

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

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

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

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

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

基本的HTTP连接方式

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

HttpUriRequest request = new HttpGet(url);
HttpClient client = DemoHttp.getClient();
try {
    HttpResponse httpResponse = client.execute(request);
    int responseCode = httpResponse.getStatusLine().getStatusCode();
    String message = httpResponse.getStatusLine().getReasonPhrase();
    HttpEntity entity = httpResponse.getEntity();
    if (responseCode == 200 && entity != null)
    {
        return parseString(entity);
    }
}
finally {
    client.getConnectionManager().shutdown();
}
return "";

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

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

public static HttpClient getClient() {
    BasicHttpParams params = new BasicHttpParams();
    HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1);
    HttpProtocolParams.setContentCharset(params, HTTP.DEFAULT_CONTENT_CHARSET);
    HttpProtocolParams.setUseExpectContinue(params, true);

    SchemeRegistry schReg = new SchemeRegistry();
    schReg.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80));

    ClientConnectionManager connMgr = new ThreadSafeClientConnManager(params, schReg);
    return new DefaultHttpClient(connMgr, params);
}

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

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

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

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

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

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的实现:

public class SSLTrustAllSocketFactory extends SSLSocketFactory {

    private static final String TAG = "SSLTrustAllSocketFactory";
    private SSLContext mCtx;

    public class SSLTrustAllManager implements X509TrustManager {

        @Override
        public void checkClientTrusted(X509Certificate[] arg0, String arg1)
                throws CertificateException {
        }

        @Override
        public void checkServerTrusted(X509Certificate[] arg0, String arg1)
                throws CertificateException {
        }

        @Override
        public X509Certificate[] getAcceptedIssuers() {
            return null;
        }

    }

    public SSLTrustAllSocketFactory(KeyStore truststore)
            throws Throwable {
        super(truststore);
        try {
            mCtx = SSLContext.getInstance("TLS");
            mCtx.init(null, new TrustManager[] { new SSLTrustAllManager() },
                    null);
            setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
        } catch (Exception ex) {
        }
    }

    @Override
    public Socket createSocket(Socket socket, String host,
                               int port, boolean autoClose)
            throws IOException, UnknownHostException {
        return mCtx.getSocketFactory().createSocket(socket, host, port, autoClose);
    }

    @Override
    public Socket createSocket() throws IOException {
        return mCtx.getSocketFactory().createSocket();
    }

    public static SSLSocketFactory getSocketFactory() {
        try {
            KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
            trustStore.load(null, null);
            SSLSocketFactory factory = new SSLTrustAllSocketFactory(trustStore);
            return factory;
        } catch (Throwable e) {
            Log.d(TAG, e.getMessage());
            e.printStackTrace();
        }
        return null;
    }

}

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

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

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

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

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

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

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

已标记关键词 清除标记
课程简介: 历经半个多月的时间,Debug亲自撸的 “企业员工角色权限管理平台” 终于完成了。正如字面意思,本课程讲解的是一个真正意义上的、企业级的项目实战,主要介绍了企业级应用系统后端应用权限的管理,其主要涵盖了六大核心业务模块、十几张数据库表。 其的核心业务模块主要包括用户模块、部门模块、岗位模块、角色模块、菜单模块和系统日志模块;与此同时,Debug还亲自撸了额外的附属模块,包括字典管理模块、商品分类模块以及考勤管理模块等等,主要是为了更好地巩固相应的技术栈以及企业应用系统业务模块的开发流程! 核心技术栈列表: 值得介绍的是,本课程在技术栈层面涵盖了前端和后端的大部分常用技术,包括Spring Boot、Spring MVC、Mybatis、Mybatis-Plus、Shiro(身份认证与资源授权跟会话等等)、Spring AOP、防止XSS攻击、防止SQL注入攻击、过滤器Filter、验证码Kaptcha、热部署插件Devtools、POI、Vue、LayUI、ElementUI、JQuery、HTML、Bootstrap、Freemarker、一键打包部署运行工具Wagon等等,如下图所示: 课程内容与收益: 总的来说,本课程是一门具有很强实践性质的“项目实战”课程,即“企业应用员工角色权限管理平台”,主要介绍了当前企业级应用系统员工、部门、岗位、角色、权限、菜单以及其他实体模块的管理;其,还重点讲解了如何基于Shiro的资源授权实现员工-角色-操作权限、员工-角色-数据权限的管理;在课程的最后,还介绍了如何实现一键打包上传部署运行项目等等。如下图所示为本权限管理平台的数据库设计图: 以下为项目整体的运行效果截图: 值得一提的是,在本课程,Debug也向各位小伙伴介绍了如何在企业级应用系统业务模块的开发,前端到后端再到数据库,最后再到服务器的上线部署运行等流程,如下图所示:
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页