Https实现机制详解

Https请求

一、访问HTTPS站点

两种方法来模拟发送HTTP请求,访问HTTP站点。一种方式是通过java.net自带的HttpURLConnection,另一种方式是通过Apache的HttpClient,这两种方式各有各的优势。这里也使用这两种方式来访问HTTPS站点,从下面的代码可以看到,和前面访问HTTP站点几乎完全一样。

1.1使用HttpURLConnection

@Test

public void basicHttpsGet() throws

Exception {

String url =  "https://www.baidu.com";

URL obj = new

URL(url);

HttpsURLConnection

con = (HttpsURLConnection) obj.openConnection();

con.setRequestProperty("User-Agent",

"Mozilla/5.0 (Windows NT 6.1; WOW64) ...");

con.setRequestProperty("Accept-Language",

"en-US,en;q=0.5");

con.setRequestMethod("GET");

String

responseBody = readResponseBody(con.getInputStream());

System.out.println(responseBody);

}

1.2使用HttpClient

@Test

public void basicHttpsGet() throws

Exception {

String url =  "https://www.baidu.com";

HttpGet request =

new HttpGet(url);

request.setHeader("User-Agent",

"Mozilla/5.0 (Windows NT 6.1; WOW64) ...");

CloseableHttpClient

httpclient = HttpClients.createDefault();

CloseableHttpResponse

response = httpclient.execute(request);

String

responseBody = readResponseBody(response);

System.out.println(responseBody);

}

具体的代码解释参见第一篇博客,这里不再赘述。一般情况下,访问HTTPS站点就和访问HTTP站点一样简单,无论是HttpURLConnection还是HttpClient,都将底层的实现细节封装了起来,给我们提供了一致的对外接口,所以我们不用关心HTTPS的实现原理。

二、Java里的证书

上面所介绍的是浏览器对证书进行验证的过程,浏览器保存了一个常用的CA证书列表,在验证证书链的有效性时,直接使用保存的证书里的公钥进行校验,如果在证书列表中没有找到或者找到了但是校验不通过,那么浏览器会警告用户,由用户决定是否继续。与此类似的,操作系统也一样保存有一份可信的证书列表,譬如在Windows系统下,你可以运行certmgr.msc打开证书管理器查看,这些证书实际上是存储在Windows的注册表中,一般情况下位于:\SOFTWARE\Microsoft\SystemCertificates\路径下。那么在Java程序中是如何验证证书的呢?

和浏览器操作系统类似,Java在JRE的安装目录下也保存了一份默认可信的证书列表,这个列表一般是保存在$JRE/lib/security/cacerts文件中。要查看这个文件,可以使用类似KeyStore Explorer这样的软件,当然也可以使用JRE自带的keytool工具(后面再介绍),cacerts文件的默认密码为changeit(但是我保证,大多数人都不会change it)。

我们知道,证书有很多种不同的存储格式,譬如CA在发布证书时,常常使用PEM格式,这种格式的好处是纯文本,内容是BASE64编码的,证书中使用"-----BEGIN CERTIFICATE-----"和"-----END CERTIFICATE-----"来标识。另外还有比较常用的二进制DER格式,在Windows平台上较常使用的PKCS#12格式等等。当然,不同格式的证书之间是可以相互转换的,我们可以使用openssl这个命令行工具来转换,参考SSL

Converter,另外,想了解更多证书格式的,可以参考这里:Various

SSL/TLS Certificate File Types/Extensions。

在Java平台下,证书常常被存储在KeyStore文件中,上面说的cacerts文件就是一个KeyStore文件,KeyStore不仅可以存储数字证书,还可以存储密钥,存储在KeyStore文件中的对象有三种类型:Certificate、PrivateKey和SecretKey。Certificate就是证书,PrivateKey是非对称加密中的私钥,SecretKey用于对称加密,是对称加密中的密钥。KeyStore文件根据用途,也有很多种不同的格式:JKS、JCEKS、PKCS12、DKS等等,PixelsTech上有一系列文章对KeyStore有深入的介绍,可以学习下:Different

types of keystore in Java。

到目前为止,我们所说的KeyStore其实只是一种文件格式而已,实际上在Java的世界里KeyStore文件分成两种:KeyStore和TrustStore,这是两个比较容易混淆的概念,不过这两个东西从文件格式来看其实是一样的。KeyStore保存私钥,用来加解密或者为别人做签名;TrustStore保存一些可信任的证书,访问HTTPS时对被访问者进行认证,以确保它是可信任的。所以准确来说,上面的cacerts文件应该叫做TrustStore而不是KeyStore,只是它的文件格式是KeyStore文件格式罢了。

除了KeyStore和TrustStore,Java里还有两个类KeyManager和TrustManager与此息息相关。JSSE的参考手册中有一张示意图,说明了各个类之间的关系:


可以看出如果要进行SSL会话,必须得新建一个SSLSocket对象,而SSLSocket对象是通过SSLSocketFactory来管理的,SSLSocketFactory对象则依赖于SSLContext,SSLContext对象又依赖于keyManager、TrustManager和SecureRandom。我们这里最关心的是TrustManager对象,另外两个暂且忽略,因为正是TrustManager负责证书的校验,对网站进行认证,要想在访问HTTPS时通过认证,不报sun.security.validator.ValidatorException异常,必须从这里开刀。

三、Java客户端访问https时证书验证处理规则

客户端的TrustStore文件中保存着被客户端所信任的服务器的证书信息。客户端在进行SSL连接时,JSSE将根据这个文件中的证书决定是否信任服务器端的证书。在SunJSSE中,有一个信任管理器类负责决定是否信任远端的证书,这个类有如下的处理规则:

1)

若系统属性javax.net.sll.trustStore指定了TrustStore文件,那么信任管理器就去jre安装路径下的lib/security/目录中寻找并使用这个文件来检查证书。

2)

若该系统属性没有指定TrustStore文件,它就会去jre安装路径下寻找默认的TrustStore文件,这个文件的相对路径为:lib/security/jssecacerts

3)

若jssecacerts不存在,但是cacerts存在(它随J2SDK一起发行,含有数量有限的可信任的基本证书),那么这个默认的TrustStore文件就是lib/security/cacerts

四、自定义TrustManager绕过证书检查进行https访问

我们知道了TrustManager是专门负责校验证书的,那么最容易想到的方法应该就是改写TrustManager类,让它不要对证书做校验,这种方法虽然粗暴,但是却相当有效,而且Java中的TrustManager也确实可以被重写,下面是示例代码:

@Test

public void

basicHttpsGetIgnoreCertificateValidation() throws Exception {

String url =  "https://kyfw.12306.cn/otn/";

// Create a trust

manager that does not validate certificate chains

TrustManager[]

trustAllCerts = new TrustManager[] {

new

X509TrustManager() {

public

X509Certificate[] getAcceptedIssuers() {

return

null;

}

public

void checkClientTrusted(X509Certificate[] certs, String authType) {

//

don't check

}

public

void checkServerTrusted(X509Certificate[] certs, String authType) {

//

don't check

}

}

};

SSLContext ctx =

SSLContext.getInstance("TLS");

ctx.init(null,

trustAllCerts, null);

LayeredConnectionSocketFactory

sslSocketFactory = new SSLConnectionSocketFactory(ctx);

CloseableHttpClient

httpclient = HttpClients.custom()

.setSSLSocketFactory(sslSocketFactory)

.build();

HttpGet request =

new HttpGet(url);

request.setHeader("User-Agent",

"Mozilla/5.0 (Windows NT 6.1; WOW64) ...");

CloseableHttpResponse

response = httpclient.execute(request);

String responseBody

= readResponseBody(response);

System.out.println(responseBody);

}

我们新建了一个匿名类,继承自X509TrustManager接口,这个接口提供了三个方法用于验证证书的有效性:getAcceptedIssuers、checkClientTrusted、checkServerTrusted,我们在验证的函数中直接返回,不做任何校验,这样在访问HTTPS站点时,就算是证书不可信,也不会抛出异常,可以继续执行下去。

这种方法虽然简单,但是却有一个最严重的问题,就是不安全。因为不对证书做任何合法性校验,而且这种处理是全局性的,不管青红皂白,所有的证书都不会做验证,所以就算遇到不信任的证书,代码依然会继续与之通信,至于通信的数据安全不安全就不能保证了。所以如果你只是想在测试环境做个实验,那没问题,但是如果你要将代码发布到生产环境,请慎重。

五、使用证书进行https访问

对于有些证书,我们基本上确定是可以信任的,但是这些证书又不在Java的cacerts文件中,譬如12306网站,或者使用了Let's Encrypt证书的一些网站,对于这些网站,我们可以将其添加到信任列表中,而不是使用上面的方法统统都相信,这样程序的安全性仍然可以得到保障。

5.1使用keytool导入证书

简单的做法是将这些网站的证书导入到cacerts文件中,这样Java程序在校验证书的时候就可以从cacerts文件中找到并成功校验这个证书了。上面我们介绍过JRE自带的keytool这个工具,这个工具小巧而强悍,拥有很多功能。首先我们可以使用它查看KeyStore文件,使用下面的命令可以列出KeyStore文件中的所有内容(包括证书、私钥等):

$ keytool -list -keystore cacerts

然后通过下面的命令,将证书导入到cacerts文件中:

$ keytool -import -alias 12306 -keystore cacerts

-file 12306.cer

要想将网站的证书导入cacerts文件中,首先要获取网站的证书,譬如上面命令中的12306.cer文件,它是使用浏览器的证书导出向导保存的。如下图所示:


关于keytool的更多用法,可以参考keytool的官网手册,SSLShopper上也有一篇文章列出了常用的keytool命令

5.2使用KeyStore动态加载证书

使用keytool导入证书,这种方法不仅简单,而且保证了代码的安全性,最关键的是代码不用做任何修改。所以我比较推荐这种方法。但是这种方法有一个致命的缺陷,那就是你需要修改JRE目录下的文件,如果你的程序只是在自己的电脑上运行,那倒没什么,可如果你的程序要部署在其他人的电脑上或者公司的服务器上,而你没有权限修改JRE目录下的文件,这该怎么办?如果你的程序是一个分布式的程序要部署在成百上千台机器上,难道还得修改每台机器的JRE文件吗?好在我们还有另一种通过编程的手段来实现的思路,在代码中动态的加载KeyStore文件来完成证书的校验,抱着知其然知其所以然的态度,我们在最后也实践下这种方法。通过编写代码可以更深刻的了解KeyStore、TrustManagerFactory、SSLContext以及SSLSocketFactory这几个类之间的关系。

@Test

public void

basicHttpsGetUsingSslSocketFactory() throws Exception {

String

keyStoreFile = "D:\\code\\ttt.ks";

String password =

"poiuyt";

KeyStore ks =

KeyStore.getInstance(KeyStore.getDefaultType());

FileInputStream

in = new FileInputStream(keyStoreFile);

ks.load(in,

password.toCharArray());

System.out.println(KeyStore.getDefaultType().toString());

System.out.println(TrustManagerFactory.getDefaultAlgorithm().toString());

TrustManagerFactory

tmf =

TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());

tmf.init(ks);

SSLContext ctx =

SSLContext.getInstance("TLS");

ctx.init(null,

tmf.getTrustManagers(), null);

LayeredConnectionSocketFactory

sslSocketFactory = new SSLConnectionSocketFactory(ctx);

String url =  "https://ttt.aneasystone.com";

/**

* Return

the page with content:

*  401

Authorization Required

*/

CloseableHttpClient

httpclient = HttpClients.custom()

.setSSLSocketFactory(sslSocketFactory)

.build();

HttpGet request =

new HttpGet(url);

request.setHeader("User-Agent",

"Mozilla/5.0 (Windows NT 6.1; WOW64) ...");

CloseableHttpResponse

response = httpclient.execute(request);

String

responseBody = readResponseBody(response);

System.out.println(responseBody);

}

上面的代码使用了HttpClient,如果是使用HttpsURLConnection只需要改动下面两行即可:

HttpsURLConnection con =

(HttpsURLConnection) obj.openConnection();

con.setSSLSocketFactory(ctx.getSocketFactory());

最后的最后,我们还可以通过下面的属性来指定trustStore,这样也不需要编写像上面那样大量繁琐的代码,另外,参考我前面的博客,这些属性还可以通过JVM的参数来设置。

System.setProperty("javax.net.ssl.trustStore",

"D:\\code\\ttt.ks");

System.setProperty("javax.net.ssl.trustStorePassword",

"poiuyt");

若没有通过设置JVM参数来指定要加载的证书库文件,则使用jdk默认的jre\\lib\\cacerts证书库文件来验证请求站点的证书是否合法。

5.3 Java请求https服务(真伪查询实例)

publicstaticJSONObject SetSystsHttpsSSLPost(Stringurl, Stringargs, Stringappkey, Stringiv)throwsException {

JSONObjectresult=newJSONObject();

Stringargs= SSLPostTest.encryption(querydata);

Stringiv=newBase64().encodeToString(IV.getBytes());

StringkeyStoreFile="C:\\Users\\Gufung\\nubiacsm.keystore";

/***方式一:使用keystore加载证书库文件****/

/*String keyStoreFile = System.getProperty("java.home")+ "\\lib\\security\\cacerts";

Stringpassword = "changeit";

KeyStoreks= KeyStore.getInstance(KeyStore.getDefaultType());

FileInputStreamin = new FileInputStream(keyStoreFile);

ks.load(in,password.toCharArray());

TrustManagerFactorytmf= TrustManagerFactory

.getInstance(TrustManagerFactory.getDefaultAlgorithm());

tmf.init(ks);

SSLContextctx= SSLContext.getInstance("TLS");

ctx.init(null,tmf.getTrustManagers(), null);

URL myURL = new URL(url);

HttpsURLConnectioncon= (HttpsURLConnection) myURL.openConnection();

con.setSSLSocketFactory(ctx.getSocketFactory());*/

/***方式一:使用keystore加载证书库文件****/

/***方式二:通过设置JVM参数来指定要加载的证书库文件****/

System.setProperty("javax.net.ssl.trustStore",keyStoreFile);

System.setProperty("javax.net.ssl.trustStorePassword","changeit");

URLmyURL=newURL(url);

HttpsURLConnectioncon= (HttpsURLConnection)myURL.openConnection();

/***方式二:通过设置JVM参数来指定要加载的证书库文件****/

con.setDoOutput(true);

con.setDoInput(true);

con.setRequestMethod("POST");

con.setUseCaches(false);

con.connect();

DataOutputStreamout=newDataOutputStream(con.getOutputStream());

Stringcontent="args="+ URLEncoder.encode(args,"UTF-8");

content+="&appkey="+ URLEncoder.encode(appkey,"UTF-8");

content+="&iv="+ URLEncoder.encode(iv,"UTF-8");

out.writeBytes(content);

out.flush();

out.close();

intresultCode=con.getResponseCode();

System.out.println(resultCode);

if(HttpURLConnection.HTTP_OK==resultCode) {

StringreadLine=newString();

BufferedReaderresponseReader=newBufferedReader(

newInputStreamReader(con.getInputStream(),"UTF-8"));

while((readLine=responseReader.readLine()) !=null) {

result=newJSONObject(readLine);

}

responseReader.close();

}

con.disconnect();

System.out.println(result.toString());

returnresult;

}

参考

SSL如何工作

SSL/TLS协议简介与实例分析

SSL/TLS原理详解

TLS握手优化详解

三种解密HTTPS流量的方法介绍

图解SSL/TLS协议

SSL/TLS协议运行机制的概述

HTTPS从原理到实战

HTTPS工作原理和TCP握手机制

扫盲HTTPS和SSL/TLS协议

HTTPS那些事(一)HTTPS原理

理解HTTPS协议

SSL/TLS协议安全系列:SSL/TLS概述

Different types of keystore in Java -- Overview

Different types of keystore in Java -- JKS

Java中用HttpsURLConnection访问Https链接的问题

Where is the certificate folder in Windows 7?

数字证书及CA的扫盲介绍

数字证书原理

数字证书

Java使用自签证书访问https站点

12306的证书问题

数字签名是什么?

在线买火车票为什么要安装根证书?

Java加密技术(八)——数字证书

Java加密技术(九)——初探SSL

常见的数字证书格式

keyStore vs trustStore

Difference between trustStore and keyStore in Java - SSL

Java Secure Socket Extension (JSSE) Reference Guide

Disable Certificate Validation in Java SSL Connections

javax.net.ssl.SSLHandshakeException:

sun.security.validator.ValidatorException: PKIX path building failed

How to solve javax.net.ssl.SSLHandshakeException?

SSL Converter

The Most Common Java Keytool Keystore Commands

keytool - Key and Certificate Management Tool

原文链接:http://www.aneasystone.com/archives/2016/04/java-and-https.html

参考资料:Java安全通信:HTTPS与SSL

http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html

http://www.aneasystone.com/archives/2016/04/java-and-https.html

http://www.ruanyifeng.com/blog/2014/09/illustration-ssl.html

https://my.oschina.net/zhlmmc/blog/42111

https://www.sslshopper.com/article-most-common-java-keytool-keystore-commands.html

http://blog.csdn.net/csdnbenbenchong/article/details/7388260

http://blog.csdn.net/u011042133/article/details/51671801

http://snowolf.iteye.com/blog/397693

http://www.iamlbk.com/blog/20160731/tomcat-https/?utm_source=tuicool&utm_medium=referral

http://blog.chenxiaosheng.com/posts/2013-12-26/java-use-self_signed_certificate.html

http://www.zhixing123.cn/jsp/49937.html

http://www.cnblogs.com/JeffreySun/archive/2010/06/24/1627247.html

https://publib.boulder.ibm.com/tividd/td/TRM/SC23-4822-00/zh_CN/HTML/user276.htm

http://op.baidu.com/2015/04/https-s01a01/

http://lukejin.iteye.com/blog/605634

http://ln-ydc.iteye.com/blog/1335213

https://blog.cnbluebox.com/blog/2014/03/24/shu-zi-zheng-shu/

http://blog.csdn.net/sfdev/article/details/2957240

https://segmentfault.com/a/1190000002554673#articleHeader0

http://www.cnblogs.com/devinzhang/archive/2012/02/28/2371631.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容