Android HTTPS认证


1、概述

因为最近公司的项目需求涉及到HTTPS双向认证和服务器进行交互,所以最近花了大量时间研究相关知识,发现网上并没有完善的相关文章,自己在这个过程中也走了好多弯路,所以决定写篇文章记录自己学到的东西,同时也希望能够帮到需要的人。

2、HTTPS相关介绍

HTTPS(全称:Hypertext Transfer Protocol over Secure Socket Layer),是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。这个系统的最初研发由网景公司进行,提供了身份验证与加密通讯方法,现在它被广泛用于万维网上安全敏感的通讯,例如交易支付方面。

HTTPS和HTTP的区别

  • https协议需要到ca申请证书,一般免费证书很少,需要交费。

  • http是超文本传输协议,信息是明文传输;https 则是具有安全性的ssl加密传输协议。

  • http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。

  • http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

HTTPS的作用

它的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。

  • 一般意义上的https,就是服务器有一个证书。主要目的是保证服务器就是他声称的服务器,这个跟第一点一样;服务端和客户端之间的所有通讯,都是加密的。
  • 具体讲,是客户端产生一个对称的密钥,通过服务器的证书来交换密钥,即一般意义上的握手过程。

  • 接下来所有的信息往来就都是加密的。第三方即使截获,也没有任何意义,因为他没有密钥,当然篡改也就没有什么意义了。

  • 少许对客户端有要求的情况下,会要求客户端也必须有一个证书。

这里客户端证书,其实就类似表示个人信息的时候,除了用户名/密码,还有一个CA 认证过的身份。因为个人证书一般来说是别人无法模拟的,所有这样能够更深的确认自己的身份。目前少数个人银行的专业版是这种做法,具体证书可能是拿U盘(即U盾)作为一个备份的载体。

3、keytool生成证书

在公司的项目中证书是自己生成的,这里我们使用的是JDK自带的工具keytool来生成的,它的位置为JAVA_HOME\bin\keytool.exe。 
在配置了JDK环境的电脑上都可以直接使用。

keytool命令介绍

keytool中有许多命令,在生成自己证书的时候会用到相关命令,所以需要对命令进行简单介绍

  • -alias 产生别名

  • -keystore 指定密钥库的名称(就像数据库一样的证书库,可以有很多个证书,cacerts这个文件是jre自带的,你也可以使用其它文件名字,如果没有这个文件名字,它会创建这样一个)

  • -storepass 指定密钥库的密码

  • -keypass 指定别名条目的密码

  • -list 显示密钥库中的证书信息

  • -v 显示密钥库中的证书详细信息

  • -export 将别名指定的证书导出到文件

  • -file 参数指定导出到文件的文件名

  • -delete 删除密钥库中某条目

  • -import 将已签名数字证书导入密钥库

  • -keypasswd 修改密钥库中指定条目口令

  • -dname 指定证书拥有者信息

  • -keyalg 指定密钥的算法

  • -validity 指定创建的证书有效期多少天

  • -keysize 指定密钥长度

下面通过创建证书来熟悉keytool命令

  • 生成服务端证书库 
    keytool -genkey -alias zhxu_server -keyalg RSA -keystore zhxu_server.jks -validity 3600 -storepass 123456 -ext san=ip:192.168.56.1

Android HTTPS认证_第1张图片

注意-ext san=ip:**此处内容为要访问地址的IP,必须填写,名字与姓氏也要填写IP,否则访问时报错,确定完成后,生成的证书默认在C:\Users\用户名 路径下。

  • 生成客户端证书库

    keytool -genkey -alias zhxu_client -keyalg RSA -keystore zhxu_client.jks -validity 3600 -storepass 123456

配置和上述配置一样

完成后上述步骤后就可以看到对应路径下的.jks文件了,下面就是签名证书然后让证书库信任证书。

  • 导出客户端证书

    keytool -exportcert -alias zhxu_client -keystore zhxu_client.jks -file zhxu_client.cer -storepass 123456

  • 让服务器证书库信任客户端证书

    keytool -import -alias zhxu_client -file zhxu_client.cer -keystore zhxu_server.jks -storepass 123456

  • 导出服务端证书

    keytool -exportcert -alias zhxu_server -keystore zhxu_server.jks -file server.cer -storepass 123456

  • 让服务器证书库信任客户端证书

    keytool -import -alias zhxu_server -file server.cer -storepass 123456 -keystore zhxu_client.jks

完成后上述6个步骤后就可以在对应路径下生成我们需要的四个文件了。

我们需要访问的是服务器,所以接下来我们需要配置我们的tomcat服务器了。

4、配置tomcat服务器

找到conf目录下的server.xml服务器的配置文件,打开添加如下标签

<Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol"
           maxThreads="150" SSLEnabled="true" scheme="https" secure="true" clientAuth="true" sslProtocol="TLS"
           keystoreFile="D:/zhxu_server.jks" keystorePass="123456"
           truststoreFile="D:/zhxu_client.jks" truststorePass="123456"/>


打开浏览器输入https://本机IP:8443/ 就会出现 基于证书的验证失败 提示,这就说明证书生成添加没有错误,接下来我们需要在客户端进行访问了。

5、客户端访问

关键代码如下

public SSLSocketFactory setCertificates(InputStream... certificates){
    try{
        CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        keyStore.load(null);
        int index = 0;
        for (InputStream certificate : certificates){
            String certificateAlias = Integer.toString(index++);
            keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));

            try{
                if (certificate != null)
                    certificate.close();
            } catch (IOException e){
                e.printStackTrace() ;
            }
        }

        //取得SSL的SSLContext实例
        SSLContext sslContext = SSLContext.getInstance("TLS");
        TrustManagerFactory trustManagerFactory = TrustManagerFactory.
                getInstance(TrustManagerFactory.getDefaultAlgorithm());
        trustManagerFactory.init(keyStore);

        //初始化keystore
        KeyStore clientKeyStore = KeyStore.getInstance(KeyStore.getDefaultType());
        clientKeyStore.load(getAssets().open("zhxu_client.jks"), "123456".toCharArray());

        KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
        keyManagerFactory.init(clientKeyStore, "123456".toCharArray());

        sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
        return sslContext.getSocketFactory() ;

    } catch (Exception e){
        e.printStackTrace();
    }
    return null ;
}


但是此时运行会报java.io.IOException: Wrong version of key store错误。

为什么呢?

因为jks证书是Java平台的,Android平台识别不了,Android平台识别的是bks格式的证书。

所以接下来就需要将jks的证书转换成bks的证书了。

通过protecle将jks证书转化成bks证书,具体使用方法比较简单,自行百度即可。

转换完成后将bks证书放到assets文件中

/初始化keystore
KeyStore clientKeyStore = KeyStore.getInstance("BKS");
clientKeyStore.load(getAssets().open("zhxu_client.bks"), "123456".toCharArray());

KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(clientKeyStore, "123456".toCharArray());

sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
return sslContext.getSocketFactory() ;


对应代码部分修改成上述代码即可。

连接服务器部分代码如下

/**
 * 通过HTTPS访问
 */
private void runHttpsRequestWithHttpsURLConnection(){
    AsyncTask<String, Void, String> testTask = new AsyncTask<String, Void, String>() {
        @Override
        protected String doInBackground(String... params) {
            String result = "";
            HttpsURLConnection conn = null;
            try {
                URL url = new URL(HTTPS_URL);
                conn = (HttpsURLConnection) url.openConnection();
                conn.setSSLSocketFactory(setCertificates(MainActivity.this.getAssets().open("server.cer")));
                conn.connect();
                result = getStreamString(conn.getInputStream());
                return result;
            } catch (Exception e){
                e.printStackTrace();
            }
            return null;
        }

        @Override
        protected void onPostExecute(String result) {
            super.onPostExecute(result);
            System.out.println("result:"+result);
        }
    };

    testTask.execute();
}


如果一切配置完成就可以正确的连接服务器获取数据了。

项目地址 https://github.com/studyzhxu/HttpsDemo

6、总结

本人也是因为公司项目需要才研究这个相关内容的,这篇博客相当于这段时间的学习总结吧。希望能够帮到有需要的朋友。

你可能感兴趣的:(android,socket,服务器)