因为最近公司的项目需求涉及到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的作用
它的主要作用可以分为两种:一种是建立一个信息安全通道,来保证数据传输的安全;另一种就是确认网站的真实性。
具体讲,是客户端产生一个对称的密钥,通过服务器的证书来交换密钥,即一般意义上的握手过程。
接下来所有的信息往来就都是加密的。第三方即使截获,也没有任何意义,因为他没有密钥,当然篡改也就没有什么意义了。
少许对客户端有要求的情况下,会要求客户端也必须有一个证书。
这里客户端证书,其实就类似表示个人信息的时候,除了用户名/密码,还有一个CA 认证过的身份。因为个人证书一般来说是别人无法模拟的,所有这样能够更深的确认自己的身份。目前少数个人银行的专业版是这种做法,具体证书可能是拿U盘(即U盾)作为一个备份的载体。
在公司的项目中证书是自己生成的,这里我们使用的是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
注意-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服务器了。
找到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/ 就会出现 基于证书的验证失败 提示,这就说明证书生成添加没有错误,接下来我们需要在客户端进行访问了。
关键代码如下
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
本人也是因为公司项目需要才研究这个相关内容的,这篇博客相当于这段时间的学习总结吧。希望能够帮到有需要的朋友。