HTTP 協議僅僅制定了互聯網傳輸的標準,簡化了直接使用 TCP 協議進行通信的難度。

圖片來自 Pexels

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

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

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

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

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

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

③不驗證發送方身份,可能遭遇僞裝

HTTP 協議中的請求和響應不會對通信方進行確認。在 HTTP 協議通信時,由於不存在確認通信方的處理步驟,任何人都可以發起請求。

另外,服務器只要接收到請求,不管對方是誰都會返回一個響應(但也僅限於發送端的 IP 地址和端口號沒有被 Web 服務器設定限制訪問的前提下)。

HTTP 協議無法驗證通信方身份,任何人都可以僞造虛假服務器欺騙用戶,實現釣魚欺詐,用戶無法察覺。

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

什麼是安全

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

通常認爲,如果通信過程具備了四個特性,就可以認爲是安全的:

  • 機密性(Secrecy/Confidentiality):是指對數據的保密,只能由可信的人訪問,對其他人是不可見的祕密,簡單來說就是不能讓不相關的人看到不該看的東西。
  • 完整性(Integrity,也叫一致性):是指數據在傳輸過程中沒有被竄改,不多也不少,完完整整地保持着原狀。
  • 身份認證(Authentication):是指確認對方的真實身份,也就是證明你真的是你,保證消息只能發送給可信的人。如果通信時另一方是假冒的網站,那麼數據再保密也沒有用,黑客完全可以使用冒充的身份套出各種信息,加密和沒加密一樣。
  • 不可否認(Non-repudiation/Undeniable),也叫不可抵賴,意思是不能否認已經發生過的行爲,不能說話不算數,耍賴皮。

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

比如,小明借了小紅一千元,沒寫借條,第二天矢口否認,小紅也確實拿不出借錢的證據,只能認倒黴。

另一種情況是小明借錢後還了小紅,但沒寫收條,小紅於是不承認小明還錢的事,說根本沒還,要小明再掏出一千元。

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

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 的功能實現主要依賴於三類基本算法:散列函數 、對稱加密和非對稱加密,利用非對稱加密實現身份認證和密鑰協商,對稱加密算法採用協商的密鑰對數據加密,基於散列函數驗證信息的完整性。

什麼是 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 用於消息認證和產生隨機數。

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 還差得很遠。

機密性實現:加密

實現機密性最常用的手段是加密(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 字節,所以慢一點也無所謂。

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

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

完整性

摘要算法

實現完整性的手段主要是摘要算法(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 裏有什麼東西和現實中的簽名、印章很像,只能由本人持有,而其他任何人都不會有呢?只要用這個東西,就能夠在數字世界裏證明你的身份。

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

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

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

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

剛纔的這兩個行爲也有專用術語,叫做簽名和驗籤。

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

比如,你用自己的私鑰簽名一個消息馬冬梅你別跑。網站收到後用你的公鑰驗籤,確認身份沒問題,於是也用它的私鑰簽名消息十年翻身同學會,拆散一對是一對。

你收到後再用它的公鑰驗一下,也沒問題,這樣你和網站就都知道對方不是假冒的,後面就可以用混合加密進行安全通信了。

數字證書和 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,擁有最高權威認證;過期時間;證書所屬組織;證書籤發機構。

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。

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

①首先讀取證書所有者有效期等信息進行校驗,查找內置的收信人證書發佈機構 CA 與證書 CA 相對比,校驗是否是合法機構頒發。

這一步會做如下操作:

  • 證書鏈的可信性 (trusted certificate path)校驗,發行服務器證書的 CA 是否可靠。
  • 證書是否吊銷 (revocation),有兩類方式離線 CRL 與在線 OCSP,不同的客戶端行爲會不同。
  • 有效期 (expiry date),證書是否在有效時間範圍。
  • 域名 (domain),覈查證書域名是否與當前的訪問域名匹配,匹配規則後續分析。

②第一步檢驗通過之後產生隨機數 Pre-master,並用證書公鑰加密,發送給服務器,作爲以後對稱加密的密鑰。

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

接下來服務器接收到客戶端發送的 Pre-master,解密出被加密的 Pre-master,然後通知客戶端:握手階段結束,隨後所有的通信將使用對稱加密的方式進行。

雙向認證

上面已經講完了 TLS 握手,從上面的流程不難看出只是客戶端認證了服務器的身份,而服務器是沒有認證客戶端身份的,我們簡稱單向認證。

通常單向認證通過後已經建立了安全通信,用賬號、密碼等簡單的手段就能夠確認用戶的真實身份。

但爲了防止賬號、密碼被盜,有的時候(比如網上銀行)還會使用 U 盾給用戶頒發客戶端證書,實現雙向認證,這樣會更加安全。

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

不過 TLS1.2 已經是 10 年前(2008 年)的老協議了,雖然歷經考驗但畢竟 歲月不饒人,在安全、性能等方面已經跟不上如今的互聯網。

經過四年近 30 個草案的反覆打磨,TLS1.3 在 2018 年推出,再次確立了信息安全領域的新標準。

最大化兼容性

由於 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_groups、key_share、signature_algorithms、server_name等,這些等後面用到的時候再說。

強化安全

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 個套件,無論是客戶端還是服務器都不會再犯選擇困難症了。

這裏還要特別說一下廢除 RSA 和 DH 密鑰交換算法的原因。在 RSA 密鑰交換中,共享密鑰由客戶端生成,然後客戶端利用服務器的公鑰(從證書中提取)將共享密鑰加密並將其發送到服務器。

DH 算法由 Diffie 和 Hellman 於 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 作爲唯一的密鑰交換機制。

提升性能

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_key 和 early_data 擴展,在 TCP 連接後立即就建立安全連接發送加密消息,不過這需要有一些前提條件,限於篇幅這裏暫且不講解。

HTTPS 使用成本

HTTPS 截止到目前爲止國內的大中型企業基本都支持並已經使用。

一般來講,使用 HTTPS 前大家可能會非常關注如下問題:

  • 證書費用以及更新維護:費用主要是證書的申請,而且現在也有了免費的證書機構,比如著名的 mozilla 發起的免費證書項目:let’s encrypt 就支持免費證書安裝和自動更新。
  • HTTPS 降低用戶訪問速度:HTTPS 對速度會有一定程度的降低,但是隻要經過合理優化和部署,HTTPS 對速度的影響完全可以接受。

在很多場景下,HTTPS 速度完全不遜於 HTTP,如果使用 SPDY,HTTPS 的速度甚至還要比 HTTP 快。

作者:rickiyang

編輯:陶家龍

出處:轉載自微信公衆號 rickiyang

相關文章