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閱讀 227,797評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,179評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,628評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,642評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,444評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,948評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,185評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,717評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,602評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,794評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,045評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,418評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,671評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,414評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,750評論 2 370

推薦閱讀更多精彩內容