先來看一下HTTPS的定義:
HTTPS(Hyper Text Transfer Protocol Secure)是一種通過計算機網絡進行安全通訊的傳輸協議。HTTPS經由HTTP進行通訊,但利用TLS來加密數據包。HTTPS開發的主要目的,是提供對網站服務器的身份驗證,保護交換數據的隱私與完整性。
也就是說,HTTPS就是在HTTP協議的基礎上加入了TLS協議。爲何用TLS協議而不是SSL協議呢?
TLS是傳輸層加密協議,前身是SSL協議。經常使用的TLS協議版本有: TLS1.2, TLS1.1, TLS1.0和SSL3.0。其中,SSL3.0已經被證明不安全,TLS1.0也存在部分安全漏洞。
HTTPS相對HTTP提供了更安全的數據傳輸保障,主要體如今三個方面:
(1)內容加密
客戶端到服務器的內容都是以加密形式傳輸,中間者沒法直接查看明文內容
(2)身份認證
經過校驗保證客戶端訪問的是本身的服務器
(3)數據完整性
防止內容被第三方冒充或者篡改html
和TCP在創建鏈接時的三次握手同樣,SSL/TLS也須要客戶端和服務器之間進行握手,可是目的不同。在SSL/TLS握手的過程當中,客戶端和服務器彼此交換並驗證證書,並協商出一個"對話密鑰",後續的全部通訊都是用這個"對話密鑰"進行加密,保證通訊安全。
用一張圖來簡單的說明一下握手的過程: java
爲了提升安全性和效率,HTTPS結合了對稱和非對稱兩種加密方式。客戶端使用對稱加密生成密鑰key對傳輸數據進行加密,而後使用非對稱加密的公鑰對key再次加密。所以網絡上傳輸的數據是被key加密的密文和用公鑰加密後的密文key,即便被黑客截獲,因爲沒有私鑰便沒法獲取明文key,也就沒法獲取原始數據,所以HTTPS的加密方式是安全的。
以TLS1.2爲例來認識HTTPS的握手過程:
(1)客戶端發送client_hello,包含了一個隨機數random1
(2)服務器回覆server_hello,包含一個隨機數random2,攜帶證書公鑰P
(3)客戶端拿到這個random2以後作兩件事,第一件事是生成對稱加密的密鑰key,並用該密鑰對要傳輸的數據加密;第二件事就是用公鑰P對key加密,並將加密後的key和數據都發送給服務器
(4)服務器使用私鑰獲得key,並用key對數據加密,在相同的輸入參數下能獲得跟客戶端傳來的同樣的數據。apache
在訪問某些HTTPS站點時可能會遇到下面的異常:
javax.net.ssl.SSLHandshakeException:
sun.security.validator.ValidatorException: PKIX path building failed:
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
這個異常就是HTTPS握手的過程當中出現了問題。編程
HTTPS經過對稱加密和非對稱加密實現數據的安全傳輸,在非對稱加密的過程當中須要用公鑰來加密,這裏的公鑰實際上就被包含在數字證書中。
數字證書一般來講是由受信任的數字證書頒發機構CA,在驗證了服務器身份後頒發,證書中包含了一個密鑰對(公鑰和私鑰)和全部者識別信息。數字證書被放到服務器端,具備服務器身份驗證和數據傳輸加密的功能。
來看一下證書的申請流程: windows
若是一個用戶想要獲得一份屬於本身的證書,首先應該向CA提出申請。在CA判明申請者的身份以後,便爲他分配一個公鑰,而且將該公鑰與申請者的身份信息綁在一塊兒,併爲之簽字,便造成了數字證書發給申請者。
若是一個用戶想要鑑別另外一個證書的真僞,就用CA的公鑰對那個證書上的簽字進行驗證。一旦驗證經過,該證書就被認爲是有效的。
經過這種方式,黑客就不能簡單的修改證書中的公鑰了,由於公鑰有了CA的數字簽名,由CA證實公鑰的有效性,不能被輕易篡改。api
CA證書具備層級結構,它創建了自上而下的信任鏈。下級CA信任上級CA,下級CA由上級CA頒發證書並認證。那麼根證書由誰來驗證呢?根證書本身證實本身,換句話說,根證書不須要被證實。根證書是整個證書鏈的安全之本,若是根證書被篡改,那麼整個證書體系的安全將受到威脅。因此不要輕易相信根證書,當下次訪問某個網站遇到提示說,請安裝咱們的根證書,最好留個心眼。
除了CA機構頒發的證書以外,還有非CA機構頒發的證書和自簽名證書:
(1)非CA機構即不受信任的機構頒發的證書
(2)自簽名證書,就是本身給本身頒發的證書
好比12306網站在首頁都會標明:
爲保障您順暢購票,請下載安裝根證書。
關於12306的安裝根證書的問題,能夠看網上的幾篇博客:
在線買票爲何要安裝根證書
12306的證書問題數組
瀏覽器驗證證書
瀏覽器保存了一個經常使用的CA證書列表,在驗證證書鏈的有效性時,直接使用保存的證書裏的公鑰進行校驗。若是在證書列表中沒有找到或者找到了可是校驗不經過,那麼瀏覽器會警告用戶,由用戶決定是否繼續。
瀏覽器主要從三個方面驗證證書,任何一個不知足都會給出警告:
(1)證書的頒發者是否在"根受信任的證書頒發機構列表"中
(2)證書是否過時
(3)證書的持有者是否和訪問的網站一致
另外,瀏覽器還會按期查看證書頒發者公佈的"證書吊銷列表",若是某個證書符合上面的三個條件,可是被它的頒發者在"證書吊銷列表"中列出,那麼也將給出警告。Windows對這個列表是不敏感的,也就是說windows的api會緩存這個列表,直到設置的緩存過時纔會再次去下載最新的列表。瀏覽器
操做系統驗證證書
一樣,操做系統也保存了一份可信的證書列表,能夠運行certmgr.msc打開證書管理器查看,這些證書其實是存儲在Windows的註冊表中,通常在\SOFTWARE\Microsoft\SystemCertificates\下。緩存
JAVA驗證證書
在Java平臺下,證書經常被存儲在KeyStore文件中,KeyStore不只能夠存儲數字證書,還能夠存儲密鑰。存儲在KeyStore文件中的對象有三類:
Certificate: 證書
PrivateKey: 非對稱加密中的私鑰
SecretKey: 對稱加密中的密鑰
KeyStore文件根據用途有不少種不一樣的格式:JKS, JCEKS, PKCS12, DKS等等。
Java裏的KeyStore文件分紅兩種類型:KeyStore和TrustStore,不過這兩個從文件格式上來看是同樣的。KeyStore保存私鑰,用來加解密或爲別人作簽名。TrustStore保存一些可信任的證書,訪問HTTPS時對被訪問者進行認證,以確保它是可信任的。
除了KeyStore和TrustStore,Java還有兩個類KeyManager和TrustManager與此相關。看下面一張圖說明各個類之間的關係:
那麼具體的編程就能夠包括如下幾個步驟:
(1)使用KeyStore類將證書庫或信任庫加載進來
(2)使用KeyManagerFactory和加載了證書庫的KeyStore實例,生成KeyManager實例數組
(3)使用TrustManagerFactory和加載了信任庫的KeyStore實例,生成TrustManager實例數組
(4)使用SSLContext初始化KeyManager實例數組和TrustManager實例數組,從而設定好通訊的環境
(5)利用SSLContext產生的SSLSocket和SSLServerSocket進行通訊安全
HTTPS單向認證
單向認證是指只要服務器配置證書,客戶端在請求服務器時驗證服務器的證書便可。上述講到的內容大部分都是HTTPS單向認證,對於安全性要求不高的網站單向認證便可。
在Android系統中已經內置了全部CA機構的根證書,也就是說,只要是CA機構頒發的證書,Android是直接信任的。對於這種狀況,雖然能正常訪問到服務器,可是仍然存在安全隱患。若是黑客自家搭建了一個服務器並申請到了CA證書,那麼黑客仍然能發起中間人攻擊劫持咱們的請求到黑客的服務器,實際上就成了咱們的客戶端和黑客的服務器創建起了鏈接。
對於非CA機構頒發的證書和自簽名證書,咱們是沒法直接訪問到服務器的,直接訪問一般會拋出異常:
javax.net.ssl.SSLHandshakeException:
java.security.cert.CertPathValidatorException:
Trust anchor forcertification path not found.
有兩種方法能夠經過忽略證書直接調用:
方法一:自定義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); }
方法二:信任全部證書
@Test public void basicHttpsGetIgnoreCertificateValidation() throws Exception { SSLContextBuilder builder = new SSLContextBuilder(); //JAVA8支持的新語法 //TrustStrategy acceptingTrustStrategy = ((X509Certificate[] certificate,String type) -> true); //builder.loadTrustMaterial(null, acceptingTrustStrategy); //上面這兩步能夠這麼寫: builder.loadTrustMaterial(new TrustStrategy(){ @Override public boolean isTrusted(X509Certificate[] arg0, String arg1) throws CertificateException { return true; } }); SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(builder.build()); CloseableHttpClient httpclient = HttpClients.custom().setSSLSocketFactory( sslsf).build(); HttpGet httpGet = new HttpGet("https://some-server"); CloseableHttpResponse response = httpclient.execute(httpGet); try { System.out.println(response.getStatusLine()); HttpEntity entity = response.getEntity(); EntityUtils.consume(entity); } finally { response.close(); } }
方法二雖然解決了SSHHandshakeException異常,可是因爲客戶端信任全部的證書,反而存在更大的隱患。
單向驗證且服務器證書可信
package com.ydd.study.hello.httpclient; /** * @COPYRIGHT (C) 2018 Schenker AG * <p/> * All rights reserved */ import org.apache.http.HttpHost; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.protocol.HttpClientContext; import org.apache.http.config.Registry; import org.apache.http.config.RegistryBuilder; import org.apache.http.conn.routing.HttpRoute; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.TrustSelfSignedStrategy; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.ssl.SSLContexts; import org.apache.http.util.EntityUtils; import javax.net.ssl.SSLContext; import java.io.File; import java.io.IOException; import java.security.KeyManagementException; import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; public class oneWayAuthTest { public static CloseableHttpClient httpclient; // 得到池化得HttpClient static { // 設置truststore SSLContext sslcontext = null; try { sslcontext = SSLContexts.custom() .loadTrustMaterial(new File("D://https//ca//cl.jks"), "123456".toCharArray(), new TrustSelfSignedStrategy()) .build(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } // 客戶端支持TLSV1,TLSV2,TLSV3這三個版本 SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory( sslcontext, new String[]{"TLSv1", "TLSv2", "TLSv3"}, null, SSLConnectionSocketFactory.getDefaultHostnameVerifier());// 客戶端驗證服務器身份的策略 // Create a registry of custom connection socket factories for supported // protocol schemes. Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder .<ConnectionSocketFactory>create() .register("http", PlainConnectionSocketFactory.INSTANCE) .register("https", new SSLConnectionSocketFactory(sslcontext)) .build(); PoolingHttpClientConnectionManager connManager = new PoolingHttpClientConnectionManager( socketFactoryRegistry); // Configure total max or per route limits for persistent connections // that can be kept in the pool or leased by the connection manager. connManager.setMaxTotal(100); connManager.setDefaultMaxPerRoute(10); // 個性化設置某個url的鏈接 connManager.setMaxPerRoute(new HttpRoute(new HttpHost("www.y.com", 80)), 20); httpclient = HttpClients.custom().setConnectionManager(connManager) .build(); } /** * 單向驗證且服務端的證書可信 * @throws IOException * @throws ClientProtocolException */ public static void oneWayAuthorizationAccepted() throws ClientProtocolException, IOException { // Execution context can be customized locally. HttpClientContext context = HttpClientContext.create(); HttpGet httpget = new HttpGet("https://www.yunzhu.com:8443"); // 設置請求的配置 RequestConfig requestConfig = RequestConfig.custom() .setSocketTimeout(5000).setConnectTimeout(5000) .setConnectionRequestTimeout(5000).build(); httpget.setConfig(requestConfig); System.out.println("executing request " + httpget.getURI()); CloseableHttpResponse response = httpclient.execute(httpget, context); try { System.out.println("----------------------------------------"); System.out.println(response.getStatusLine()); System.out.println(EntityUtils.toString(response.getEntity())); System.out.println("----------------------------------------"); // Once the request has been executed the local context can // be used to examine updated state and various objects affected // by the request execution. // Last executed request context.getRequest(); // Execution route context.getHttpRoute(); // Target auth state context.getTargetAuthState(); // Proxy auth state context.getTargetAuthState(); // Cookie origin context.getCookieOrigin(); // Cookie spec used context.getCookieSpec(); // User security token context.getUserToken(); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] a) throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException { oneWayAuthorizationAccepted(); } }