知乎都全站 HTTPS 好久了, 你還好意思不懂 HTTPS?
買證書啊
中國的一個叫 WoSign 的 CA 之前給一個 schrauger.github.com 生成了一個 *.github.com 的 SSL,然後還有一個月內生成了幾百個相同序列號的證書。Firefox 腦袋一拍,決定從 2016 年 10月份把它從信任 CA 列表中除名。對 WoSign 感興趣?這裡:CA:WoSign Issues,你也可以點上面那個 github 的地址,第一篇就是:The story of how WoSign gave me an SSL certificate for GitHub.com。
所以你需要考慮到底是用國內的還是國外的。就安全性來說當然是 GeoTrust 是最靠譜的,但是貴呀。如果你的企業有一定的背景,說不定還不讓用國外的產品。So... 如果你網管不小心配錯了證書,但是你又開了 HTTPS 的埠,會這樣:證書夠裝逼才行
說到證書,你會發現它有很多種類型的,最裝逼的當然是 twitter 這樣的啦:
這類證書叫做:EV 證書。如果你公司名字比較長可能會比較蛋疼,譬如會變成這樣:
在最新版的 Chrome 下已經不顯示名字了,只有 Safari 才顯示。Chrome 下和普通證書顯示一樣,只有一個綠色的鎖。
這個證書麻煩在於不支持通配,譬如只能 「twitter.com」 或者 「http://www.twitter.com」 ,而不能 「*.twitter.com」,維護成本特別高。
所以大家一般都會選擇支持 「*.twitter.com」 的 wildcard 證書。或者同時支持 「taobao.com」 和 「alipay.com」 這樣的 SAN 證書。
上全量 CDN 的 HTTPS 啊
買證書的錢其實還好,服務端的加密解密消耗的伺服器其實也還好。最大的頭其實來自 CDN,動態內容上了 HTTPS,所有靜態內容也要跟著上,都是錢啊貴著呢!去看看 HTTPS 和 HTTP 的價格,差好幾倍啊。
不然你的頁面會變成這樣,這特么不是和 HTTP 一毛一樣么!甚至還變差了!
SNI 支持
默認情況下,在客戶端和服務端建立好連接之後。服務端會把默認的 SSL 證書發過去給客戶做 SSL 校驗。但是隨著伺服器性能逐漸變強,一台伺服器會部署多個 SSL 證書服務多個站點(CDN 是最典型的)。服務端需要客戶端先告知正準備訪問的網站域名(Header 中 Host 欄位),然後服務端才能發送正確的 SSL 證書信息和客戶端來 SSL 握手。
如果在上之前沒有調研過這個東西,上線之後可能會收到一堆 Windows XP 的用戶的反饋。國內不乏有大量古董級用戶,用著 windows XP + IE6。如果你上了 HTTPS 他們將永遠上不去你的網站了。因為 Windows XP 不支持 SNI。如果你只有一個證書,同時只有一個入口,那麼你的動態請求就不會有問題。但是像 CDN 這類第三方服務,人家不可能只為你服務(好吧只要你有錢你也是可以的)。SSL 加解密
軟體扛 HTTPS 流量現在主要用 HAProxy 或者 Nginx。如果配置正確加上合理的優化,一台 24 核的 HAProxy 是可以輕鬆扛下 10,000 qps 的量。
關於 SSL 的版本,有:SSL 1.0、SSL 2.0、SSL 3.0、 TLS 1.0、TLS 1.1、TLS 1.2。前面三個協議因為發現有漏洞,已經廢棄了。HAProxy 和 Nginx 的 SSL Protocol 默認都是 TLS 的三個。------------好了文章終於可以進入正文了!說到 HTTPS,涉及 2 個概念。- 怎麼保證我和真實服務端的通訊是安全的。
- 怎麼證明我的拿到的請求是真實服務端發我的。
HTTPS 怎麼保證通訊安全
怎麼保證我和真實服務端的通訊是安全的呢?這個問題就好像你和你女朋友想聊一些很羞羞的話題不希望別人知道。其實很簡單。這個時候就需要用到非對稱加密演算法了,簡單來說就是一個公鑰和一個私鑰:
- 公鑰加密的內容私鑰可以解密
- 私鑰解密的內容公鑰可以解密
- 公鑰是公開給每個人的,私鑰是只有服務端自己持有
你拿著私鑰,然後把公鑰遞給你的女朋友。你們每次發送之前都做一次加密就完事了。
但是你會想到另外一個問題:怎麼證明和我聊天的就是我的女朋友呢,或者說你的女朋友怎麼知道正在聊天的那個就是你呢?說不定在第一次遞交公鑰的時候就已經被人冒充拿走了!這怎麼行!這就是第二個問題:怎麼證明我的拿到的請求是真實服務端發我的。聰明的你想到一個辦法,你找了一個第三方公正。你和公正說:公正,你也生成一對密鑰吧。然後你用你的私鑰把我的公鑰給加密了。然後你把這個被公正加密過的公鑰給了你女朋友,你女朋友會嘗試用公正的公鑰(公鑰都是公開的)去解密,發現解密成功,說明這個公鑰真的是你的。就可以安心的聊羞羞話題了。所以流程是:服務端(你自己)生成一對密鑰,找 CA (公正)讓它用私鑰把你的公鑰加密,然後客戶(你女朋友)在訪問你的時候嘗試用 CA 的公鑰解密你的公鑰,解密成功表示你是可信任的。聰明的你又想到一個問題,如果 CA 的公鑰也被冒充了怎麼辦?感覺進入死循環了。所以,所有瀏覽器都會內置一系列 CA 公鑰。這樣就完全隔離了網路了。但是這樣也有缺陷,會導致更新 CA 不及時,證書不能實時撤銷的問題。而我們經常說到的 GeoTrust,以及國內被黑的很慘的 WoSign 就是 CA 機構。同樣,一旦他們的私鑰泄漏了,這就意味著這家公司可以做破產清算了,同時很多互聯網公司都可能會出現數據泄漏或者賬號被竊的風險。HTTPS 通訊到底用了多少種演算法
非對稱加密演算法無非是互聯網安全通信最重要的協議之一,奠定了整個互聯網安全架構。但是 RSA 演算法本身計算量特別大,如果把所有數據都通過 RSA 加密解密會特別消耗計算能力,所以 HTTPS 並不是一直都是用非對稱演算法的:
- HTTPS 在做 SSL 握手的時候是非對稱加密
- 握手成功之後使用對稱加密傳輸數據
- 同時還用到 Hash 演算法校驗數據的一致性
所以前面的舉例其實並不妥當,應該是你女朋友和你握手完了之後協商了一個對稱密鑰,然後用這個對稱密鑰加密通訊的。
HTTPS 握手流程
所有鋪墊都已說明,那麼,我們來說 SSL 到底是怎麼建立的,我們現在只討論基於 RSA 的加密方式。
所有的包都被封裝為 Record Layer 結構體裡面,可以是一個,也可以是多個,這樣可以減少包的數量,裡面包括:
- Content Type:表示這是什麼類型的包,握手階段都是 Handshake
- Version:表示是用什麼 SSL 協議,較新的瀏覽器都會使用 TLS 1.2
- Length:包的內容的長度,指的是 Fragment 的長度,不是整個包的長度。
- Fragment:和包的類型相關,譬如在握手階段就是 Handshake Protocol 的結構體,連接連接後的傳輸過程,這裡就是 Encrypted Application Data 的結構體。
在客戶端和服務端完成了 TCP 握手建立了連接之後,客戶端會發送 Client Hello 包,其中包括 4 個重要的欄位需要說明一下:
- Random:由時間戳和 28 bytes 的隨機數組成,將用於後面生成 Master Secret。
- SessionID:用來標記是否復用之前的連接。
- Cipher Suites:用於告知客戶端自己支持的 Cipher Suites。可以在伺服器上運行
openssl ciphers -V
查看自己電腦支持的 Cipher Suites。
譬如
0xC0,0x30 - ECDHE-RSA-AES256-GCM-SHA384 TLSv1.2 Kx=ECDH Au=RSA Enc=AESGCM(256) Mac=AEAD
表示用 RSA 演算法驗證證書,用 AES256 加密數據,用 SHA384 驗證數據一致性。
- Server Name(擴展):告知服務端正在訪問的是什麼域名,因為 SNI 是後面才補充進去 RFC 的,所以放在了擴展欄位裡面。注意如果你的客戶端不支持 SNI,將不會帶上這個欄位,這個時候伺服器就會傳回默認的 SSL 證書了。
Server Hello
服務端收到客戶端發來的 Client Hello 之後,會傳回 Server Hello 包,其中包括:
- Random:由時間戳和 28 bytes 的隨機數組成,將用於後面生成 Master Secret。
- SessionID:一個 32 bytes 的隨機數,如果下次客戶端想復用這個連接,在 Client Hello 中帶上即可。
- Cipher Suite:服務端會根據客戶端傳來的 Cipher Suites 從中選擇一個用於加密。
Certificate
在服務端發送完 Server Hello 之後,會馬上繼續發一個 2464 bytes 大小的包,裡面包括了服務端的公鑰。
Server key Exchange Message & Server Hello Done
如果發送完 Certificate 之後還需要補充一些其他的信息,譬如加密演算法相關的,會額外補充一個 Server key Exchange 的包,最後會統一發送一個 Server Hello Done 的包表示傳輸結束。
Verify Certificate and Signatures
客戶端收到服務端發來的證書文件之後,必須檢驗:
- 證書的 Common Name 是否符合我們訪問的域名,如果不符合,會彈出警告框讓用戶選擇是否繼續
- 證書的時間,不能早於證書上的 「not before」,也不能晚於 「not after」
- 使用 CA 的公鑰驗證服務端公鑰是未被篡改的,同樣,如果發現 CA 校驗不正確,瀏覽器也會彈出警告
驗證過程涉及到 RSA 演算法,如果感興趣可以看阮一峰的這篇文章:RSA演算法原理(一) - 阮一峰的網路日誌
Client Key Exchange Message
客戶端在驗證服務端發來的證書是可靠的之後,會生成一個 48 bytes 的隨機數,我們稱之為 Pre-Master Secret。然後使用服務端的公鑰對這個 Pre-Master Secret 加密,傳回服務端。不同的加密方法可能會加上位數填充等手段增加其安全性。
Change Cipher Spec
這是客戶端發送的最後一個明文包,告訴服務端我們之後所有的請求都會通過協商好的加密方法進行通訊。同樣,服務端也會傳回一個 Change Cipher Spec 告知客戶端收到了。這裡有一點注意,這個 Change Cipher Spec 是可以和前面的 Client Key Exchange Message,或者其他包共用同一個 TCP 包的。SSL Record Layer 裡面也允許這樣做,而且大部分瀏覽器也是這樣做的,可以有效減少 TCP 包的包量。
Master Secret
雙方並不是只依賴 Pre-Master Secret 來加密解密傳輸數據,最後會生成一個 Master Secret 用來加解密,函數為:
master_secret = PRF(pre_master_secret, "master secret", ClientHello.random + ServerHello.random)[0..47];
這裡終於用到了最開始提到的兩個 random。取前面 48 bytes,後面全部截斷。
剩下的就是用對稱演算法對內容進行加密了,部分演算法涉及到生成多個 key pair,在此就不展開了,想了解可以看這裡:The TLS Protocol Version 1.0。
同樣在加密之後,會對內容做一次 HASH,譬如這樣:
verify_data = PRF(master_secret, "client finished", MD5(handshake_messages) + SHA-1(handshake_messages) ) [12]
這裡假設使用的是 MD5 的校驗方法。
Application Layer
恭喜你,你終於完成了 SSL 握手,進入了應用層。
匯總
最後我們來看一看抓包的截圖
後續
如果大家對 HTTPS 希望了解的更深,可以看看這篇文章:The First Few Milliseconds of an HTTPS Connection 非常詳細的介紹了整個過程。文章結尾還有一個小程序描述整個 SSL 握手的過程。
-------
參考文獻:
- The TLS Protocol Version 1.0
- Transport Layer Security (TLS) Extensions
- The First Few Milliseconds of an HTTPS Connection
- Understanding SSLHTTPS
- HTTPS and the TLS handshake protocol.
- RSA演算法原理(一) - 阮一峰的網路日誌
- RSA演算法原理(二) - 阮一峰的網路日誌
推薦閱讀: