HTTPS理論基礎及其在Android中的最佳實務

來源:互聯網
上載者:User

標籤:

我們知道,HTTP請求都是明文傳輸的,所謂的明文指的是沒有經過加密的資訊,如果HTTP請求被駭客攔截,並且裡面含有銀行卡密碼等敏感性資料的話,會非常危險。為瞭解決這個問題,Netscape 公司制定了HTTPS協議,HTTPS可以將資料加密傳輸,也就是傳輸的是密文,即便駭客在傳輸過程中攔截到資料也無法破譯,這就保證了網路通訊的安全。

密碼學基礎

在正式講解HTTPS協議之前,我們首先要知道一些密碼學的知識。

明文: 明文指的是未被加密過的未經處理資料。

密文:明文被某種密碼編譯演算法加密之後,會變成密文,從而確保未經處理資料的安全。密文也可以被解密,得到原始的明文。

密鑰:密鑰是一種參數,它是在明文轉換為密文或將密文轉換為明文的演算法中輸入的參數。密鑰分為對稱金鑰與非對稱金鑰,分別應用在對稱式加密和非對稱式加密上。

對稱式加密:對稱式加密又叫做私密金鑰加密,即資訊的發送方和接收方使用同一個密鑰去加密和解密資料。對稱式加密的特點是演算法公開、加密和解密速度快,適合於對大資料量進行加密,常見的對稱式加密演算法有DES、3DES、TDEA、Blowfish、RC5和IDEA。
其加密過程如下:明文 + 密碼編譯演算法 + 私密金鑰 => 密文
解密過程如下:密文 + 解密演算法 + 私密金鑰 => 明文
對稱式加密中用到的密鑰叫做私密金鑰,私密金鑰表示個人私人的密鑰,即該密鑰不能被泄露。
其加密過程中的私密金鑰與解密過程中用到的私密金鑰是同一個密鑰,這也是稱加密之所以稱之為“對稱”的原因。由於對稱式加密的演算法是公開的,所以一旦私密金鑰被泄露,那麼密文就很容易被破解,所以對稱式加密的缺點是密鑰安全管理困難。

非對稱式加密:非對稱式加密也叫做公開金鑰加密。非對稱式加密與對稱式加密相比,其安全性更好。對稱式加密的通訊雙方使用相同的密鑰,如果一方的密鑰遭泄露,那麼整個通訊就會被破解。而非對稱式加密使用一對密鑰,即公開金鑰和私密金鑰,且二者成對出現。私密金鑰被自己儲存,不能對外泄露。公開金鑰指的是公用的密鑰,任何人都可以獲得該密鑰。用公開金鑰或私密金鑰中的任何一個進行加密,用另一個進行解密。
被公開金鑰加密過的密文只能被私密金鑰解密,過程如下:
明文 + 密碼編譯演算法 + 公開金鑰 => 密文, 密文 + 解密演算法 + 私密金鑰 => 明文
被私密金鑰加密過的密文只能被公開金鑰解密,過程如下:
明文 + 密碼編譯演算法 + 私密金鑰 => 密文, 密文 + 解密演算法 + 公開金鑰 => 明文
由於加密和解密使用了兩個不同的密鑰,這就是非對稱式加密“非對稱”的原因。
非對稱式加密的缺點是加密和解密花費時間長、速度慢,只適合對少量資料進行加密。
在非對稱式加密中使用的主要演算法有:RSA、Elgamal、Rabin、D-H、ECC(橢圓曲線密碼編譯演算法)等。

HTTPS通訊過程

HTTPS協議 = HTTP協議 + SSL/TLS協議,在HTTPS資料轉送的過程中,需要用SSL/TLS對資料進行加密和解密,需要用HTTP對加密後的資料進行傳輸,由此可以看出HTTPS是由HTTP和SSL/TLS一起合作完成的。

SSL的全稱是Secure Sockets Layer,即安全套接層協議,是為網路通訊提供安全及資料完整性的一種安全性通訊協定。SSL協議在1994年被Netscape發明,後來各個瀏覽器均支援SSL,其最新的版本是3.0

TLS的全稱是Transport Layer Security,即安全傳輸層協議,最新版本的TLS(Transport Layer Security,傳輸層安全性通訊協定)是IETF(Internet Engineering Task Force,Internet工程工作群組)制定的一種新的協議,它建立在SSL 3.0協議規範之上,是SSL 3.0的後續版本。在TLS與SSL3.0之間存在著顯著的差別,主要是它們所支援的密碼編譯演算法不同,所以TLS與SSL3.0不能互操作。雖然TLS與SSL3.0在密碼編譯演算法上不同,但是在我們理解HTTPS的過程中,我們可以把SSL和TLS看做是同一個協議。

HTTPS為了兼顧安全與效率,同時使用了對稱式加密和非對稱式加密。資料是被對稱式加密傳輸的,對稱式加密過程需要用戶端的一個密鑰,為了確保能把該密鑰安全傳輸到伺服器端,採用非對稱式加密對該密鑰進行加密傳輸,總的來說,對資料進行對稱式加密,對稱式加密所要使用的密鑰通過非對稱式加密傳輸。

以片來自於limboy的部落格

HTTPS在傳輸的過程中會涉及到三個密鑰:

  • 伺服器端的公開金鑰和私密金鑰,用來進行非對稱式加密

  • 用戶端產生的隨機密鑰,用來進行對稱式加密

一個HTTPS請求實際上包含了兩次HTTP傳輸,可以細分為8步。

  1. 用戶端向伺服器發起HTTPS請求,串連到伺服器的443連接埠。

  2. 伺服器端有一個金鑰組,即公開金鑰和私密金鑰,是用來進行非對稱式加密使用的,伺服器端儲存著私密金鑰,不能將其泄露,公開金鑰可以發送給任何人。

  3. 伺服器將自己的公開金鑰發送給用戶端。

  4. 用戶端收到伺服器端的公開金鑰之後,會對公開金鑰進行檢查,驗證其合法性,如果發現發現公開金鑰有問題,那麼HTTPS傳輸就無法繼續。嚴格的說,這裡應該是驗證伺服器發送的數位憑證的合法性,關於用戶端如何驗證數位憑證的合法性,下文會進行說明。如果公開金鑰合格,那麼用戶端會產生一個隨機值,這個隨機值就是用於進行對稱式加密的密鑰,我們將該密鑰稱之為client key,即用戶端密鑰,這樣在概念上和伺服器端的密鑰容易進行區分。然後用伺服器的公開金鑰對用戶端密鑰進行非對稱式加密,這樣用戶端密鑰就變成密文了,至此,HTTPS中的第一次HTTP請求結束。

  5. 用戶端會發起HTTPS中的第二個HTTP請求,將加密之後的用戶端密鑰發送給伺服器。

  6. 伺服器接收到用戶端發來的密文之後,會用自己的私密金鑰對其進行非對稱解密,解密之後的明文就是用戶端密鑰,然後用用戶端金鑰組資料進行對稱式加密,這樣資料就變成了密文。

  7. 然後伺服器將加密後的密文發送給用戶端。

  8. 用戶端收到伺服器發送來的密文,用用戶端金鑰組其進行對稱解密,得到伺服器發送的資料。這樣HTTPS中的第二個HTTP請求結束,整個HTTPS傳輸完成。

數位憑證

通過觀察HTTPS的傳輸過程,我們知道,當伺服器接收到用戶端發來的請求時,會向用戶端發送伺服器自己的公開金鑰,但是駭客有可能中途篡改公開金鑰,將其改成駭客自己的,所以有個問題,用戶端怎麼信賴這個公開金鑰是自己想要訪問的伺服器的公開金鑰而不是駭客的呢? 這時候就需要用到數位憑證。

在講數位憑證之前,先說一個小例子。假設一個鎮裡面有兩個人A和B,A是個富豪,B想向A借錢,但是A和B不熟,怕B借了錢之後不還。這時候B找到了鎮長,鎮長給B作擔保,告訴A說:“B人品不錯,不會欠錢不還的,你就放心借給他吧。” A聽了這話後,心裡想:“鎮長是全鎮最德高望重的了,他說B沒問題的話那就沒事了,我就放心了”。 於是A相信B的為人,把錢借給了B。

與此相似的,要想讓用戶端信賴公開金鑰,公開金鑰也要找一個擔保人,而且這個擔保人的身份必須德高望重,否則沒有說服力。這個擔保人的就是認證認證中心(Certificate Authority),簡稱CA。 也就是說CA是專門對公開金鑰進行認證,進行擔保的,也就是專門給公開金鑰做擔保的擔保公司。 全球知名的CA也就100多個,這些CA都是全球都認可的,比如VeriSign、GlobalSign等,國內知名的CA有WoSign。

那CA怎麼對公開金鑰做擔保認證呢?CA本身也有一對公開金鑰和私密金鑰,CA會用CA自己的私密金鑰對要進行認證的公開金鑰進行非對稱式加密,此處待認證的公開金鑰就相當於是明文,加密完之後,得到的密文再加上認證的到期時間、頒發給、頒發者等資訊,就組成了數位憑證。

不論什麼平台,裝置的作業系統中都會內建100多個全球公認的CA,說具體點就是裝置中儲存了這些知名CA的公開金鑰。當用戶端接收到伺服器的數位憑證的時候,會進行如下驗證:

  1. 首先用戶端會用裝置中內建的CA的公開金鑰嘗試解密數位憑證,如果所有內建的CA的公開金鑰都無法解密該數位憑證,說明該數位憑證不是由一個全球知名的CA簽發的,這樣用戶端就無法信任該伺服器的數位憑證。

  2. 如果有一個CA的公開金鑰能夠成功解密該數位憑證,說明該數位憑證就是由該CA的私密金鑰簽發的,因為被私密金鑰加密的密文只能被與其成對的公開金鑰解密。

  3. 除此之外,還需要檢查用戶端當前訪問的伺服器的網域名稱是與數位憑證中提供的“頒發給”這一項吻合,還要檢查數位憑證是否到期等。

通過瀏覽器直接擷取伺服器的公開金鑰很容易,各個瀏覽器操作大同小異。百度現在已經實現了全網站HTTPS,我們就以百度為例如何從Chrome中擷取其公開金鑰。

  1. 用Chrome開啟百度首頁,在https左側我們會發現有一個綠色的鎖頭。

  2. 點擊該鎖頭,出現一個彈出面板,點擊面板中的“詳細資料”幾個字。

  3. 這是會開啟Chrome的Developer Tool,並自動切換到Security這個頁面。

  4. 點擊“View ceertificate”按鈕就可以查看該網站的認證了,如下所示:

    在“常規”這個面板中,我們從中可以查看該認證是又Symantec頒發給baidu.com這個網站的,有效期間是從2015年9月17到2016年9月1日。

  5. 切換到“詳細資料”面板,可以查看認證的一些詳細資料,比如認證所使用的數位簽章的演算法,如所示:

    上面有個“複製到檔案”的按鈕,點擊該按鈕就可以將百度的數位憑證匯出成檔案,從而我們就可以儲存到自己的機器上,介面如下所示:

    我們將其匯出成X.509格式的認證,以.cer作為副檔名,最後儲存到本地機器如下所示:

  6. 切換到“憑證路徑”面板,可以查看認證的憑證鏈結。

    這裡先解釋一下什麼是憑證鏈結。我們之前提到,VeriSign是一個全球知名的CA,但是一般情況下,CA不會用自己的私密金鑰去直接簽名某網站的數位憑證,一般CA會首先簽發一種認證,然後用這種認證再去簽發百度等的數位憑證。在此例中,VeriSign簽發了Symantec認證,然後Symantec又簽發了baiduc.om,VeriSign位於最頂端,類似於根結點,因此叫做根CA,Symatec位於中間,叫做中間CA,當然,有可能有多個中間CA,這樣從根CA到中間CA,再到最終的網站的認證,這樣自上而下形成了一條憑證鏈結。如果想要查看憑證鏈結中的某個認證,只需要選中它,比如選中了Symantec,然後點擊下面的“查看認證”按鈕就會彈出另一個對話方塊,在其中可以查看Symantec的數位憑證,當然也可以將其匯出成認證檔案儲存在硬碟上。

Android中訪問HTTPS

在Android中我們也會經常發送HTTPS請求,這時需要使用HttpsURLConnection這個類,HttpsURLConnection是繼承自HttpURLConnection的,其用法跟HttpURLConnection是一樣的,比如我們想用HTTPS訪問百度的首頁,代碼如下所示:

URL url = new URL("https://www.baidu.com");HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();InputStream is = conn.getInputStream();...

我們通過得到的InputStream就可以解析伺服器的返回結果了。

如果對應伺服器的數位憑證存在問題,那麼用戶端就無法信任該認證,從而無法建立HTTPS連結,我們以國內的12306.cn網站為例進行說明。

12306.cn的使用者登入是需要HTTPS的訪問的,如果瀏覽器第一次開啟頁面https://kyfw.12306.cn/otn/regist/init,那麼瀏覽器要麼顯示認證警告資訊,要麼索性直接不顯示頁面,因為12306.cn的數位憑證存在問題。

其憑證鏈結如下所示:

大家可以看到,該12306.cn的認證是由SRCA這個機構簽發的,也就是說SRCA是憑證鏈結上的根CA。

但是SRCA是啥呢?沒聽過啊!

我們選中SRCA後,點擊“查看認證”按鈕,SRCA的認證如下所示:

也就是說SRCA的全稱是Sinorail Certification Authority, 在百度裡面搜尋該名稱,可以查到一個叫做中鐵資訊工程集團的網站,http://www.sinorail.com/ProductInfos.aspx?id=185,裡面有這麼一段描述:

也就是說SRCA是鐵道部給旗下的網站等做簽名的一個所謂CA,但是它不具備公信力,因為它不是一個全球知名的CA,所以用戶端根本不認可它。

我們在Android中直接存取https://kyfw.12306.cn/otn/regist/init時,會得到如下異常:
java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

這是因為Android的用戶端內建的可信任CA列表中沒有包含所謂的SRCA,這樣就出現了12306.cn的認證不被用戶端信任的異常。

為瞭解決用戶端不信任伺服器數位憑證的問題,網路上大部分的解決方案都是讓用戶端不對認證做任何檢查,這是一種有很大安全性漏洞的辦法,如下所示:

首先定義一個自己的SSLTrustAllManager,其繼承自X509TrustManager,具體來說,用SSLTrustAllManager建立一個SSLContext,然後用SSLContext產生一個SSLSocketFactory,然後通過調用HttpURLConnectoin的setSSLSocketFactory方法將其給某個具體的連線物件使用。由於SSLTrustAllManager沒有對其中的三個核心方法進行具體實現,也就是不對認證做任何審查。這樣無論伺服器的認證如何,都能建立HTTPS串連,因為用戶端直接忽略了驗證伺服器憑證這一步。

這樣雖然能建立HTTPS串連,但是存在很大的安全性漏洞。因為駭客有可能攔截到我們的HTTPS請求,然後駭客用自己的假認證冒充12306.cn的數位憑證,由於用戶端不對認證做驗證,這樣用戶端就會和駭客的伺服器建立串連,這樣會導致用戶端把自己的12306的帳號和密碼發送給了駭客,所以用戶端不對認證做任何驗證的做法有很大的安全性漏洞。

解決此問題的辦法是讓Android用戶端信任12306的認證,而不是不對認證做任何檢查。

我們通過上面提到的方法得到12306的認證12306.cer,將其放到assets目錄下,也就是將12306.cer打包到我們的App中,然後用如下代碼訪問12306的HTTPS網站。

class DownloadThread extends Thread{        @Override        public void run() {            HttpsURLConnection conn = null;            InputStream is = null;            try {                URL url = new URL("https://kyfw.12306.cn/otn/regist/init");                conn = (HttpsURLConnection)url.openConnection();                //建立X.509格式的CertificateFactory                CertificateFactory cf = CertificateFactory.getInstance("X.509");                //從asserts中擷取認證的流                InputStream cerInputStream = getAssets().open("12306.cer");//SRCA.cer                //ca是java.security.cert.Certificate,不是java.security.Certificate,                //也不是javax.security.cert.Certificate                Certificate ca;                try {                    //認證工廠根據認證檔案的流產生認證Certificate                    ca = cf.generateCertificate(cerInputStream);                    System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN());                } finally {                    cerInputStream.close();                }                // 建立一個預設類型的KeyStore,儲存我們信任的認證                String keyStoreType = KeyStore.getDefaultType();                KeyStore keyStore = KeyStore.getInstance(keyStoreType);                keyStore.load(null, null);                //將認證ca作為信任的認證放入到keyStore中                keyStore.setCertificateEntry("ca", ca);                //TrustManagerFactory是用於產生TrustManager的,我們建立一個預設類型的TrustManagerFactory                String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm();                TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm);                //用我們之前的keyStore執行個體初始化TrustManagerFactory,這樣tmf就會信任keyStore中的認證                tmf.init(keyStore);                //通過tmf擷取TrustManager數組,TrustManager也會信任keyStore中的認證                TrustManager[] trustManagers = tmf.getTrustManagers();                //建立TLS類型的SSLContext對象, that uses our TrustManager                SSLContext sslContext = SSLContext.getInstance("TLS");                //用上面得到的trustManagers初始化SSLContext,這樣sslContext就會信任keyStore中的認證                sslContext.init(null, trustManagers, null);                //通過sslContext擷取SSLSocketFactory對象                SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();                //將sslSocketFactory通過setSSLSocketFactory方法作用於HttpsURLConnection對象                //這樣conn對象就會信任我們之前得到的認證對象                conn.setSSLSocketFactory(sslSocketFactory);                is = conn.getInputStream();                //將得到的InputStream轉換為字串                final String str = getStringByInputStream(is);                handler.post(new Runnable() {                    @Override                    public void run() {                        textView.setText(str);                        btn.setEnabled(true);                    }                });            }catch (Exception e){                e.printStackTrace();                final String errMessage = e.getMessage();                handler.post(new Runnable() {                    @Override                    public void run() {                        btn.setEnabled(true);                        Toast.makeText(MainActivity.this, errMessage, Toast.LENGTH_LONG).show();                    }                });            }finally {                if(conn != null){                    conn.disconnect();                }            }        }

上面的注釋寫的比較詳細,此處我們還是對以上代碼進行一下說明。

  1. 首先從asserts目錄中擷取12306.cer認證的檔案流,然後用CertificateFactory的generateCertificate方法將該檔案流轉化為一個認證對象Certificate,該認證就是12306網站的數位憑證。

  2. 建立一個預設類型的KeyStore執行個體,keyStore用於儲存著我們信賴的數位憑證,將12306的數位憑證放入keyStore中。

  3. 我們擷取一個預設的TrustManagerFactory的執行個體,並用之前的keyStore初始化它,這樣TrustManagerFactory的執行個體也會信任keyStore中12306.cer認證。通過TrustManagerFactory的getTrustManagers方法擷取TrustManager數組,該數組中的TrustManager也會信任12306.cer認證。

  4. 建立一個TLS類型的SSLContext執行個體,並用之前的TrustManager數組初始化sslContext執行個體,這樣該sslContext執行個體也會信任12306.cer認證。

  5. 通過sslContext擷取SSLSocketFactory對象,將sslSocketFactory通過setSSLSocketFactory方法作用於HttpsURLConnection對象,這樣conn對象就會信任keyStore中的12306.cer認證。這樣一來,用戶端就會信任12306的認證,從而正確建立HTTPS串連。

以上的處理過程是Google官方建議的流程,步驟流程總結如下:

認證檔案流 InputStream -> Certificate -> KeyStore -> TrustManagerFactory -> TrustManager[] -> SSLContext -> SSLSocketFactory -> HttpsURLConnection

以上步驟的起點是擷取認證檔案的檔案流,不一定非要從assets目錄中擷取,也可以通過其他途徑得到,只要能拿到認證的檔案流即可。

上面的過程是對的,但是還存在一點問題。我們將12306網站自身的12306.cer放到了assets目錄中,然後讓我們建立的HttpsURLConnection的執行個體信任了12306.cer。但是,數位憑證都是有到期時間的,如果12306網站的數位憑證到期了,那麼12306會去SRCA那裡重建一個數位憑證,這時候12306網站的公開金鑰和私密金鑰都會更新,那這樣就存在問題了。我們App的assets目錄中儲存的是老的12306.cer認證,這樣12306網站重建了新的數位憑證,那麼老的數位憑證就自動作廢了,因為我們App中的12306.cer中的老的公開金鑰無法解密12306網站最新的私密金鑰了(公開金鑰和私密金鑰只能成對出現,舊的公開金鑰只能解密舊的私密金鑰)。

很不幸的是,網上大部分的解決方案就是直接信任12306.cer這種網站自身的數位憑證,雖然這種辦法暫時可以解決HTTPS問題,但是不是長久之計,會為以後的數位憑證的更新埋下隱患。

那怎麼解決這個問題呢?

最好的辦法不是讓我們的App直接信任12306.cer,而是讓我們的App直接信任12306數位憑證的簽發者SRCA的數位憑證。

我們用之前提到過的辦法將12306的簽發者SRCA的數位憑證匯出,取名SRCA.cer,將其放到assets目錄下,我們只需要改一行代碼裡面的參數即可,我們將代碼:

InputStream cerInputStream = getAssets().open("12306.cer");

修成該

InputStream cerInputStream = getAssets().open("SRCA.cer");

也就是我們讀取的是SRCA.cer認證的檔案流,而不再是12306.cer的。

通過讓HttpsURLConnection執行個體信任SRCA.cer,也能夠正常建立HTTPS串連,這是為什麼呢?

我們之前提到了憑證鏈結的概念,假設存在如下憑證鏈結:
CA -> A -> B -> C

CA是根CA認證,例如SRCA.cer,C是最終的網站的數位憑證,例如12306.cer,A和B都是中間認證,理論上來說,只要用戶端信任了該數位憑證鏈中的任何一個認證,那麼C認證都會被信任。比如用戶端信任了根憑證CA,由於CA信任A,所以用戶端也會信任A,由於A信任B,那麼用戶端也信任B,由於B信任C,那麼用戶端也信任C。所以在12306的例子中,只要信任了SRCA.cer,那麼用戶端就信任12306網站自身的12306.cer數位憑證了。

Android用戶端不信任伺服器憑證的原因主要是因為用戶端不信任憑證鏈結中的根憑證CA。12306網站的自身的數位憑證可能會過幾年就會重建,發生變動,但是SRCA作為其簽發者,發生變動的次數會少的多,或者說是很長時間內不會改動,所以我們的App去信任SRCA.cer比直接去信任12306.cer要更穩定一些。

總結:

  1. HTTPS的傳輸過程涉及到了對稱式加密和非對稱式加密,對稱式加密加密的是實際的資料,非對稱式加密加密的是對稱式加密所需要的用戶端的密鑰。

  2. 為了確保用戶端能夠確認公開金鑰就是想要訪問的網站的公開金鑰,引入了數位憑證的概念,由於認證存在一級一級的簽發過程,所以就出現了憑證鏈結,在憑證鏈結中的頂端的就是根CA。

  3. Android用戶端不信任伺服器憑證的原因主要是因為用戶端不信任憑證鏈結中的根憑證CA,我們應該讓我們的App去信任該根憑證CA,而不是直接信任網站的自身的數位憑證,因為網站的數位憑證可能會發生變化。

希望本身對大家有所協助!

參考:
Limboy的《圖解HTTPS》
阮一峰的《數位簽章是什嗎?》
Google官方的《Security with HTTPS and SSL》

相關閱讀:
我的Android博文整理匯總
Android中HttpURLConnection使用詳解

HTTPS理論基礎及其在Android中的最佳實務

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.