1. 程式人生 > 實用技巧 >你想了解的 HTTPS 都在這裡

你想了解的 HTTPS 都在這裡

HTTP 協議僅僅制定了網際網路傳輸的標準,簡化了直接使用 TCP 協議進行通訊的難度。有關 HTTP 協議相關的講解請看前面兩節:

HTTP 協議詳解

HTTP協議詳解(二)

less is more 的概念本身很好,但是過於簡單也會承擔一些後果:

通訊使用明文,內容可能會竊聽

HTTP本身不具備加密的功能,所以也無法做到對通訊整體(使用HTTP協議通訊的請求和響應的內容)進行加密。即,HTTP報文使用明文(指未經過加密的報文)方式傳送。

報文是否是正經使用者發出的報文無法得知,可能被篡改

HTTP協議無法證明通訊的報文完整性,因此,在請求或響應送出之後直到對方接收之前的這段時間內,即使請求或響應的內容遭到篡改,也沒有辦法獲悉。


換句話說,沒有任何辦法確認,發出的請求/響應和接收到的請求/響應是前後相同的。

不驗證傳送方身份,可能遭遇偽裝

HTTP協議中的請求和響應不會對通訊方進行確認。在HTTP協議通訊時,由於不存在確認通訊方的處理步驟,任何人都可以發起請求。另外,伺服器只要接收到請求,不管對方是誰都會返回一個響應(但也僅限於傳送端的IP地址和埠號沒有被Web伺服器設定限制訪問的前提下)

HTTP協議無法驗證通訊方身份,任何人都可以偽造虛假伺服器欺騙使用者,實現釣魚欺詐,使用者無法察覺。

因為有以上問題且隨著時代發展,黑客的技術能力也越來越強,非加密的 HTTP 請求很容易引起相關的網路安全問題,對於安全效能的攻防控制需求也越來越強烈。

1. 什麼是安全

既然 HTTP 不安全,那什麼樣的通訊過程才是安全的呢?

通常認為,如果通訊過程具備了四個特性,就可以認為是 安全 的,這四個特性是:機密性、完整性,身份認證和不可否認。

  • 機密性(Secrecy/Confidentiality):是指對資料的保密,只能由可信的人訪問,對其他人是不可見的祕密,簡單來說就是不能讓不相關的人看到不該看的東西。

  • 完整性(Integrity,也叫一致性):是指資料在傳輸過程中沒有被竄改,不多也不少,完完整整地保持著原狀。

  • 身份認證(Authentication):是指確認對方的真實身份,也就是 證明你真的是你,保證訊息只能傳送給可信的人。

    如果通訊時另一方是假冒的網站,那麼資料再保密也沒有用,黑客完全可以使用冒充的身份 套 出各種資訊,加密和沒加密一樣。

  • 不可否認(Non-repudiation/Undeniable),也叫不可抵賴,意思是不能否認已經發生過的行為,不能說話不算數,耍賴皮

使用前三個特性,可以解決安全通訊的大部分問題,但如果缺了不可否認,那通訊的事務真實性就得不到保證,有可能出現 老賴

比如,小明借了小紅一千元,沒寫借條,第二天矢口否認,小紅也確實拿不出借錢的證據,只能認倒黴。另一種情況是小明借錢後還了小紅,但沒寫收條,小紅於是不承認小明還錢的事,說根本沒還,要小明再掏出一千元。

所以,只有同時具備了機密性、完整性、身份認證、不可否認這四個特性,通訊雙方的利益才能有保障,才能算得上是真正的安全。

2. HTTPS 解決什麼問題

HTTPS 為 HTTP 增加了剛才所說的四大安全特性。

HTTPS 其實是一個 非常簡單 的協議,RFC 檔案只有短短的 7 頁,裡面規定了新的協議名https,預設埠號 443,至於其他的什麼請求 - 應答模式、報文結構、請求方法、URI、頭欄位、連線管理等等都完全沿用 HTTP,沒有任何新的東西。

也就是說,除了協議名 http 和埠號 80 這兩點不同,HTTPS 協議在語法、語義上和 HTTP 完全一樣,優缺點也照單全收(當然要除去 明文不安全 )。

HTTPS 憑什麼就能做到機密性、完整性這些安全特性呢?

祕密就在於 HTTPS 名字裡的 S,它把 HTTP 下層的傳輸協議由 TCP/IP 換成了 SSL/TLS,由 HTTP over TCP/IP 變成了 HTTP over SSL/TLS,讓 HTTP 執行在了安全的 SSL/TLS 協議上,收發報文不再使用 Socket API,而是呼叫專門的安全介面。HTTPS 本身並沒有什麼驚世駭俗的本事,全是靠著後面的 SSL/TLS 撐腰。只要學會了 SSL/TLS,HTTPS 自然就手到擒來。

HTTPS 協議的主要功能基本都依賴於 TLS/SSL 協議,TLS/SSL 的功能實現主要依賴於三類基本演演算法:雜湊函式 、對稱加密和非對稱加密,利用非對稱加密實現身份認證和金鑰協商,對稱加密演演算法採用協商的金鑰對資料加密,基於雜湊函式驗證資訊的完整性。

3. 什麼是 SSL/TLS

現在我們就來看看 SSL/TLS,它到底是個什麼來歷。

SSL 即安全套接層(Secure Sockets Layer),在 OSI 模型中處於第 5 層(會話層),由網景公司於 1994 年發明,有 v2 和 v3 兩個版本,而 v1 因為有嚴重的缺陷從未公開過。

SSL 發展到 v3 時已經證明瞭它自身是一個非常好的安全通訊協議,於是網際網路工程組 IETF 在 1999 年把它改名為 TLS(傳輸層安全,Transport Layer Security),正式標準化,版本號從 1.0 重新算起,所以 TLS1.0 實際上就是 SSLv3.1。

到今天 TLS 已經發展出了三個版本,分別是 2006 年的 1.1、2008 年的 1.2 和去年(2018)的 1.3,每個新版本都緊跟密碼學的發展和網際網路的現狀,持續強化安全和效能,已經成為了資訊保安領域中的權威標準。

目前應用的最廣泛的 TLS 是 1.2,而之前的協議(TLS1.1/1.0、SSLv3/v2)都已經被認為是不安全的,各大瀏覽器即將在 2020 年左右停止支援,所以接下來的講解都針對的是 TLS1.2。

TLS 由記錄協議、握手協議、警告協議、變更密碼規範協議、擴充套件協議等幾個子協議組成,綜合使用了對稱加密、非對稱加密、身份認證等許多密碼學前沿技術。

瀏覽器和伺服器在使用 TLS 建立連線時需要選擇一組恰當的加密演演算法來實現安全通訊,這些演演算法的組合被稱為密碼套件(cipher suite,也叫加密套件)。

TLS 的密碼套件命名非常規範,格式很固定。基本的形式是金鑰交換演演算法 + 簽名演演算法 + 對稱加密演演算法 + 摘要演演算法,比如剛才的密碼套件的意思就是:

握手時使用 ECDHE 演演算法進行金鑰交換,用 RSA 簽名和身份認證,握手後的通訊使用 AES 對稱演演算法,金鑰長度 256 位,分組模式是 GCM,摘要演演算法 SHA384 用於訊息認證和產生隨機數。

4. OpenSSL

說到 TLS,就不能不談到 OpenSSL,它是一個著名的開源密碼學程式庫和工具包,幾乎支援所有公開的加密演演算法和協議,已經成為了事實上的標準,許多應用軟體都會使用它作為底層庫來實現 TLS 功能,包括常用的 Web 伺服器 Apache、Nginx 等。

OpenSSL 是從另一個開源庫 SSLeay 發展出來的,曾經考慮命名為 OpenTLS,但當時(1998 年)TLS 還未正式確立,而 SSL 早已廣為人知,所以最終使用了 OpenSSL 的名字。OpenSSL 目前有三個主要的分支,1.0.2 和 1.1.0 都將在今年(2019)年底不再維護,最新的長期支援版本是 1.1.1。

由於 OpenSSL 是開源的,所以它還有一些程式碼分支,比如 Google 的 BoringSSL、OpenBSD 的 LibreSSL,這些分支在 OpenSSL 的基礎上刪除了一些老舊程式碼,也增加了一些新特性,雖然背後有 大金主,但離取代 OpenSSL 還差得很遠。

5. 機密性實現-加密

實現機密性最常用的手段是 加密(encrypt),就是把訊息用某種方式轉換成誰也看不懂的亂碼,只有掌握特殊鑰匙的人才能再轉換出原始文字。

這裡的鑰匙就叫做 金鑰(key),加密前的訊息叫明文(plain text/clear text),加密後的亂碼叫 密文(cipher text),使用金鑰還原明文的過程叫 解密(decrypt)。

所有的加密演演算法都是公開的,任何人都可以去分析研究,而演演算法使用的 金鑰 則必須保密。這個關鍵的 金鑰 又是什麼呢?由於 HTTPS、TLS 都執行在計算機上,所以 金鑰 就是一長串的數字,但約定俗成的度量單位是 (bit),而不是 位元組(byte)。比如,說金鑰長度是 128,就是 16 位元組的二進位制串,金鑰長度 1024,就是 128 位元組的二進位制串。

按照金鑰的使用方式,加密可以分為兩大類:對稱加密 和 非對稱加密

對稱加密

對稱加密 很好理解,就是指加密和解密時使用的金鑰都是同一個,是 對稱 的。只要保證了金鑰的安全,那整個通訊過程就可以說具有了機密性。

舉個例子,你想要登入某網站,只要事先和它約定好使用一個對稱密碼,通訊過程中傳輸的全是用金鑰加密後的密文,只有你和網站才能解密。黑客即使能夠竊聽,看到的也只是亂碼,因為沒有金鑰無法解出明文,所以就實現了機密性。

TLS 裡有非常多的對稱加密演演算法可供選擇,比如 RC4、DES、3DES、AES、ChaCha20 等,但前三種演演算法都被認為是不安全的,通常都禁止使用,目前常用的有 AES-128、AES-192、AES-256 和 ChaCha20。

DES 的全稱是 Data Encryption Standard(資料加密標準) ,它是用於數字資料加密的對稱金鑰演演算法。儘管其 56 位的短金鑰長度使它對於現代應用程式來說太不安全了,但它在加密技術的發展中具有很大的影響力。

AES 的意思是 高階加密標準(Advanced Encryption Standard),AES-128, AES-192 和 AES-256 都是屬於 AES 。金鑰長度可以是 128、192 或 256。它是 DES 演演算法的替代者,安全強度很高,效能也很好,而且有的硬體還會做特殊優化,所以非常流行,是應用最廣泛的對稱加密演演算法。

ChaCha20 是 Google 設計的另一種加密演演算法,金鑰長度固定為 256 位,純軟體執行效能要超過 AES,曾經在移動客戶端上比較流行,但 ARMv8 之後也加入了 AES 硬體優化,所以現在不再具有明顯的優勢。

分組模式

對稱演演算法還有一個 分組模式 的概念,它可以讓演演算法用固定長度的金鑰加密任意長度的明文,把小祕密(即金鑰)轉化為大祕密(即密文)。

最早有 ECB、CBC、CFB、OFB 等幾種分組模式,但都陸續被發現有安全漏洞,所以現在基本都不怎麼用了。最新的分組模式被稱為 AEAD(Authenticated Encryption with Associated Data),在加密的同時增加了認證的功能,常用的是 GCM、CCM 和 Poly1305。

把上面這些組合起來,就可以得到 TLS 密碼套件中定義的對稱加密演演算法。

比如,AES128-GCM,意思是金鑰長度為 128 位的 AES 演演算法,使用的分組模式是 GCM;ChaCha20-Poly1305 的意思是 ChaCha20 演演算法,使用的分組模式是 Poly1305。

非對稱加密

對稱加密看上去好像完美地實現了機密性,但其中有一個很大的問題:如何把金鑰安全地傳遞給對方,術語叫 金鑰交換。因為在對稱加密演演算法中只要持有金鑰就可以解密。如果你和網站約定的金鑰在傳遞途中被黑客竊取,那他就可以在之後隨意解密收發的資料,通訊過程也就沒有機密性可言了。

這個問題該怎麼解決呢?一般來說除非是雙方私下約定好了金鑰,如果是每次通訊都會發生變化的金鑰是不能在通訊過程中帶給對端,這樣你就陷入了要給金鑰加一次密的尷尬境地。所以,就出現了非對稱加密(也叫公鑰加密演演算法)。

它有兩個金鑰,一個叫 公鑰(public key),一個叫 私鑰(private key)。兩個金鑰是不同的,不對稱,公鑰可以公開給任何人使用,而私鑰必須嚴格保密。

公鑰和私鑰有個特別的 單向 性,雖然都可以用來加密解密,但公鑰加密後只能用私鑰解密,反過來,私鑰加密後也只能用公鑰解密。

非對稱加密可以解決 金鑰交換 的問題。網站祕密保管私鑰,在網上任意分發公鑰,你想要登入網站只要用公鑰加密就行了,密文只能由私鑰持有者才能解密。而黑客因為沒有私鑰,所以就無法破解密文。

非對稱加密演演算法的設計要比對稱演演算法難得多,在 TLS 裡只有很少的幾種,比如 DH、DSA、RSA、ECC 等。

RSA 可能是其中最著名的一個,幾乎可以說是非對稱加密的代名詞,它的安全性基於 整數分解 的數學難題,使用兩個超大素數的乘積作為生成金鑰的材料,想要從公鑰推算出私鑰是非常困難的。

10 年前 RSA 金鑰的推薦長度是 1024,但隨著計算機運算能力的提高,現在 1024 已經不安全,普遍認為至少要 2048 位。

ECC(Elliptic Curve Cryptography)是非對稱加密裡的 後起之秀,它基於 橢圓曲線離散對數 的數學難題,使用特定的曲線方程和基點生成公鑰和私鑰,子演演算法 ECDHE 用於金鑰交換,ECDSA 用於數字簽名。

ECDHE 即使用橢圓曲線(ECC)的 DH 演演算法,優點是能用較小的素數(256 位)實現 RSA 相同的安全等級。缺點是演演算法實現複雜,用於金鑰交換的歷史不長,沒有經過長時間的安全攻擊測試。

目前比較常用的兩個曲線是 P-256(secp256r1,在 OpenSSL 稱為 prime256v1)和 x25519。P-256 是 NIST(美國國家標準技術研究所)和 NSA(美國國家安全域性)推薦使用的曲線,而 x25519 被認為是最安全、最快速的曲線。

比起 RSA,ECC 在安全強度和效能上都有明顯的優勢。160 位的 ECC 相當於 1024 位的 RSA,而 224 位的 ECC 則相當於 2048 位的 RSA。因為金鑰短,所以相應的計算量、消耗的記憶體和頻寬也就少,加密解密的效能就上去了,對於現在的移動網際網路非常有吸引力。

混合加密

看到這裡你是不是認為可以拋棄對稱加密,只用非對稱加密來實現機密性呢?

很遺憾,雖然非對稱加密沒有 金鑰交換 的問題,但因為它們都是基於複雜的數學難題,運算速度很慢,即使是 ECC 也要比 AES 差上好幾個數量級。如果僅用非對稱加密,雖然保證了安全,但通訊速度有如烏龜、蝸牛,實用性就變成了零。

是不是能夠把對稱加密和非對稱加密結合起來呢,兩者互相取長補短,即能高效地加密解密,又能安全地金鑰交換。

這就是現在 TLS 裡使用的 混合加密 方式,其實說穿了也很簡單:

在通訊剛開始的時候使用非對稱演演算法,比如 RSA、ECDHE,首先解決金鑰交換的問題。

然後用隨機數產生對稱演演算法使用的 會話金鑰(session key),再用公鑰加密。因為會話金鑰很短,通常只有 16 位元組或 32 位元組,所以慢一點也無所謂。

對方拿到密文後用私鑰解密,取出會話金鑰。這樣,雙方就實現了對稱金鑰的安全交換,後續就不再使用非對稱加密,全都使用對稱加密。

這樣混合加密就解決了對稱加密演演算法的金鑰交換問題,而且安全和效能兼顧,完美地實現了機密性。

6. 完整性

摘要演演算法

實現完整性的手段主要是 摘要演演算法(Digest Algorithm),也就是常說的雜湊函式、雜湊函式(Hash Function)。

你可以把摘要演演算法近似地理解成一種特殊的壓縮演演算法,它能夠把任意長度的資料壓縮成固定長度、而且獨一無二的摘要字串,就好像是給這段資料生成了一個數字指紋。

換一個角度,也可以把摘要演演算法理解成特殊的單向加密演演算法,它只有演演算法,沒有金鑰,加密後的資料無法解密,不能從摘要逆推出原文。

摘要演演算法實際上是把資料從一個 大空間 對映到了 小空間,所以就存在 衝突(collision,也叫碰撞)的可能性,就如同現實中的指紋一樣,可能會有兩份不同的原文對應相同的摘要。好的摘要演演算法必須能夠 抵抗衝突,讓這種可能性儘量地小。

因為摘要演演算法對輸入具有 單向性雪崩效應,輸入的微小不同會導致輸出的劇烈變化,所以也被 TLS 用來生成偽隨機數(PRF,pseudo random function)。

你一定在日常工作中聽過、或者用過 MD5(Message-Digest 5)、SHA-1(Secure Hash Algorithm 1),它們就是最常用的兩個摘要演演算法,能夠生成 16 位元組和 20 位元組長度的數字摘要。但這兩個演演算法的安全強度比較低,不夠安全,在 TLS 裡已經被禁止使用了。

目前 TLS 推薦使用的是 SHA-1 的後繼者:SHA-2。

SHA-2 實際上是一系列摘要演演算法的統稱,總共有 6 種,常用的有 SHA224、SHA256、SHA384,分別能夠生成 28 位元組、32 位元組、48 位元組的摘要。

摘要演演算法保證了 數字摘要 和原文是完全等價的。所以,我們只要在原文後附上它的摘要,就能夠保證資料的完整性。

比如,你發了條訊息:有內鬼,終止交易,然後再加上一個 SHA-2 的摘要。網站收到後也計算一下訊息的摘要,把這兩份 指紋 做個對比,如果一致,就說明訊息是完整可信的,沒有被修改。

如果黑客在中間哪怕改動了一個標點符號,摘要也會完全不同,網站計算比對就會發現訊息被竄改,是不可信的。

不過摘要演演算法不具有機密性,如果明文傳輸,那麼黑客可以修改訊息後把摘要也一起改了,網站還是鑑別不出完整性。

所以,真正的完整性必須要建立在機密性之上,在混合加密系統裡用會話金鑰加密訊息和摘要,這樣黑客無法得知明文,也就沒有辦法動手腳了。這有個術語,叫雜湊訊息認證碼(HMAC)。

數字簽名

加密演演算法結合摘要演演算法,我們的通訊過程可以說是比較安全了。但這裡還有漏洞,就是通訊的兩個端點(endpoint)。

就像一開始所說的,黑客可以偽裝成網站來竊取資訊。而反過來,他也可以偽裝成你,向網站傳送支付、轉賬等訊息,網站沒有辦法確認你的身份,錢可能就這麼被偷走了。

現實生活中,解決身份認證的手段是簽名和印章,只要在紙上寫下簽名或者蓋個章,就能夠證明這份檔案確實是由本人而不是其他人發出的。

回想一下上面的介紹在 TLS 裡有什麼東西和現實中的簽名、印章很像,只能由本人持有,而其他任何人都不會有呢?只要用這個東西,就能夠在數字世界裡證明你的身份。

沒錯,這個東西就是非對稱加密裡的 私鑰,使用私鑰再加上摘要演演算法,就能夠實現 數字簽名,同時實現 身份認證不可否認

數字簽名的原理其實很簡單,就是把公鑰私鑰的用法反過來,之前是公鑰加密、私鑰解密,現在是私鑰加密、公鑰解密。

但又因為非對稱加密效率太低,所以私鑰只加密原文的摘要,這樣運算量就小的多,而且得到的數字簽名也很小,方便保管和傳輸。

簽名和公鑰一樣完全公開,任何人都可以獲取。但這個簽名只有用私鑰對應的公鑰才能解開,拿到摘要後,再比對原文驗證完整性,就可以像簽署檔案一樣證明訊息確實是你發的。

剛才的這兩個行為也有專用術語,叫做 簽名驗籤

只要你和網站互相交換公鑰,就可以用 簽名驗籤 來確認訊息的真實性,因為私鑰保密,黑客不能偽造簽名,就能夠保證通訊雙方的身份。

比如,你用自己的私鑰簽名一個訊息 馬冬梅你別跑。網站收到後用你的公鑰驗籤,確認身份沒問題,於是也用它的私鑰簽名訊息 十年翻身同學會,拆散一對是一對。你收到後再用它的公鑰驗一下,也沒問題,這樣你和網站就都知道對方不是假冒的,後面就可以用混合加密進行安全通訊了。

有關加解密相關的演演算法,可以看我另一篇博文:

加解密演演算法淺析

可以幫助大家對加解密有一個統一的認識。

7. 數字證書和 CA--身份認證

到現在,綜合使用對稱加密、非對稱加密和摘要演演算法,我們已經實現了安全的四大特性,是不是已經完美了呢?

不是的,這裡還有一個 公鑰的信任 問題。因為誰都可以釋出公鑰,我們還缺少防止黑客偽造公鑰的手段,也就是說,怎麼來判斷這個公鑰就是你的公鑰呢?

我們可以用類似金鑰交換的方法來解決公鑰認證問題,用別的私鑰來給公鑰簽名,顯然,這又會陷入俄羅斯套娃

但這次實在是沒招了,要終結這個死迴圈,就必須引入外力,找一個公認的可信第三方,讓它作為信任的起點,遞迴的終點,構建起公鑰的信任鏈。

這個第三方就是我們常說的 CA(Certificate Authority,證書認證機構)。它就像網路世界裡的公安局、教育部、公證中心,具有極高的可信度,由它來給各個公鑰簽名,用自身的信譽來保證公鑰無法偽造,是可信的。

CA 對公鑰的簽名認證也是有格式的,不是簡單地把公鑰繫結在持有者身份上就完事了,還要包含序列號、用途、頒發者、有效時間等等,把這些打成一個包再簽名,完整地證明公鑰關聯的各種資訊,形成 數字證書(Certificate)。

知名的 CA 全世界就那麼幾家,比如 DigiCert、VeriSign、Entrust、Let’s Encrypt 等,它們簽發的證書分 DV、OV、EV 三種,區別在於可信程度。

DV 是最低的,只是域名級別的可信,背後是誰不知道。EV 是最高的,經過了法律和審計的嚴格核查,可以證明網站擁有者的身份(在瀏覽器位址列會顯示出公司的名字,例如 Apple、GitHub 的網站)。

不過,CA 怎麼證明自己呢?

這還是信任鏈的問題。小一點的 CA 可以讓大 CA 簽名認證,但鏈條的最後,也就是Root CA,就只能自己證明自己了,這個就叫 自簽名證書(Self-Signed Certificate)或者 根證書(Root Certificate)。你必須相信,否則整個證書信任鏈就走不下去了。

有了這個證書體系,作業系統和瀏覽器都內建了各大 CA 的根證書,上網的時候只要伺服器發過來它的證書,就可以驗證證書裡的簽名,順著證書鏈(Certificate Chain)一層層地驗證,直到找到根證書,就能夠確定證書是可信的,從而裡面的公鑰也是可信的。

證書體系的弱點

證書體系(PKI,Public Key Infrastructure)雖然是目前整個網路世界的安全基礎設施,但絕對的安全是不存在的,它也有弱點,還是關鍵的 信任 二字。

如果 CA 失誤或者被欺騙,簽發了錯誤的證書,雖然證書是真的,可它代表的網站卻是假的。

還有一種更危險的情況,CA 被黑客攻陷,或者 CA 有惡意,因為它(即根證書)是信任的源頭,整個信任鏈裡的所有證書也就都不可信了。

這兩種事情並不是聳人聽聞,都曾經實際出現過。所以需要再給證書體系打上一些補丁。

針對第一種,開發出了 CRL(證書吊銷列表,Certificate revocation list)和 OCSP(線上證書狀態協議,Online Certificate Status Protocol),及時廢止有問題的證書。

對於第二種因為涉及的證書太多,就只能作業系統或者瀏覽器從根上下狠手了,撤銷對 CA 的信任,列入黑名單,這樣它頒發的所有證書就都會被認為是不安全的。

我們來看一下Github的數字證書長什麼樣子:

證書上的資訊可以得知:型別是 EV,擁有最高權威認證;過期時間;證書所屬組織;證書籤發機構。

8. TLS 協議的組成

當你在瀏覽器位址列裡鍵入 https 開頭的 URI,再按下回車,會發生什麼呢?

瀏覽器首先要從 URI 裡提取出協議名和域名。因為協議名是https,所以瀏覽器就知道了埠號是預設的 443,它再用 DNS 解析域名,得到目標的 IP 地址,然後就可以使用三次握手與網站建立 TCP 連線了。

在 HTTP 協議裡,建立連線後,瀏覽器會立即傳送請求報文。但現在是 HTTPS 協議,它需要再用另外一個握手過程,在 TCP 上建立安全連線,之後才是收發 HTTP 報文。

這個握手過程與 TCP 有些類似,是 HTTPS 和 TLS 協議裡最重要、最核心的部分。

在講 TLS 握手之前,先簡單介紹一下 TLS 協議的組成。

TLS 包含幾個子協議,你也可以理解為它是由幾個不同職責的模組組成,比較常用的有:記錄協議、警報協議、握手協議、變更密碼規範協議等。

  • 記錄協議(Record Protocol)規定了 TLS 收發資料的基本單位:記錄(record)。它有點像是 TCP 裡的 segment,所有的其他子協議都需要通過記錄協議發出。但多個記錄資料可以在一個 TCP 包裡一次性發出,也並不需要像 TCP 那樣返回 ACK。
  • 警報協議(Alert Protocol)的職責是向對方發出警報資訊,有點像是 HTTP 協議裡的狀態碼。比如,protocol_version 就是不支援舊版本,bad_certificate 就是證書有問題,收到警報後另一方可以選擇繼續,也可以立即終止連線。
  • 握手協議(Handshake Protocol)是 TLS 裡最複雜的子協議,要比 TCP 的 SYN/ACK 複雜的多,瀏覽器和伺服器會在握手過程中協商 TLS 版本號、隨機數、密碼套件等資訊,然後交換證書和金鑰引數,最終雙方協商得到會話金鑰,用於後續的混合加密系統。
  • 變更密碼規範協議(Change Cipher Spec Protocol),它非常簡單,就是一個通知,告訴對方,後續的資料都將使用加密保護。那麼反過來,在它之前,資料都是明文的。

下面的這張圖簡要地描述了 TLS 的握手過程,其中每一個都是一個記錄,多個記錄組合成一個 TCP 包傳送。所以,最多經過兩次訊息往返(4 個訊息)就可以完成握手,然後就可以在安全的通訊環境裡傳送 HTTP 報文,實現 HTTPS 協議。

在 TCP 完成三次握手建立連線之後, HTTPS 開始加密認證握手流程。 TLS 的握手過程如下:

以上過程我們可以使用 wireShark 抓包工具看到。

在 TCP 建立連線之後,瀏覽器會首先發一個 client_hello 訊息,裡面有客戶端的版本號、支援的密碼套件,還有一個隨機數(Client Random),用於後續生成會話金鑰:

可以看到客戶端傳送給服務端他所支援的密碼套件有 16 套之多,另外客戶端使用的 TLS 版本是1.2。伺服器收到 Client Hello 後,會返回一個 Server Hello 訊息。把版本號對一下,也給出一個隨機數(Server Random),然後從客戶端的列表裡選一個作為本次通訊使用的密碼套件,在這裡它選擇了Cipher Suite: TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (0xc02f)

然後伺服器為了證明自己的身份,就把證書也發給了客戶端(Server Certificate)。

接下來是一個關鍵的操作,因為伺服器選擇了 ECDHE 演演算法,所以它會在證書後傳送 Server Key Exchange 訊息,裡面是 橢圓曲線的公鑰(Server Params),用來實現金鑰交換演演算法,再加上自己的私鑰簽名認證。

Handshake Protocol: Server Key Exchange
EC Diffie-Hellman Server Params
Curve Type: named_curve (0x03)
Named Curve: x25519 (0x001d)
Pubkey: 3b39deaf00217894e...
Signature Algorithm: rsa_pkcs1_sha512 (0x0601)
Signature: 37141adac38ea4...

這相當於說:剛才我選的密碼套件有點複雜,所以再給你個演演算法的引數,和剛才的隨機數一樣有用別丟了。為了防止別人冒充,我又蓋了個章。

之後是 Server Hello Done 訊息,伺服器說:我的資訊就是這些,打招呼完畢。

這樣第一個訊息往返就結束了(兩個 TCP 包),結果是客戶端和伺服器通過明文共享了三個資訊:Client Random、Server Random 和 Server Params

客戶端這時也拿到了伺服器的證書,那這個證書是不是真實有效的呢?下面客戶端就開始鑑定證書的真偽。校驗過程如下:

  1. 首先讀取證書所有者有效期等資訊進行校驗,查詢內建的收信人證書釋出機構 CA 與證書 CA 相對比,校驗是否是合法機構頒發;這一步會做如下操作:

    1. 證書鏈的可信性 (trusted certificate path)校驗,發行伺服器證書的 CA 是否可靠;
    2. 證書是否吊銷 (revocation),有兩類方式離線 CRL 與線上 OCSP,不同的客戶端行為會不同;
    3. 有效期 (expiry date),證書是否在有效時間範圍;
    4. 域名 (domain),核查證書域名是否與當前的訪問域名匹配,匹配規則後續分析。
  2. 第一步檢驗通過之後產生隨機數 Pre-master, 並用證書公鑰加密,傳送給伺服器,作為以後對稱加密的金鑰。

客戶端向伺服器傳送 Client Key Exchange。最後客戶端與伺服器互發 Change Cipher Spec,Encrypted Handshake Message

接下來伺服器接收到客戶端傳送的 Pre-master,解密出被加密的 Pre-master,然後通知客戶端:

握手階段結束,隨後所有的通訊將使用對稱加密的方式進行。

9. 雙向認證

上面已經講完了 TLS 握手,從上面的流程不難看出只是客戶端認證了伺服器的身份,而伺服器是沒有認證客戶端身份的,我們簡稱 單向認證。通常單向認證通過後已經建立了安全通訊,用賬號、密碼等簡單的手段就能夠確認使用者的真實身份。但為了防止賬號、密碼被盜,有的時候(比如網上銀行)還會使用 U 盾給使用者頒發客戶端證書,實現雙向認證,這樣會更加安全。

雙向認證的流程也沒有太多變化,只是在 Server Hello Done 之後,Client Key Exchange 之前,客戶端要傳送 Client Certificate 訊息,伺服器收到後也把證書鏈走一遍,驗證客戶端的身份。

不過 TLS1.2 已經是 10 年前(2008 年)的協議了,雖然歷經考驗但畢竟 歲月不饒人,在安全、效能等方面已經跟不上如今的網際網路。經過四年近 30 個草案的反覆打磨,TLS1.3 在2018 年推出,再次確立了資訊保安領域的新標準。

10. 最大化相容性

由於 1.1、1.2 等協議已經出現了很多年,很多應用軟體、中間代理(官方稱為MiddleBox)只認老的記錄協議格式,更新改造很困難,甚至是不可行(裝置僵化)。

在早期的試驗中發現,一旦變更了記錄頭欄位裡的版本號,也就是由 0x303(TLS1.2)改為 0x304(TLS1.3)的話,大量的代理伺服器、閘道器都無法正確處理,最終導致 TLS 握手失敗。

為了保證這些被廣泛部署的老裝置能夠繼續使用,避免新協議帶來的衝擊,TLS1.3 不得不做出妥協,保持現有的記錄格式不變,通過偽裝來實現相容,使得 TLS1.3 看上去像是 TLS1.2。

那麼,該怎麼區分 1.2 和 1.3 呢?

這要用到一個新的 擴充套件協議(Extension Protocol),它有點 補充條款 的意思,通過在記錄末尾新增一系列的 擴充套件欄位 來增加新的功能,老版本的 TLS 不認識它可以直接忽略,這就實現了後向相容。

在記錄頭的 Version 欄位被相容性固定的情況下,只要是 TLS1.3 協議,握手的 Hello 訊息後面就必須有 supported_versions 擴充套件,它標記了 TLS 的版本號,使用它就能區分新舊協議。

你可以看一下Client Hello訊息後面的擴充套件,只是因為伺服器不支援 1.3,所以就後向相容降級成了 1.2。

TLS1.3 利用擴充套件實現了許多重要的功能,比如supported_groupskey_sharesignature_algorithmsserver_name等,這些等後面用到的時候再說。

11. 強化安全

TLS1.2 在十來年的應用中獲得了許多寶貴的經驗,陸續發現了很多的漏洞和加密演演算法的弱點,所以 TLS1.3 就在協議裡修補了這些不安全因素。

比如:

  • 偽隨機數函式由 PRF 升級為 HKDF(HMAC-based Extract-and-Expand Key Derivation Function);
  • 明確禁止在記錄協議裡使用壓縮;
  • 廢除了 RC4、DES 對稱加密演演算法;
  • 廢除了 ECB、CBC 等傳統分組模式;
  • 廢除了 MD5、SHA1、SHA-224 摘要演演算法;
  • 廢除了 RSA、DH 金鑰交換演演算法和許多命名曲線。

經過這一番減肥瘦身之後,TLS1.3 裡只保留了 AES、ChaCha20 對稱加密演演算法,分組模式只能用 AEAD 的 GCM、CCM 和 Poly1305,摘要演演算法只能用 SHA256、SHA384,金鑰交換演演算法只有 ECDHE 和 DHE,橢圓曲線也被到只剩 P-256 和 x25519 等 5 種。

演演算法精簡後帶來了一個意料之中的好處:原來眾多的演演算法、引陣列合導緻密碼套件非常複雜,難以選擇,而現在的 TLS1.3 裡只有 5 個套件,無論是客戶端還是伺服器都不會再犯選擇困難症了。

密碼套件名稱 程式碼
Cipher Suite: TLS_AES_128_GCM_SHA256 0x1301
Cipher Suite: TLS_AES_256_GCM_SHA384 0x1302
Cipher Suite: TLS_CHACHA20_POLY1305_SHA256 0x1303
TLS-AES128-CCM-SHA256 0x1304
TLS-AES128-CCM-8-SHA256 0x1305

這裡還要特別說一下廢除 RSA 和 DH 金鑰交換演演算法的原因。

在 RSA 金鑰交換中,共享金鑰由客戶端生成,然後客戶端利用伺服器的公鑰(從證書中提取)將共享金鑰加密並將其傳送到伺服器。

DH 演演算法由 DiffieHellman 於1976年發明,即所謂的 Diffie-Hellman 金鑰交換。在 Diffie-Hellman 中,客戶端和伺服器都從建立 DH 引數對開始。然後,他們將其 DH 引數的公共部分傳送給另一方。當雙方都收到對方方的公共引數時,它們將它與自己的私鑰組合在一起,最終計算出同一個值:前主金鑰。然後,伺服器使用數字簽名來確保交換未被篡改。如果客戶端和伺服器都為每次金鑰交換選擇一個新的 DH 引數,則該金鑰交換稱為 “Ephemeral”(DHE)。

DH 是一個功能強大的工具,但並非所有DH引數都可以 “安全” 使用。DH 的安全性取決於稱為數學中離散對數問題的難度。如果可以解決一組引數的離散對數問題,就可以提取私鑰並破壞協議的安全性。一般來說,使用的數字越大,解決離散對數問題就越困難。因此,如果您選擇較小的DH引數,就有可能遭受攻擊。

上兩種模式都會使客戶端和伺服器具有共享金鑰,但 RSA 模式有一個嚴重的缺點,這是因為它不具有 前向安全(Forward Secrecy)。

假設有這麼一個很有耐心的黑客,一直在長期收集混合加密系統收發的所有報文。如果加密系統使用伺服器證書裡的 RSA 做金鑰交換,一旦私鑰洩露或被破解(使用社會工程學或者巨型計算機),那麼黑客就能夠使用私鑰解密出之前所有報文的 Pre-Master,再算出會話金鑰,破解所有密文。

而 ECDHE 演演算法在每次握手時都會生成一對臨時的公鑰和私鑰,每次通訊的金鑰對都是不同的,也就是 一次一密,即使黑客花大力氣破解了這一次的會話金鑰,也只是這次通訊被攻擊,之前的歷史訊息不會受到影響,仍然是安全的。

所以現在主流的伺服器和瀏覽器在握手階段都已經不再使用 RSA,改用 ECDHE,而 TLS1.3 在協議裡明確廢除 RSA 和 DH 則在標準層面保證了前向安全。

RSA 金鑰交換在一段時間內一直存在問題,其原因不僅僅是因為它支援前向保密。而是因為想要正確的實現 RSA 金鑰交換也是不容易的。1998年,Daniel Bleichenbacher 在 SSL 中使用 RSA 加密時發現了一個漏洞並建立了所謂的 “百萬訊息攻擊”, 它允許攻擊者通過傳送數百萬條訊息或一些特定的訊息給伺服器,根據伺服器響應的不同錯誤碼計算加密金鑰, 進而解密訊息。多年來,這種攻擊得到了改進,在某些情況下只需要數千次就可破解,這使得在膝上型電腦上都可以破解。最近發現,許多大型網站(包括facebook.com)在2017年也受到 Bleichenbacher 變種漏洞的影響,即ROBOT攻擊

為了降低非前向加密連線和 Bleichenbacher 漏洞所帶來的風險,RSA 加密已從 TLS 1.3 中刪除,將 Diffie-Hellman Ephemeral 作為唯一的金鑰交換機制。

12. 提升效能

HTTPS 建立連線時除了要做 TCP 握手,還要做 TLS 握手,在 1.2 中會多花兩個訊息往返(2-RTT),可能導致幾十毫秒甚至上百毫秒的延遲,在行動網路中延遲還會更嚴重。

現在因為密碼套件大幅度簡化,也就沒有必要再像以前那樣走複雜的協商流程了。TLS1.3 壓縮了以前的 Hello 協商過程,刪除了 Key Exchange 訊息,把握手時間減少到了 1-RTT,效率提高了一倍。

那麼它是怎麼做的呢?其實具體的做法還是利用了擴充套件。客戶端在 Client Hello 訊息裡直接用 supported_groups 帶上支援的曲線,比如 P-256、x25519,用 key_share帶上曲線對應的客戶端公鑰引數,用signature_algorithms帶上簽名演演算法。

伺服器收到後在這些擴充套件裡選定一個曲線和引數,再用key_share 擴充套件返回伺服器這邊的公鑰引數,就實現了雙方的金鑰交換,後面的流程就和 1.2 基本一樣了。

除了標準的 1-RTT 握手,TLS1.3 還引入了0-RTT 握手,用 pre_shared_keyearly_data 擴充套件,在 TCP 連線後立即就建立安全連線傳送加密訊息,不過這需要有一些前提條件,限於篇幅這裡暫且不表。

13. HTTPS使用成本

HTTPS 截止到目前為止國內的大中型企業基本都支援並已經使用。一般來講,使用 HTTPS 前大家可能會非常關注如下問題:

  1. 證書費用以及更新維護。費用主要是證書的申請,而且現在也有了免費的證書機構,比如著名的 mozilla 發起的免費證書專案:let’s encrypt 就支援免費證書安裝和自動更新。
  2. HTTPS 降低使用者訪問速度。HTTPS 對速度會有一定程度的降低,但是隻要經過合理優化和部署,HTTPS 對速度的影響完全可以接受。在很多場景下,HTTPS 速度完全不遜於 HTTP,如果使用 SPDY,HTTPS 的速度甚至還要比 HTTP 快。