簡述我對HTTPS的理解

HTTPS原理

我們先看一下定義,來自wikipedia的一個介紹:

HTTPS (also called HTTP over Transport Layer Security (TLS), HTTP over SSL, and HTTP Secure) is a communications protocol for secure communication over a computer network which is widely used on the Internet. HTTPS consists of communication over Hypertext Transfer Protocol (HTTP) within a connection encrypted by Transport Layer Security, or its predecessor, Secure Sockets Layer. The main motivation for HTTPS is authentication of the visited website and protection of the privacy and integrity of the exchanged data.

從這個定義中我們可以看出,HTTPS是包含了HTTP協議及SSL /TLS協議這兩部分內容,簡單的理解就是基于SSL/TLS進行HTTP的加密傳輸。HTTP是一個應用層的協議,定義了很多請求和響應方通信的遵循的規則,這部分內容可以從HTTP權威指南這部巨作中得到很詳細的介紹,這里就不贅述了。其實主要還是想探討一下SSL/TLS協議的一些具體細節,畢竟這是HTTPS區別于HTTP最大的地方,首先我們來看一下一個SSL/TLS完整的握手過程。

SSL/TLS握手過程

很復雜的交互過程,但是理解下來就是用非對稱加密的手段傳遞密鑰,然后用密鑰進行對稱加密傳遞數據。在這個握手過程中最重要的就是證書校驗,其他就是正常的數據交互過程。如何校驗一個證書合法有很大的文章,處理不好就會讓你的網絡失去了安全性。一個證書的校驗,主要包括以下幾個方面:

  • 第一,校驗證書是否是由客戶端中“受信任的根證書頒發機構”頒發;
  • 第二,校驗證書是否在上級證書的吊銷列表;
  • 第三,校驗證書是否過期;
  • 第四,校驗證書域名是否一致。

一天我們的QA妹子氣憤憤的找到我說,為啥別人的APP可以用Charles抓到HTTPS的包,為啥我們的不能,我心中竊喜的告訴她只能說明我們技高一籌了。具體如何做到的后面我會分享一下我們的做法,先討論一下Charles如何實現https的抓包的,這里面涉及到一個中間人攻擊的問題。

一個針對SSL的中間人攻擊過程如下:

image.png

中間人其實是做了一個偷梁換柱的動作,核心是如何欺騙客戶端,從而讓客戶端能夠放心的與中間人進行數據交互而沒有任何察覺。我們來看Charles如何做到HTTPS抓包的,網上有很多Charles如何抓HTTPS包的教程,幾步就搞定了,其中最核心的就是:

將私有CA簽發的數字證書安裝到手機中并且作為受信任證書保存

自簽發一個證書實現上述二、三、四條校驗規則很簡單,要把這個證書安裝到手機端信任列表必須得到用戶的許可,這里不好繞過,但是鑒于大部分用戶的網絡安全意識比較差,有時也會稀里糊涂的信任了,那我們作為APP的開發人員,能否避免這種情況的發生呢?

其實也很簡單,我們把服務端的證書內置在我們的APP里,我們在做服務端證書校驗的時候只比對是否和這個證書完全相同,不同就直接拋錯,那中間人便沒有辦法繞過證書進行攻擊。但是這里面也有一個問題就是服務端的證書可能會過期或者升級,而且服務端往往為了提高網絡的安全性,證書的有效時間不會設置太長,這樣APP就會因為這個證書的事情頻繁發版,也很痛苦。(前段時間我司IOS的APP就是因為授權企業用戶的證書沒有及時更新,導致大家無法正常打開APP,血的教訓導致我們不想重走這條路)可能你又想到了,我們可以把證書配置在后端,有更新的時候直接去下載不就完了,那我們的證書下載沒有沒攔截的風險嗎,一旦攔截,我們所有的證書校驗都會失效,比直接信任手機內置的證書更可怕。我們既不想只信任我們服務器的證書,又不想信任手機上所有的 CA 證書。有個不錯的的信任方式是把簽發我們服務器的證書的根證書導出打包到APP中,這樣雖然不能做到百分之百的證書無漏洞,但是相比于信任手機中幾百個證書,我們只信任一個風險會小很多,這也就是我們的QA妹子用Charles抓不了我們的包的原因。~~~

OKHTTP

作為一個Android開發者,我們來看一下牛逼閃閃的網絡庫OKHTTP對于HTTPS的支持。下面這段話摘自OKHTTP對于HTTPS的介紹中(地址請戳中文地址):

OkHttp attempts to balance two competing concerns:

  • Connectivity to as many hosts as possible. That includes advanced hosts that run the latest versions of boringssl and less out of date hosts running older versions of OpenSSL.
  • Security of the connection. This includes verification of the remote webserver with certificates and the privacy of data exchanged with strong ciphers.

幾個與HTTPS相關的API:

SSLSocketFactory:

安全套接層工廠,用于創建SSLSocket。默認的SSLSocket是信任手機內置信任的證書列表,我們可以通過OKHttpClient.Builder的sslSocketFactory方法定義我們自己的信任策略,比如實現上面提到的我們只信任服務端證書的根證書,代碼實現如下:

/**
     * 載入證書
     */
    public static SSLSocketFactory getSSLSocketFactory(InputStream... certificates) {
        try {
//用我們的證書創建一個keystore
            CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
            KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
            keyStore.load(null);
            int index = 0;
            for (InputStream certificate : certificates) {
                String certificateAlias = "server"+Integer.toString(index++);
                keyStore.setCertificateEntry(certificateAlias, certificateFactory.generateCertificate(certificate));
                try {
                    if (certificate != null) {
                        certificate.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
//創建一個trustmanager,只信任我們創建的keystore
            SSLContext sslContext = SSLContext.getInstance("TLS");
            TrustManagerFactory trustManagerFactory =
                    TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustManagerFactory.init(keyStore);
            sslContext.init(
                    null,
                    trustManagerFactory.getTrustManagers(),
                    new SecureRandom()
            );
            return sslContext.getSocketFactory();
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

X509TrustManager:

public interface X509TrustManager extends TrustManager {
    void checkClientTrusted(X509Certificate[] var1, String var2) throws CertificateException;

    void checkServerTrusted(X509Certificate[] var1, String var2) throws CertificateException;

    X509Certificate[] getAcceptedIssuers();
}

checkServerTrusted方式實現了對于服務端校驗,這里一般使用系統默認的實現,有些教程講到這樣配置ssl

private static synchronized SSLSocketFactory getDefaultSSLSocketFactory() {
    try {
        SSLContext sslContext = SSLContext.getInstance("TLS");
        sslContext.init(null, new TrustManager[]{
                new X509TrustManager() {
                    public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {

                    }

                    public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
                    }

                    public X509Certificate[] getAcceptedIssuers() {
                        return new X509Certificate[0];
                    }
                }
        }, null);
        return sslContext.getSocketFactory();
    } catch (GeneralSecurityException e) {
        throw new AssertionError();
    }
}

千萬不能這么做,這樣將你是沒有做任何校驗的,這里推薦使用系統默認的,他會在校驗過程中發現有異常直接拋出。

HostnameVerifier:

public interface HostnameVerifier {
    boolean verify(String var1, SSLSession var2);
}

這個接口主要實現對于域名的校驗,OKHTTP實現了一個OkHostnameVerifier,對于證書中的IP及Host做了各種正則匹配,默認情況下使用的是這個策略。有些你遇到了一些奇怪的校驗問題,大部分教程會教你這樣:

OKHttpClient.Builder.hostnameVerifier(new HostnameVerifier() {
                    @Override
                    public boolean verify(String hostname, SSLSession session) {
                        return true;
                    }
                })

其實這樣你是完全放棄了hostname的校驗,這也是相當不安全的。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容