萬字長文,帶你徹底搞懂 HTTPS(文末附實戰)

2023-04-15 06:00:46

大家好,我是滿天星,歡迎來到我的技術角落,本期我將帶你一起來了解 HTTPS。

前言

其實網上寫 HTTPS 的文章也不少了,但是不少文章都是從原理上泛泛而談,只講概念,沒有講原因,作為小白,看完還是會有一種似懂非懂的感覺。
本文嘗試從 HTTP 開始,一步一步深入到 HTTPS,告訴你 HTTPS 到底是什麼、為什麼需要 HTTPS、以及 HTTPS 到底是怎麼做的。相信你在閱讀完本文後,一定會對 HTTPS 有深入的瞭解。
紙上得來終覺淺,除開原理部分外,本文還提供了 HTTP 和 HTTPS 的實戰教學,幫助你從 0 開始搭建一個 HTTPS 加密的 Web 伺服器,如果按照實戰教學一步一步走下來,那麼你將對 HTTPS 有更進一步的把握。
好了,廢話不多說,直接進入正題吧。
什麼是 HTTPS ?一句話,HTTPS = HTTP + SSL。HTTPS 並不是一個全新的協定,而是在 HTTP 的基礎上,通過 SSL 增加了一層加密協定,從而大大增加了 HTTP 協定的安全性。
所以在正式瞭解 HTTPS 之前,我們需要先了解 HTTP。

1. HTTP

HTTP 全稱 超文字傳輸協定(HyperText Transfer Protocol),是一種廣泛用於網際網路中瀏覽器與伺服器之間的應用層傳輸協定。簡單來說,瀏覽器向伺服器傳送 HTTP 請求,伺服器向瀏覽器返回 HTTP 響應,兩者之間通過這種方式進行「交流」,來使得我們的瀏覽器可以正常從伺服器端獲取資料,並展示在使用者的電腦螢幕上。

以存取 http://httpbin.org網址為例,一個典型的 HTTP 請求如下所示:

GET / HTTP/1.1
Accept: text/html,...
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cache-Control: no-cache
Connection: keep-alive
Host: httpbin.org
Pragma: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36
  • GET 表示請求方法,常見的 HTTP 請求方法有 GET、POST、PUT、DELETE 等...
  • GET 後面的 /表示請求路徑,這裡我們存取的根路徑,所以顯示為 /。如果你存取 httpbin.org/get的話,這裡顯示的就是 /get
  • HTTP/1.1 表示使用的 HTTP 協定版本,現在常用的有 HTTP/1.1 和 HTTP/2,當然還有更先進的 HTTP/3,這裡就不過多展開了
  • 下面的 9 行全部都是 HTTP header,每一個 header 包含 name 和 value,之間用冒號分隔開。

一個典型的 HTTP 響應如下所示

HTTP/1.1 200 OK
Date: Sat, 08 Apr 2023 16:28:43 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 9593
Connection: keep-alive
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

body...
  • HTTP/1.1 指的協定版本,響應和請求的協定版本是一致的
  • 200 OK 代表返回的響應碼,表示這個響應是符合預期的。另外還有非常常見的返回碼 404 NOT FOUND ,大家應該或多或少聽說過,它表示伺服器告訴你你存取的這個資源不存在
  • 後面 7 行全部是 HTTP header,同樣每一個 header 包含 name 和 value,之間用冒號分隔開。
  • 最後是 HTTP Body,也就是響應體,即伺服器返回給你的內容主體,瀏覽器正是根據響應體來渲染頁面的

由於本文重點不在 HTTP,這裡不再詳細介紹各個部分的作用。
如果你對 HTTP 還不太瞭解的話,我在本文附錄部分準備了一份 HTTP 實戰,建議你跟著實操一遍,瞭解下 HTTP 的基本結構。當然,這部分內容比較基礎,如果你已經對 HTTP 比較熟悉的話,這部分可以跳過了。

2. 為什麼需要 HTTPS

上面簡單講了一下 HTTP。在正式講 HTTPS 之前,首先我們要搞清楚 HTTP 的缺點是什麼,為什麼需要 HTTPS。
我們知道,HTTP (除了最新的 HTTP/3 外),傳輸層是基於 TCP 協定的。TCP 建立連線時,有三次握手。三次握手完畢之後,TCP 連線就順利建立了,接下來兩端將會傳輸資料。
對於普通的 HTTP 協定,在建立完 TCP 連線之後,就直接開始傳輸資料了,這時候資料是明文傳輸的,這也是 HTTP 最不安全的地方。
明文傳輸是什麼概念呢?我們知道,瀏覽器和伺服器之間,是存在很長一條路線的,你在家裡通過瀏覽器存取網頁的時候,資料會從你的電腦,傳到你家裡的路由器,再到光貓,到運營商,到網際網路....直到最後才到伺服器。在明文傳輸下,理論上來講,瀏覽器和伺服器之間的任一節點,包括你家裡的路由器、包括你購買寬頻/流量的運營商,都可以「竊聽」你們的資料,甚至還可以修改資料。
聽起來不夠直觀?打個比方,近代時期,戰場上打仗時,部隊之間會通過電臺進行交流。如果通過明文進行交流,那麼非常危險,敵軍可以開啟電臺進行竊聽,偷取你的軍事情報,這樣的事也屢見不鮮了...那麼他們是如何解決這個問題的呢?兩個部隊之間提前約定一個加密的方案,在傳資料之前,先把它進行加密再傳輸,另一端收到資料之後,按照事先約定的方案進行解密,然後讀取就可以了。這樣即使敵軍開始竊聽,也只能聽到加密後的情報,如果無法對其破解的話,得不到任何有效資訊。
沒錯,這就是 HTTPS 的思想,瀏覽器在傳送 HTTP 請求之前,先通過某種方式對其進行加密,然後再進行傳輸。伺服器端收到資料之後,對其解密,讀取真實內容,生成 HTTP 響應,同樣對響應進行加密,然後傳回給瀏覽器,瀏覽器收到資料之後,對其進行解密,得到真正的 HTTP 響應。這樣就可以保證資料在傳輸過程中的安全性,無論是路由器還是運營商,都沒有辦法「竊聽」你們的資料了。
說到這裡,想必你已經知道 HTTPS 的一大作用了,它可以保證資料在網際網路上傳輸的安全性,避免中間節點進行竊聽和修改。
當然,聰明的你還可能會想到一些問題,例如:

  1. 戰場上軍隊之間是提前約定好加密方案的,但是咱們任意一個瀏覽器都可以隨時存取網頁,沒有辦法提前約定加密方案呀,那是怎麼做到的呢?
  2. 戰場上經常出現敵軍對另一方部隊之間的電臺加密進行破解的事情,破解完成之後,還是能夠竊聽到資料,那 HTTPS 的這個加密方案到底安全嗎,會被破解嗎?

別急,這些問題,你都可以在本文中得到答案。

3. HTTP + SSL = HTTPS !

上面提到了對 HTTP 進行加密的思想。在 HTTPS 的具體實現中,這個加密方案即是大名鼎鼎的 SSL(Secure Sockets Layer)。
定義:SSL(Secure Sockets Layer)是一種安全協定,用於在網際網路上保護資料的傳輸安全。它工作在傳輸層,主要功能是通過加密技術,保護不同計算機之間的資料傳輸過程,防止敏感資料被駭客竊取和篡改。SSL 協定可以用於保護網站的使用者登入、信用卡支付、網上銀行等敏感資訊的傳輸,以及企業之間的機密資料的傳輸。SSL 協定目前已經被繼承為 TLS(Transport Layer Security),是一種安全性更高的傳輸層協定。所以,下面我將統一以 TLS 為名稱進行講解。
首先,劃重點,TLS 中有 Transport Layer,顧名思義,它一定是工作在傳輸層了。上面提到過,HTTP 是應用層協定,傳輸層和應用層的概念,想必大家應該知道吧,計算機網路的事實標準中,自頂向下可以分為五層:應用層、傳輸層、網路層、鏈路層、物理層...這是《計算機網路》的基礎,這裡不過多展開,不熟悉的同學,要回去重修一下課程了。
我們知道,TCP 協定裡有三次握手,三次握手成功後連線才算建立,接下來才會真正開始傳輸資料。傳統的 HTTP 協定中,三次握手成功之後,就會直接開始明文傳輸 HTTP 資料了。
那麼 TLS 是什麼時候開始發揮作用的呢?答案很簡單,在三次握手之後,傳輸資料之前。
也就是說,在 TCP 協定中加入 TLS 之後,三次握手成功之後就不會再立刻開始傳輸資料了,而是緊接著開始 TLS 的建立過程,也被稱為 TLS 握手。
TLS 握手是幹嘛呢?或者說為什麼需要 TLS 握手呢?上面提到,在戰場上,兩個部隊之間會提前約定好加密的方案,例如面對面用紙互相寫下加密方案,然後在一段時間之內的電臺通訊統一用這個加密方案,這樣能一定程度上保證電臺通訊的安全性。但是 TLS 中我們並沒有這樣一個「面對面」的機會,咱們總不可能在存取網頁之前,人肉跑到伺服器的維護者那邊去跟他約定加密方案吧。出於這個目的,TLS 握手便出現了。所以我們可以說,TLS 握手的目的是給通訊雙方約定一個安全的加密方案(可以理解為商量一個只有雙方知道的加密金鑰)。
知道了 TLS 握手的目的,接下來我們需要知道它具體是怎麼做的。首先,我們肯定不能直接明文傳輸加密方案(金鑰),不然這個金鑰在傳輸過程中就直接被第三方獲取了,那麼加密將沒有任何意義。也就是說,TLS 握手需要做到:通訊雙方可以約定一個共同的加密方案(金鑰),並且這個約定的過程(即 TLS 握手過程),即使被任何第三方竊聽到,也無法解析出這個加密方案(金鑰)
是不是聽起來很神奇,那到底是怎麼做到的呢?這就不得不提到密碼學中非常經典的兩個概念:對稱加密和非對稱加密。

4. 對稱加密,非對稱加密


對稱加密是 TLS 握手成功後,通訊雙方之間採用的資料加密方案。現在的主要問題是:通訊雙方如何安全的商量好這個對稱金鑰,防止金鑰被其他人竊取?
這時就需要輪到非對稱加密出場了。什麼是非對稱加密呢?與對稱加密不同,非對稱加密方案中,使用者手握兩把金鑰,一把稱為公鑰,一把稱為私鑰,其中公/私鑰都可以用來加密/解密資料,但是:用公鑰加密後的資料,只有用私鑰才能將其解開;用私鑰加密後的資料,只有用公鑰才能將其解開!


這裡只介紹了非對稱加密的特點,並沒有介紹其原理,因為這屬於密碼學的範疇了,展開來講又是一篇文章。簡單說說其思想吧,目前流行的非對稱加密演演算法 RSA 基於的原理其實就一句話:我們目前還沒有很好的辦法對一個很大的數做因式分解,例如你在心裡默默想一個很大的質數 p 和 質數 q,算出其乘積 n,那麼向外公開 n 的話,外部人員是很難找出 p 和 q 的(只能暴力嘗試,而當這樣的數夠大夠多的時候,以現在的計算機算力也需要幾百上千年的時間才能破解了)。對密碼學感興趣的同學,可自行進一步瞭解。
有了非對稱加密,事情就變得有意思起來了。見下圖,伺服器端用非對稱加密方案生成一對公/私鑰,私鑰掌握在自己手裡,誰也不告訴;在 TLS 握手的過程中,伺服器將自己的公鑰交給瀏覽器端,瀏覽器端在心裡默默想出一個對稱加密的金鑰後,將這個金鑰用伺服器端的公鑰進行加密,然後再傳回給瀏覽器端;瀏覽器端收到這個資料之後,用自己的私鑰將其進行解密,就能夠得到剛才瀏覽器心裡默唸的那個對稱金鑰了。這樣這個問題就完美的解決了,兩邊可以心有靈犀的拿到這個對稱金鑰,而不用擔心被任何第三方竊取到了。

這就是 TLS 握手的過程嗎?不,當然沒這麼簡單了,我們還沒有考慮一個非常巧妙的攻擊手段:中間人攻擊。

5. 中間人攻擊

具體什麼是中間人攻擊呢?看下面這張圖

假設現在我們從電腦上存取百度,如果有一箇中間人在我的路由器端,或者運營商端,或者任何一箇中間節點上擷取了我的請求,剛不是提到伺服器端需要返回給我們公鑰嗎,中間人他自己也生成一套公/私鑰,然後將自己的公鑰返回給我,這樣我就與中間人之間建立了一條我以為「安全」的連線了,此時我以為我連線的是百度伺服器,其實我連線的是中間人...那麼此時中間人可以做任何事情了,如果他人品比較好的話,他可以默默當一個代理,我要存取百度,他就去幫我存取百度,然後把結果返回給我,勤勤懇懇做一個「中間商」。當然,我們知道做這種攻擊的人人品往往不會太好,所以他們可以做更壞的事情,例如偽造一個銀行網頁返回給我,讓我填寫賬號和密碼,這樣的話...後果就不堪設想了。
那麼如何防止中間人攻擊呢?其中的核心就是:我們需要保證我們存取的就是目標伺服器,例如,當我們存取百度時,我們需要確保在 TLS 握手時,給我們公鑰的人就是百度,而不是任何其他人。
那麼這個應該如何去保證呢?這就不得不提到接下來的幾個概念了,數位憑證,以及證書權威機構(Certificate Authority,簡稱 CA)。

6. 數位憑證、CA

數位憑證是由證書權威機構(CA)頒發的一個用於證明身份的證書,當然其中還包含了該使用者的公鑰等資訊。例如還是以百度為例,假設百度需要給 www.baidu.com這個域名申請一個數位憑證,他需要在生成公鑰/私鑰後,將自身的資訊(包括域名、公司名稱、公鑰資訊等)發給某個證書權威機構(CA),讓 CA 給自己頒發一個數位憑證。CA 需要驗證百度的真實身份,並且他確實擁有 www.baidu.com這個域名,一切都驗證通過後,CA 才會給百度頒發這麼一個數位憑證。那麼之後,不管是誰用瀏覽器存取 www.baidu.com的時候,百度都會將剛才那個 CA 頒發的數位憑證傳送給使用者,既可以用來自證身份,同時還順便告訴了使用者自己的公鑰。
到這裡你可能還會有幾個疑問:

  1. 數位憑證如何保證不能偽造呢,難道中間人不能偽造一個數位憑證傳送給使用者嗎?
  2. 即使數位憑證不能被偽造,從概念上看他是公開的,難道中間人不能直接把這個證書頒發給使用者嗎?

下面會一一回答這幾個問題。正式回答之前,先來看看數位憑證裡究竟有哪些內容。
既然上面提到百度,我們就以百度為例,我們使用瀏覽器存取百度,可以在位址列左邊看到一個小鎖,點選後,就可以檢視百度的數位憑證


圖中可以看到該證書的一些基本資訊:

  • 頒發物件:這個證書是頒發給百度的,並且只對域名 (www.)baidu.com 有效
  • 頒發者:這個證書是由 GlobalSign 頒發的。(GlobalSign是一家全球知名的證書權威機構)
  • 有效期:這個證書的有效期是從 2022 年 7 月到 2023 年 8 月。(一旦過期,證書將不被信任)
  • 指紋:指紋是整張證書經過雜湊計算後得到的特徵值,主要與後面會提到的簽名一起工作,起到防篡改的作用

當然,一張數位憑證的內容遠遠不止於此,例如還包含了伺服器的公鑰,可以在「詳細資訊」中進行檢視。
下面我們在證書詳細資訊中點選「匯出」,將證書匯出為 Base64 編碼的單一證書,然後使用 openssl 對其進行解析和檢視

$ openssl x509 -noout -text -in baidu.com.cer
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            44:17:ce:86:ef:82:ec:69:21:cc:6f:68
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=BE, O=GlobalSign nv-sa, CN=GlobalSign RSA OV SSL CA 2018
        Validity
            Not Before: Jul  5 05:16:02 2022 GMT
            Not After : Aug  6 05:16:01 2023 GMT
        Subject: C=CN, ST=beijing, L=beijing, OU=service operation department, O=Beijing Baidu Netcom Science Technology Co., Ltd, CN=baidu.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (2048 bit)
                Modulus:
                    00:aa:2f:cc:41:8d:25:ae:83:e9:f4:27:c4:00:b3:
                    39:6f:0e:98:2a:55:7d:07:e5:80:49:82:fa:d3:d3:
                    85:98:b5:df:7b:6f:bb:02:dd:ed:78:e4:0c:07:2b:
                    9e:1e:86:4b:f6:6a:86:58:d7:57:6f:21:59:11:d8:
                    6f:96:6e:d2:de:36:28:f6:b4:e3:ce:95:32:29:00:
                    c1:65:8e:69:b0:00:fe:52:37:f4:88:3f:8b:6d:0f:
                    bb:f0:ec:c5:c0:31:ef:ad:b5:0c:06:66:ad:be:dc:
                    43:13:c4:66:b0:5d:cf:56:53:e2:d1:96:82:1c:06:
                    bb:9b:5f:ed:60:8d:d2:ed:f3:d2:50:ee:bb:cd:b2:
                    36:97:c8:ce:7b:d2:4b:b7:5c:b4:88:ca:37:6e:8b:
                    ce:f9:96:fd:b4:f5:47:b5:20:77:bb:fc:a8:9d:81:
                    b2:6c:f8:c7:09:6a:dd:22:6e:83:3f:a7:53:df:f1:
                    da:2f:29:6b:22:c3:e9:1d:65:e8:c5:a0:ba:13:4e:
                    16:3f:03:93:f0:a5:59:8a:1a:80:e8:27:7d:49:23:
                    df:d1:f9:4b:97:b7:01:c4:19:f5:f1:c5:ff:91:33:
                    d0:a1:74:c6:ee:d4:cf:f6:38:0c:ed:bd:5e:aa:44:
                    fb:88:f7:7b:99:70:76:34:55:7e:55:d2:0f:9e:bf:
                    94:93
                    
  	... (中間省略)
   
    Signature Algorithm: sha256WithRSAEncryption
         63:21:07:23:47:06:eb:b3:7c:77:6c:df:bc:55:12:b9:f1:5e:
         6a:04:60:16:be:d0:0b:18:9c:94:0c:a8:82:08:25:0d:26:fb:
         dd:cb:fc:8c:27:d9:0c:fa:4a:b6:31:b6:67:f0:26:2c:0d:96:
         96:39:65:3f:d9:a1:ee:de:9c:10:4d:54:e1:c8:d6:a9:0e:77:
         db:00:e2:37:e3:3f:b4:9c:31:4f:ac:74:d3:22:12:53:36:d0:
         ef:18:07:2d:8e:d0:e6:91:b2:6c:4a:5e:39:53:14:58:4e:d1:
         50:04:c9:83:7e:0d:7b:15:96:87:11:d7:5d:4a:17:ac:aa:9f:
         84:e3:a8:24:9d:d6:17:77:26:8c:9f:7a:7b:18:da:39:2f:77:
         f7:2b:c7:23:b8:97:6f:c3:d1:72:4c:7e:fc:c6:0d:cc:73:38:
         19:81:fb:e7:c1:7a:e8:b9:1d:3a:05:dc:36:04:9b:f1:f0:e1:
         a6:47:a0:30:4f:55:90:6c:da:cf:9e:b2:76:12:11:a1:5c:b6:
         61:8d:15:a4:68:65:9a:57:2f:7a:6e:a3:1f:f5:b4:92:5a:3c:
         df:71:0a:cd:57:d4:d0:15:36:7e:ba:d5:03:25:27:45:b4:60:
         cd:2e:02:c1:0f:0a:e7:41:6f:58:69:20:9e:ad:47:52:1a:b5:
         e6:e5:8d:1d

可以看到,除了上面 Chrome 裡顯示的一些基本資訊外,證書裡還包含了幾個重要資訊:

  • 伺服器端的公鑰,即上面 RSA Public-Key 下面的那一長串數位,就是百度的公鑰了。
  • 簽名,證明數位憑證有效的關鍵資訊。如果把數位憑證類比成一張合同的話,我們知道合同需要老闆簽字才算有效,同樣,數位憑證是需要 CA 簽名才算有效的,這裡的一長串字元就是 CA 對該證書的「簽名」了。

我們知道合同上的簽名是靠筆跡鑑定來確認真偽的,很明顯這一長串字元裡沒有筆跡,那它是如何保證該簽名是 CA 頒發的,而不是被其他人偽造的呢?
上面我們提到,每一張數位憑證有一個指紋,是將整張證書經過雜湊運算後得到的特徵值。CA 作為權威機構,其本身也是有一對公鑰/私鑰的,它在頒發數位憑證的時候,會用自己的私鑰對證書的指紋進行加密,生成的這段加密資料,就是該證書的簽名了!那麼我們瀏覽器是如何驗證證書的真偽呢?我們只需要使用 CA 的公鑰對簽名進行解密,看看得到的值是不是跟證書的指紋是一樣的,這樣就 OK 了,只要是一樣,說明這個證書一定是 CA 頒發的。
那麼,又有問題來了:我們瀏覽器是從哪裡拿到 CA 的公鑰呢?總不能還是通過網路傳輸吧,這樣就有「套娃」的中間人攻擊風險了。所以啊,我們的瀏覽器或作業系統已經內建了世界權威的 CA 的數位憑證(證書裡就包含了其公鑰)了,點選瀏覽器的 設定 -> 隱私設定和安全性 -> 安全 -> 管理裝置證書,可以檢視當前系統內建的所有 CA 證書。

上圖是我的電腦中內建的 CA 證書。剛剛提到了百度的數位憑證是由 GlobalSign 頒發的,這裡也可以驗證,GlobalSign 是被我們的作業系統所信任的 CA,並且我們已經將它的證書內建在作業系統中了。因此現在我們可以認定說,這個證書是值得信任的,與我們建立連線的就是百度,不是別人。

你可能會想,如果中間人將百度真實的數位憑證返回給我呢?中間人是沒有百度的私鑰的,所以當我們提取出證書中的公鑰,並對心裡想的金鑰進行加密後,中間人是解不開這個金鑰的,所以中間人無論如何也無法與我們建立連線。

好了,數位憑證的內容已經全部講述完畢了,最後回過頭來複習下前面提到的兩個問題:

  1. 數位憑證如何保證自身不會被偽造?

數位憑證中有一段簽名,該簽名是 CA 使用其私鑰對證書指紋進行加密後得到的值,我們瀏覽器使用 CA 的公鑰對該簽名進行解密後,與該證書的指紋進行對比,就可以知道證書是否被篡改或者偽造了。
當然,這裡要多提一嘴,我們作為使用者端,需要保證自己的電腦裡儲存的都是值得信任的 CA 根證書,因為信任某 CA 就代表信任了該 CA 頒發的所有數位憑證,如果有人/軟體想在你的電腦裡安裝來歷不明的 CA 證書,那你就要保持警惕了...

  1. 如果中間人直接把真實的數位憑證返回給我,它能夠成功與我建立連線嗎?

答案是不行的。這個問題其實比較簡單,剛剛提到,伺服器端除了公鑰外,自身還儲存有一份私鑰的,而中間人是拿不到這個私鑰的,因為它被伺服器雪藏起來,不會傳送到網際網路中的...那麼如果中間人用伺服器的證書返回給使用者,使用者採用伺服器的公鑰對自身默唸出來的對稱金鑰進行加密後,返回給中間人的時候,中間人就一臉懵逼了,因為這個金鑰它解不開呀,它沒有私鑰的,所以這個問題就完美解決了。

7. TLS 握手具體過程

最後,我們再完完整整講一下 TLS 握手的具體流程。


上圖來自 www.ssl.com,展示了整個握手流程,我用大白話解釋一下:

  1. 使用者端向伺服器傳送 Client Hello 資訊,告知自己想要建立一條 TLS 連線,並告知自己支援的加密演演算法。
  2. 伺服器向用戶端傳送一個 Server Hello 的迴應,並選擇一個加密演演算法,同時給使用者端傳送自己的數位憑證(包含伺服器的公鑰)。
  3. 使用者端驗證伺服器發來的數位憑證,驗證通過後,在心裡默默想出一個 pre-master 金鑰(預主金鑰),然後使用伺服器的公鑰,將預主金鑰進行加密後,傳送給伺服器。
  4. 伺服器用自己的私鑰進行解密,得到預主金鑰。
  5. 使用者端和伺服器都通過預主金鑰,進行相同的計算後,得到後續通訊時使用的對稱加密金鑰,稱為 shared secret。
  6. 使用者端和伺服器端都分別用生成的 shared-secret 加密一段報文後,傳送給對方,以驗證對方能夠成功收到資訊並解密。
  7. 然後 TLS 就建立成功了,接下來雙方都用這個 shared-secret 進行加密通訊。

總結一下,HTTPS 的加密過程中其實既用到了非對稱加密也用到了對稱加密,其中握手過程使用的是非對稱加密,主要目的是雙方可以安全的協商一個統一的金鑰,而真正的資料傳輸過程則使用的是對稱加密,正是使用剛才商量的這個金鑰。
你可能會問,為什麼不全程使用非對稱加密呢?因為對稱加密效率更高,尤其是在大量資料的時候,對稱加密比非對稱加密整整快幾個數量級,所以真正資料傳輸的過程選用了對稱加密。
到這裡,HTTPS 的原理就已經全部介紹完畢了。大家如果還有什麼疑問,歡迎在評論區留言討論。

8. 總結

最後,我們總結一下,HTTPS 解決了兩個問題:

  1. 資料傳輸過程中的安全問題,因為它對資料進行了加密,只有瀏覽器和伺服器可以對其進行解密。
  2. 瀏覽器對伺服器的信任問題,數位憑證以及其中的數位簽章,保證了我們存取的就是我們想要存取的伺服器,不可能被釣魚網站欺騙,也不可能被中間人攻擊所欺騙。

當然,保證以上安全的前提是我們的電腦本身沒有被攻破,如果你的電腦被駭客攻擊,裝上了來歷不明的根證書,那麼 HTTPS 也不能保障你的安全了。

附錄一:HTTP 實戰

這裡的實操部分針對不太熟悉 HTTP 的同學,熟悉的同學請直接跳過...

在瀏覽器(建議 Chrome)中存取httpbin.org,並使用開發者工具進行觀察

點選 Request Headers 後面的 View source,可以看到原始的 HTTP 請求報文,摘抄如下

GET / HTTP/1.1
Accept: text/html,...
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cache-Control: no-cache
Connection: keep-alive
Host: httpbin.org
Pragma: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36

其中:

  • GET 表示請求方法,常見的 HTTP 請求方法有 GET、POST、PUT、DELETE 等...
  • GET 後面的 /表示請求路徑,這裡我們存取的根路徑,所以顯示為 /。如果你存取 httpbin.org/get的話,這裡顯示的就是 /get
  • HTTP/1.1 表示使用的 HTTP 協定版本,現在常用的有 HTTP/1.1 和 HTTP/2,當然還有更先進的 HTTP/3,這裡就不過多展開了
  • 下面的 9 行全部都是 HTTP header,每一個 header 包含 name 和 value,之間用冒號分隔開。

當然,點選 Response Headers 後面的 view source,你就可以看到伺服器返回給你的 HTTP 響應頭是怎麼樣的了

HTTP/1.1 200 OK
Date: Sat, 08 Apr 2023 16:28:43 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 9593
Connection: keep-alive
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

body...
  • HTTP/1.1 指的協定版本,響應和請求的版本肯定是一樣的了
  • 200 OK 代表返回的響應碼,表示這個響應是 OK 的。另外還有非常常見的返回碼 404 NOT FOUND ,表示伺服器告訴你你存取的這個資源不存在...
  • 後面 7 行全部是 HTTP header,同樣每一個 header 包含 name 和 value,之間用冒號分隔開。
  • 最後是 HTTP Body,也就是響應體,在 Response Header 裡是看不到了,不過點選上面 Tab 頁的 Response 後,就可以看到完整的響應體了。

由於本篇文章的重點不在 HTTP,這裡就不再過多展開,非常建議大家平時使用瀏覽器存取網頁的時候,有事沒事多開啟下上面的開發者工具,看看 HTTP 到底是怎麼工作的,說不定,還能找到一份工作呢...

(存取百度時,開發者工具中的小彩蛋)

當然,作為程式設計師,還是希望有更「極客」的方式來了解 HTTP 協定,這時候就要用到 curl命令了
終端裡輸入 curl -v httpbin.org,可以看到整個 HTTP 互動的流程

附錄二:數位憑證實戰

這部分實戰教學將帶你體驗 HTTPS 的全流程,從證書生成、到 HTTPS 伺服器建設、到最後的瀏覽器存取。教學將從以下三個方面進行:

  1. 首先利用 openssl工具,模擬一個證書權威機構(CA),生成 CA 證書以及金鑰。
  2. 然後利用 openssl工具,模擬 techcorner.cn網站的維護者,向上面的 CA 機構申請一個數位憑證
  3. 使用 Nginx 搭建自己的 HTTPS 伺服器,並使用上面生成的伺服器證書。
  4. 使用瀏覽器存取自己搭建的 HTTPS 伺服器,體會全加密流程。

值得一提的是,網上也有不少利用 openssl 生成證書的教學,但是它們質量參差不齊,絕大多數都無法生成一個瀏覽器識別的、能夠真正用在生產環境中的證書。而本教學是我查閱官方檔案、並且親自試驗過的教學,絕對有效。

實戰前,你需要準備:

  • Mac / Linux 伺服器
  • 安裝 openssl 工具、以及 Nginx 。

1. 生成 CA 證書

我們知道伺服器拿到的數位憑證都是從 CA 那邊頒發的,所以首先我們需要模擬出一個 CA 機構,即生成 CA 證書以及金鑰。
首先生成 CA 私鑰(公鑰可以從私鑰中提取而來)

# 生成 CA 私鑰 
openssl genrsa -out ca.key 2048

然後生成證書籤發申請檔案

# 生成證書籤發申請檔案(.csr)
openssl req -new -key ca.key -out ca.csr -subj '/C=CN/ST=Zhejiang/L=Hangzhou/O=Tech_Corner/CN=ROOTCA'

-subj 後面的引數代表了生成證書的基本資訊,包括國家、省份、城市、公司名稱等。CN=ROOTCA,代表是準備申請一個根證書。
然後根據上面生成的私鑰和證書申請檔案,自籤一個根證書

# 自籤一個根證書
openssl x509 -req -days 365 -sha256 -extensions v3_ca -signkey ca.key -in ca.csr -out ca.cer

總結一下,以上命令,一共生成了三個與 CA 有關的檔案:

  • ca.key:CA 私鑰
  • ca.csr:CA 為自己簽發證書的請求。(該檔案的作用是生成 CA 證書,生成之後不會再用到)
  • ca.cer:CA 為自己簽發的證書

可以用 openssl 命令,檢視下申請的證書資訊

$ openssl x509 -in ca.cer -text -noout
Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number:
            92:63:8f:e3:23:97:e1:c6
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=CN, ST=Zhejiang, L=Hangzhou, O=Tech_Corner, CN=ROOTCA
        Validity
            Not Before: Apr 11 15:39:56 2023 GMT
            Not After : Apr 10 15:39:56 2024 GMT
        Subject: C=CN, ST=Zhejiang, L=Hangzhou, O=Tech_Corner, CN=ROOTCA
        ... 以下省略

2. 生成伺服器證書

接下來,我們模擬網站 techcorner.cn的維護者,想要為自己的網站申請一個數位憑證,所以我們需要向上面模擬的 CA 來申請證書。
同樣,我們首先生成一個自己的私鑰 。

openssl genrsa -out server.key 2048

然後,生成一個證書籤發申請檔案

openssl req -new -key server.key -out server.csr -subj '/C=CN/ST=Zhejiang/L=Hangzhou/O=Tech_Corner/CN=techcorner.cn'

注意,這裡 -subj中有一個很重要的引數是 CN=techcorner.cn,它代表我們生成的證書,只簽發給這個域名。換句話說,其他的域名如果使用這個證書,會被瀏覽器判斷為無效。
以上證書籤發申請檔案(server.csr) 傳送給 CA 後,CA 會對其做一些驗證,例如驗證申請者本身是不是符合資質,是不是確實擁有這個域名,等等。都確認完畢後,CA 才會決定給使用者頒發這個證書。(CA 必須維護自己的權威性,隨便頒發證書的 CA 將被世界所拋棄,已經有這樣的先例了)
CA 為其頒發證書的命令,可模擬如下

openssl x509 -req -extfile <(printf "subjectAltName=DNS:techcorner.cn,DNS:www.techcorner.cn") -days 365 -in server.csr -CA ca.cer -CAkey ca.key -CAcreateserial -out server.cer

其中比較有意思的是 -extfile後面填寫的 subjectAltName=...這一串,這其實是 X509 V3 證書裡面的欄位,對於普通的 TLS 加密,他可能不是必須的,但是對於 HTTPS,尤其是瀏覽器來說,它會額外對這個欄位進行校驗,確保其中存在當前存取的域名,否則即使 CN滿足,瀏覽器同樣不會信任這個證書!(網上很多教學都沒有注意到這個地方,導致最後生成的證書不可用)
得到的伺服器證書為 server.cer。同樣可以使用 openssl 檢視證書

$ openssl x509 -in server.cer -text -noout
Certificate:
    Data:
        Version: 1 (0x0)
        Serial Number:
            80:56:02:4d:21:ff:5e:60
    Signature Algorithm: sha256WithRSAEncryption
        Issuer: C=CN, ST=Zhejiang, L=Hangzhou, O=Tech_Corner, CN=ROOTCA
        Validity
            Not Before: Apr 11 15:52:10 2023 GMT
            Not After : Apr 10 15:52:10 2024 GMT
        Subject: C=CN, ST=Zhejiang, L=Hangzhou, O=Tech_Corner, CN=techcorner.cn
...以下省略

上面可以看到該證書的基本資訊(Subject),以及頒發者資訊(Issuer)等。

3. 啟動 Nginx,並使用上面生成的伺服器證書

編輯 nginx.conf,新增 HTTPS 埠的監聽

server {
   listen       443 ssl;
   server_name  techcorner.cn;

   ssl_certificate      /path/to/server.cer;
   ssl_certificate_key  /path/to/server.key;

   ssl_session_cache    shared:SSL:1m;
   ssl_session_timeout  5m;

   ssl_ciphers  HIGH:!aNULL:!MD5;
   ssl_prefer_server_ciphers  on;

   location / {
       root   html;
       index  index.html index.htm;
   }
}
  • ssl_certificate的值修改為上一步生成的 server.cer 的絕對地址
  • ssl_certificate_key的值修改為上一步生成的 server.key 的絕對地址

然後啟動 Nginx。
啟動完畢後後,通過瀏覽器存取 https://localhost:443存取 Nginx

可以看到瀏覽器給出了不安全的警告,這是符合預期的,因為我們使用的證書是自己使用 openssl 簽發的,CA 機構也是自己模擬的,並不在電腦的信任列表中。
如果想要正常存取,可以在系統的根證書列表中,將我們自己模擬的 CA 證書新增進去(Mac 系統中,將根證書 ca.cer拖入鑰匙串存取的 系統 中即可,並對其進行 「始終信任」)。

這個 CA 證書是我們自己生成的,只要我們自己不洩漏其私鑰,那麼將是安全的


使用瀏覽器重新存取 https://localhost:443,得到如下結果

可以發現仍然是 不安全 的狀態,不過這時錯誤程式碼已經變了,剛才是 ERR_CERT_AUTHORITY_INVALID,意思是頒發證書的 CA 機構無效。
而現在錯誤變成了 ``ERR_CERT_COMMON_NAME_INVALID,這是什麼意思呢?它其實是說證書的 CN和我們存取的地址不匹配,怎麼理解呢?還記得我們建立該證書的時候,裡面包含了CN=techcorner.cn,意思是這個證書只能被 techcorner.cn這個域名使用,現在我們存取的是 localhost,顯然他們是不匹配的,因此證書無效。 雖然我們沒有購買該域名的使用許可權,但是可以通過在本地修改 hosts 的方式,強行將 techcorner.cn解析為本機地址 127.0.0.1。 具體方式為,修改本機的 /etc/hosts檔案,在檔案末尾增加一行 127.0.0.1 techcorner.cn新增完畢後,再次使用瀏覽器存取https://techcorner.cn`

可以發現,Chrome 提示連線是安全的,證書是有效的

Perfect,恭喜你,到這裡,實戰部分就結束了,你已經成功體驗了 HTTPS 的全部內容。

如果本文對你有幫助的話,歡迎 點贊&收藏&轉發,你的支援將是