全球微頭條丨https 原理分析進階-模擬https通信過程
              2023-06-28 14:23:51 來源:

              大家好,我是藍胖子,之前出過一篇https的原理分析 ,完整的介紹了https概念以及通信過程,今天我們就來比較完整的模擬實現https通信的過程,通過這篇文章,你能了解到https核心的概念以及原理,https證書是如何申請的,以及如何用golang實現https通信,https雙向認證。

              本章代碼已經上傳到github


              (資料圖片僅供參考)

              https://github.com/HobbyBear/codelearning/tree/master/httpsdemo
              https原理回顧

              在開始之前,讓我們來了解下https相關的核心知識,可以作為上篇https原理分析的補充。學習一個東西一定要先知道為什么要用它,我總結了兩點:

              1,https 第一個好處是使原本的http明文傳輸變成了密文傳輸,增加了安全性。

              2,https第二好處是采用數字證書來解決了身份認證問題,起碼對端通信是經過ca認證的。

              那么https又是通過什么技術來實現上述兩點的呢?

              數字證書原理

              我先聊聊數字證書的實現原理,在https的握手階段,服務端會發送自身的證書給客戶端,客戶端會去驗證這個證書的有效性,有效性是這樣保證的:

              數字證書上會寫明證書的簽名算法和證書的簽名,如下圖所示

              證書經過簽名算法中指定的SHA-256算法將證書內容進行hash得到消息摘要,然后再將這個摘要值經過RSA算法用證書頒發機構的私鑰進行加密就得到了證書的簽名。

              而客戶端拿到這個證書就會用證書頒發機構的公鑰去解密簽名,然后按SHA-256算法也對證書內容進行hash,也得到一個消息摘要值,客戶端就去比對自己計算的消息摘要和公鑰解密簽名得到的消息摘要是否一致,一致則說明證書未被篡改并且是證書頒發機構頒發的。

              有同學可能會疑惑,證書頒發機構的公鑰是從哪里獲取的,證書頒發機構的公鑰就在頒發機構其自身的證書里,如下圖所示。

              https密文加密原理

              知道了數字證書的驗證原理,我們來看看https通信中涉及到的加密過程,在https的握手階段,服務端會選擇一個與客戶端都支持的密鑰套件用于后續的加密,密鑰套件一般會有如下組件:

              密鑰交換算法:用于在客戶端和服務器之間安全地交換加密密鑰。常見的密鑰交換算法有RSA和Diffie-Hellman等。

              對稱加密算法:用于對通信數據進行加密和解密。常見的對稱加密算法有AES、DES和3DES等。

              摘要算法:用于生成和驗證消息的完整性。常見的摘要算法有MD5和SHA-256等。

              https采用非對稱加密的方式交換密鑰,然后使用對稱加密的方式對數據進行加密,并且對消息的內容采用摘要算法得到消息摘要,這樣對端在解密數據后可以通過相同的消息摘要算法對計算后的消息摘要和傳過來的消息摘要進行對比,從而判斷數據是否經過篡改。

              具體步驟如下:

              客戶端向服務器發送一個初始的握手請求,該請求中包含了客戶端支持的密碼套件列表。服務器收到握手請求后,會從客戶端提供的密碼套件列表中選擇一個與自己支持的密碼套件相匹配的套件。服務器將選定的密碼套件信息返回給客戶端。客戶端收到服務器返回的密碼套件信息后,會選擇一個與服務器相匹配的密碼套件。客戶端生成一個隨機的對稱加密密鑰,并使用服務器的公鑰對該密鑰進行加密。客戶端將加密后的對稱加密密鑰發送給服務器。服務器使用自己的私鑰對接收到的加密的對稱加密密鑰進行解密。客戶端和服務器現在都擁有了相同的對稱加密密鑰,可以使用該密鑰進行加密和解密通信數據。客戶端和服務器使用對稱加密密鑰對通信數據進行加密和解密,并使用摘要算法對數據進行完整性驗證。

              通過以上步驟,客戶端和服務器可以建立一個安全的HTTPS連接,并使用密碼套件來保護通信的安全性。

              模擬證書頒發

              接下來,我們就要開始實現下https的通信了,由于只是實驗,我們不會真正的去為我的服務器去申請一個數字證書,所以我們暫時在本地用openssl來模擬下證書頒發的邏輯。

              模擬根認證ca機構

              我們知道證書頒發的機構是ca,而ca根證書是默認信任的,一般內置在瀏覽器和操作系統里,所以首先來生成一個根證書,并且讓系統默認信任它。

              先生成ca的私鑰

              openssl genpkey -algorithm RSA -out ca_private.key 

              然后生成ca的證書請求

              openssl req -new -key ca_private.key -out ca_csr.csr

              生成ca證書

              openssl x509 -req -in ca_csr.csr -signkey ca_private.key -out ca_cert.crt

              我用的是mac系統,所以我這里演示下mac系統如何添加證書信任,

              打開鑰匙串應用-> 將證書拖進登錄那一欄 -> 右擊證書點擊顯示簡介-> 將信任那一欄改為始終信任

              模擬ca機構向服務器頒發證書

              生成 服務器自身的私鑰

              openssl genpkey -algorithm RSA -out final_private.key

              接著就是生成證書請求,和前面生成證書請求不同,因為目前主流瀏覽器都要求證書需要設置subjectAltName,如果沒有設置SAN會報證書錯誤。

              所以我們要換種方式生成證書請求,首先創建一個文件,比如我創建一個san.txt的文件

              [req]default_bits = 4096distinguished_name = req_distinguished_namereq_extensions = v3_req[req_distinguished_name]countryName = countrystateOrProvinceName = provincelocalityName = cityorganizationName = company name## 換成自己的域名commonName = lanpangzi.com  [v3_req]subjectAltName = @alt_names[alt_names]## 換成自己的域名DNS.1=*.lanpangzi.comDNS.2=*.lanpangzi2.com

              到時候上述文件只需要更換為自己的域名即可。由于我的域名設置為了.lanpangzi.com 和.lanpangzi2.com,所以我還要改下本地的hosts文件。

              ## /etc/hosts127.0.0.1 www.lanpangzi2.com127.0.0.1       www.lanpangzi.com

              接著生成服務器證書請求

              openssl req -new -key final_private.key -out final_csr.csr -config san.txt -sha256

              生成服務器證書

              openssl x509 -req -days 365 -in final_csr.csr -CA ca_cert.crt -CAkey ca_private.key -set_serial 01 -out final_csr.crt -extfile san.txt -extensions v3_req
              golang實現https服務驗證證書

              經過了上述步驟后算是生成了一個由ca機構頒發的證書,然后我們用golang代碼實現一個https服務器。需要為https服務器傳入證書以及服務器自身的私鑰。

              func main() {     http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {        fmt.Fprintf(w, "Hello, World!\n")     })     fmt.Println(http.ListenAndServeTLS(":443",        "./final_csr.crt",        "./final_private.key", nil))  }

              接著實現下客戶端代碼

              func main() {     client := &http.Client{Transport: tr}     resp, err := client.Get("https://www.lanpangzi.com")     if err != nil {        fmt.Println("Get error:", err)        return     }     defer resp.Body.Close()     body, err := ioutil.ReadAll(resp.Body)     fmt.Println(string(body))  }

              啟動服務端和客戶端后能看到服務正常返回了。

              /private/var/folders/yp/g914gkcd54qdm5d0qyc9ljm00000gn/T/GoLand/___go_build_codelearning_httpsdemo_clientHello, World!

              說明證書配置已經成功,而客戶端驗證證書的邏輯已經在本文開始講解了。

              golang實現https雙向認證

              上述代碼只是實現了https的單向認證,即客戶端對服務端的域名進行認證,在某些情況下,服務端也需要檢驗客戶端是否合法,所以下面我們就來看下如何用golang實現雙向認證的。首先我們還是要用ca位客戶端頒發一個證書。

              模擬ca機構向客戶端頒發證書

              生成 服務器自身的私鑰

              openssl genpkey -algorithm RSA -out client_private.key

              創建一個san_client.txt的文件

              [req]default_bits = 4096distinguished_name = req_distinguished_namereq_extensions = v3_req[req_distinguished_name]countryName = countrystateOrProvinceName = provincelocalityName = cityorganizationName = company name## 換成自己的域名commonName = lanpangziclient.com  [v3_req]subjectAltName = @alt_names[alt_names]## 換成自己的域名DNS.1=*.lanpangziclient.comDNS.2=*.lanpangziclient2.com

              到時候上述文件只需要更換為自己的域名即可。由于我的域名設置為了.lanpangzi.com 和.lanpangzi2.com,所以我還要改下本地的hosts文件。

              ## /etc/hosts127.0.0.1 www.lanpangziclient2.com127.0.0.1       www.lanpangziclient.com

              接著生成服務器證書請求

              openssl req -new -key client_private.key -out client_csr.csr -config san_client.txt -sha256

              生成服務器證書

              openssl x509 -req -days 365 -in client_csr.csr -CA ca_cert.crt -CAkey ca_private.key -set_serial 01 -out client_csr.crt -extfile san_client.txt -extensions v3_req

              服務端和客戶端需要做下改動,服務端默認不會去校驗客戶端身份,但是現在改成強制校驗

              func main() {       s := &http.Server{        Addr: ":443",        Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {           fmt.Fprintf(w, "Hello, World!\n")        }),        TLSConfig: &tls.Config{           ClientAuth: tls.RequireAndVerifyClientCert,        },     }       fmt.Println(s.ListenAndServeTLS("./final_csr.crt",        "./final_private.key"))  }

              客戶端代碼請求時需要帶上自己的證書

              func main() {     cliCrt, err := tls.LoadX509KeyPair("./client_csr.crt", "./client_private.key")     if err != nil {        fmt.Println("Loadx509keypair err:", err)        return     }     tr := &http.Transport{        TLSClientConfig: &tls.Config{           Certificates: []tls.Certificate{cliCrt},        },     }     client := &http.Client{Transport: tr}     resp, err := client.Get("https://www.lanpangzi.com")     if err != nil {        fmt.Println("Get error:", err)        return     }     defer resp.Body.Close()     body, err := ioutil.ReadAll(resp.Body)     fmt.Println(string(body))  }

              這樣就完成了一個https的雙向認證。

              關鍵詞:
              責任編輯:zN_2843