HTTPS 數字簽名 證書

HTTPS

先來看一下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

SSL/TLS協議及握手過程

和TCP在創建鏈接時的三次握手同樣,SSL/TLS也須要客戶端和服務器之間進行握手,可是目的不同。在SSL/TLS握手的過程當中,客戶端和服務器彼此交換並驗證證書,並協商出一個"對話密鑰",後續的全部通訊都是用這個"對話密鑰"進行加密,保證通訊安全。 
用一張圖來簡單的說明一下握手的過程: 
Capture.PNG-90.5kB
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,在驗證了服務器身份後頒發,證書中包含了一個密鑰對(公鑰和私鑰)和全部者識別信息。數字證書被放到服務器端,具備服務器身份驗證和數據傳輸加密的功能。 
來看一下證書的申請流程: 
Capture1.PNG-27.4kB
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與此相關。看下面一張圖說明各個類之間的關係: 
Capture3.PNG-73.8kB 
那麼具體的編程就能夠包括如下幾個步驟: 
(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();
    }

}
相關文章
相關標籤/搜索