HTTPS虐我千百遍,我卻待她如初戀!

朱小廝的部落格發表於2022-12-08

HTTPS虐我千百遍,我卻待她如初戀!

本篇將討論 HTTPS 的加解密原理,很多人都知道 RSA,以為 HTTPS=RSA,使用 RSA 加解密資料,實際上這是不對的。

HTTPS 是使用 RSA 進行身份驗證和交換金鑰,然後再使用交換的金鑰進行加解密資料。

身份驗證是使用 RSA 的非對稱加密,而資料傳輸是雙方使用相同的金鑰進行的對稱加密。那麼,什麼是對稱加密和非對稱加密

對稱加密和非對稱加密

假設隔壁小王想要約小紅出來,但是他不想讓小明知道,於是他想用對稱加密給小紅傳了個小紙條。

如下圖所示:
HTTPS虐我千百遍,我卻待她如初戀!

他想傳送的資料是"Meet at 5:00 PM"(5 點見面,如果是中文的話可以使用 UTF-8 編碼),加密方式是直接在 ASCII 表進行左移或右移。

他的金鑰是 3,表示在 ASCII 表往後移 3 位,就會變成"Phhw#dw#8=33#SP",這樣一般人如果截獲了不知道是什麼意思的。

但是我們可以想一下,如果既然他可以截獲你的資料,自然也可以截獲你的金鑰,進而進行解密。

如下圖所示:

HTTPS虐我千百遍,我卻待她如初戀!

所以小王打算用非對稱加密,非對稱加密的特點是雙方都有自己的公鑰和私鑰對,其中公鑰發給對方,金鑰不交換自己保管不洩漏。

如下圖所示:
HTTPS虐我千百遍,我卻待她如初戀!
其中小紅的公鑰為:
public_key = (N, e) = (3233, 17)

她把公鑰發給了小明,她自己的私鑰為:

private_key = (N, e) = (3233, 2753)
這裡注意公鑰和私鑰都是兩個數,N 通常是一個大整數,e 表示一個冪指數。現在小王想給小紅髮訊息,於是他用小紅的公鑰進行加密,怎麼加密呢?

他要傳送的第一個字母為 t=“M”,“M”的 ASCII 編碼為 77,77 的加密過程如下計算:

T = 77 ^ e  % N = 77 ^ 17 % 3233 = 3123
把 77 做 e 次冪然後模以 N,便得到了 T=3123,然後把這個數發給小紅(其他字母按同樣方式處理)。

小紅收到 T 之後便用她的私鑰進行解密,計算如下:

t = T ^ e % N = 3123 ^ 2753 % 3233 = 77
計算方法是一樣的,這樣便把 T 還原成了 t,只要公私鑰配對,便可透過一些數學公式證明上面的推算是成立的。這個就是 RSA 的加解密原理,如果無法知道私鑰便無法進行正確解密。
反過來,使用私鑰進行加密,公鑰進行解密也是可行的。那麼 HTTPS 是怎麼利用 RSA 進行加解密的呢,我們從 HTTPS 連線建立過程說起。

HTTPS 連線建立過程

HTTPS 主要有以下作用:
  • 驗證服務方身份,如我訪問 google.com 的時候連的確實就是谷歌伺服器

  • 防止資料被劫持,例如有些運營商會給 http 的頁面插入廣告

  • 防止敏感資料被竊取篡改等

正如 openssl 的註釋所說,這是防止中間人攻擊的唯一方法:

HTTPS虐我千百遍,我卻待她如初戀!

我們以 MDN(https://developer.mozilla.org)的網站為例,然後用 wireshark 抓包,觀察 HTTPS 連線建立的過程。

如下圖所示:

HTTPS虐我千百遍,我卻待她如初戀!
首先是 TCP 三次握手,然後客戶端(瀏覽器)發起一個 HTTPS 連線建立請求,客戶端先發一個 Client Hello 的包,然後服務端響應一個 Server Hello。
接著再給客戶端傳送它的證書,然後雙方經過金鑰交換,最後使用交換的金鑰加行加解密資料。

在 Client Hello 裡面客戶端會告知服務端自己當前的一些資訊,如下圖所示:

HTTPS虐我千百遍,我卻待她如初戀!

包括客戶端要使用的 TLS 版本,支援的加密套裝,要訪問的域名,給服務端生成的一個隨機數(Nonce)等。
需要提前告知伺服器想要訪問的域名以便伺服器傳送相應的域名的證書過來,因為此時還沒有發生 HTTP 請求。

服務端在 Server Hello 裡面會做一些響應:

HTTPS虐我千百遍,我卻待她如初戀!

服務端選中的加密套裝叫 TLSECDHERSAWITHAES128GCM_SHA256,這一串的意思是:
  • 金鑰交換使用 ECDHE

  • 證書籤名演算法 RSA

  • 資料加密使用 AES 128 GCM

  • 簽名校驗使用 SHA256

接著服務給客戶端發來了 4 個證書:

HTTPS虐我千百遍,我卻待她如初戀!
第一個證書的公用名(common name)就是我們當前訪問的域名 developer.mozilla.org。
如果公用名是 *.mozilla.org 的話那麼這個證書便能給 mozilla.org 的所有二級子域名使用。
第二個證書是第一個證書的簽發機構(CA)的證書,它是 Amazon,也就是說 Amazon 會用它的私鑰給 developer.mozilla.org 進行簽名。
依此類推,第三個證書會給第二個證書籤名,第四個證書會給第三個證書籤名,並且我們可以看到第四個證書是一個根(Root)證書。

一個證書裡面會有什麼東西呢,我們可以展開第一個證書看一下,如下圖所示:

HTTPS虐我千百遍,我卻待她如初戀!
證書包含三部分內容:
  • tbsCertificate(to be signed certificate)待簽名證書內容

  • 證書籤名演算法

  • CA 給的簽名

也就是說 CA 會用它的私鑰對 tbsCertificate 進行簽名,並放在簽名部分。為什麼證書要簽名呢?簽名是為了驗證身份。

身份驗證

我們先來看一下 tbsCertificate 裡面有什麼內容,如下圖所示:

HTTPS虐我千百遍,我卻待她如初戀!
它裡面包括了證書的公鑰、證書的適用公用名、證書的有效期還有它的簽發者等資訊。

Amazon 的證書也具備上述結構,我們可以把 Amazon 證書的公鑰拷出來,如下圖所示:

HTTPS虐我千百遍,我卻待她如初戀!

中間有一些填充的數字,用灰色字表示。可以看到N通常是一個很大的整數(二進位制 2048 位),而 e 通常為 65537。

然後我們用這個 CA 的公鑰對 mozilla.org 的證書籤名進行解密,方法和上面的類似:

HTTPS虐我千百遍,我卻待她如初戀!

取解密後的數字 decrypted 的十六進位制的末 64 位,即為二進位制 256 位的 SHA 雜湊簽名。

接下來我們手動計算一下 tbsCertificate 的 SHA256 雜湊值,方法是在 wireshark 裡面把 tbsCertificate 匯出一個原始二進位制檔案:

HTTPS虐我千百遍,我卻待她如初戀!

然後再使用 openssl 計算它的雜湊值,如下所示:

liyinchengs-MBP:https liyincheng$ openssl dgst -sha256 ~/tbsCertificate.binSHA256(/Users/liyincheng/tbsCertificate.bin)= 5e300091593a10b944051512d39114d56909dc9a504e55cfa2e2984a883a827d

我們發現手動計算的雜湊值和加密後的證書裡的雜湊值一致!說明只有知道了 Amazon 私鑰的人才能正確地對 mozilla.org 的證書籤名,因為公私鑰是唯一匹配的。

因此我們驗證了第一個證書 mozilla.org 確實是由第二個證書 Amazon 簽發的,使用同樣的方式,我們可以驗證 Amazon 是由第三個簽發的,第三個是由第四個根證書籤發。

並且第四個證書是根證書,它是內建於作業系統的(透過 Mac 的 keychain 工具可以檢視):

HTTPS虐我千百遍,我卻待她如初戀!

假如 Hacker 透過 DNS 欺騙之類的方式把你訪問的域名指向了他的機器,然後他再偽造一個證書。

但是由於根證書都是內建於作業系統的,所以它改不了簽名的公鑰,並且它沒有正確的私鑰,只能用自己的私鑰,由於公私鑰不配對,很難保證加解密後的資訊一致。
或者直接把瀏覽器拿到的證書搬到他自己的伺服器?這樣再給瀏覽器發的證書便是一模一樣,但是由於他不知道證書的私鑰,所以無法進行後續的操作,因此這樣是沒有意義的。
這個就是 HTTPS 能夠驗證身份的原理。另外一個例子是 SSH,需要手動驗證簽名是否正確。

例如透過打電話或者發郵件等方式告知伺服器的簽名,與自己算的證書的簽名是否一致,如果一致說明證書沒有被篡改過(如證書的公鑰沒有被改為 Hacker 的公鑰):

HTTPS虐我千百遍,我卻待她如初戀!

上面展示的便是自己手動計算的值,拿這個值和之前的值進行比較是否相等便可知發過來的證書是否被修改過。

那麼,為什麼不直接使用 RSA 的金鑰對進行加密資料?因為 RSA 的金鑰對數值太大,不太合適頻繁地加解密資料,所以需要更小的金鑰。
另一個原因是服務端沒有瀏覽器或者客戶端的金鑰,無法向瀏覽器傳送加密的資料(不能用自己的私鑰加密,因為公鑰是公開的)。所以需要進行金鑰交換。

金鑰交換

金鑰交換的方式有兩種:RSA 和 ECDHE,RSA 的方式比較簡單,瀏覽器生成一把金鑰,然後使用證書 RSA 的公鑰進行加密發給服務端,服務再使用它的金鑰進行解密得到金鑰,這樣就能夠共享金鑰了。
它的缺點是攻擊者雖然在傳送的過程中無法破解,但是如果它儲存了所有加密的資料,等到證書到期沒有被維護之類的原因導致私鑰洩露,那麼它就可以使用這把私鑰去解密之前傳送過的所有資料。

而使用 ECDHE 是一種更安全的金鑰交換演算法。如下圖所示,雙方透過 ECDHE 進行金鑰交換:

HTTPS虐我千百遍,我卻待她如初戀!

ECDHE 的全稱是 Elliptic Curve Diffie–Hellman key Exchange 橢圓曲線迪非-赫爾曼金鑰交換,它是對迪非-赫爾曼金鑰交換演算法的改進。

這個演算法的思想如下圖所示:


HTTPS虐我千百遍,我卻待她如初戀!
為了得到共享秘鑰 K,甲用它的私鑰計算一個數 g^a,傳送給乙,乙的私鑰為 b,乙便得到 K= g^a^b,同時傳送 g^b 給甲,甲也得到了 K=g^b^a。
這個應該比較好理解,而引入橢圓曲線加密能夠提高破解難度。

橢圓曲線加密

現在的證書的簽名演算法有兩種:RSA 和新起的 EC。如下圖所示,google.com 便是使用的 ECC 證書:

HTTPS虐我千百遍,我卻待她如初戀!



我們上面討論的便是 RSA,破解 RSA 的難點在於無法對公鑰的 N 進行質數分解。
如果你能對證書的 N 拆成兩個質數相乘,便可推算出證書的私鑰,但是在當前的計算能力下是不可能的。而 ECC 的破解難點在於找到指定點的係數。

如下圖所示,有一條橢圓曲線方程:

y ^ 3 = x ^ 2 + ax + b:

HTTPS虐我千百遍,我卻待她如初戀!

給定一個起點 G(x,y,現在要計算點 P=2G 的座標,其過程是在 G 點上做一條線與曲線相切於 -2G,做 -2G 相對於 x 軸的反射便得到 2G 點。

為了計算 3G 的座標,如下圖所示:

HTTPS虐我千百遍,我卻待她如初戀!

連線 2G 與 G 與曲線相郊於 -3G,再做反射得到 3G,同理計算 4G 便是連線 G 與 3G 再做反射。如果最後一個點和起點的連線垂直於 x 軸,說明所有的點已用完。

EC 的難點在於給定起點 G 和點 K:

K = kG
想要得到 K(K 足夠大)是一件很困難的事情。這個 K 便是私鑰,而 K=kG 便是公鑰。ECC 是怎麼加解密資料的呢?
假設要加密的資料為 m,把這個點當作x座標得到在曲線上的一個點 M,取定一個隨機數 r,計算點 C1=rG,C2=M+rK。

把這兩個點便是加密後的資料,發給對方,對方收到後使用私鑰 K 進行解密,過程如下:

M = C2 - rK = C2 - rkG = C2 - rkG = C2 - kC1
透過上面的計算便能還原得到 M,而不知道私鑰 K 的人是無法解密的。更多細節可見 Medium 的這篇文章《ECC elliptic curve encryption》。這樣我們便理解了 ECC 的原理,那麼怎麼利用 ECC 進行金鑰交換呢?

ECC 金鑰交換

原理很簡單,如下圖所示:

HTTPS虐我千百遍,我卻待她如初戀!

之前交換的是兩個冪次方的數,現在變成交換兩個曲線上的點。

而曲線方程是規定好的,例如 Curve X25519 使用的曲線方程為:

y^2 = x^3 + 486662x^2 + x

在金鑰交換裡面會指定所使用的曲線方程,如下圖所示:

HTTPS虐我千百遍,我卻待她如初戀!

mozilla.org 所使用的曲線方程為 secp256r1,這個也是比較流行的一個,它的引數比 Curve X25519 大很多。

金鑰交換也使用了證書的私鑰進行簽名,保證交換的金鑰不會被人篡改,只是這裡的私鑰是 mozilla 自己的私鑰。
也就是說從連線建立到現在都是明文傳輸的。接下來雙方傳送 Change Cipher Spec 的包通知,接下來的包都按照之前約定好的方式進行加密。至此整個安全連線建立完畢。

HTTPS 證書的應用

那麼是誰在做 HTTPS 加密呢?服務端通常是 Nginx、Apache 這些反向代理伺服器做的,而具體的業務伺服器不需要處理,客戶端通常是瀏覽器等做的加解密,Chrome 是使用 boringSSL 這個庫,fork 自 openssl。
我們透過 let’s encrypt 可以申請免費的 TLS 證書,每 3 個月需要手動續。
證書分為 3 種:DV、OV、EV,DV 適用於個人,OV 和 EV 需要身份稽核,EV 最高階。

EV 證書會在瀏覽器的位址列顯示證書的企業名稱:

HTTPS虐我千百遍,我卻待她如初戀!

但是新版的 Chrome 似乎把這個去掉了,所以我們開啟 medium 的控制檯可以看到一個提示:

As part of an experiment, Chrome temporarily shows only the lock icon in the address bar. Your SSL certificate with Extended Validation is still valid.

另外我們可以用 openssl 生成一個自簽名證書,執行以下命令:

openssl req -x509 -nodes -sha256 -days 365 -newkey rsa:2048 -keyout test.com.key -out test.com.crt

便會得到兩個檔案,test.com.crt 是證書,test.com.key 是證書的私鑰,如下圖所示:

HTTPS虐我千百遍,我卻待她如初戀!

然後把這兩個檔案給 Nginx 使用便能使用 HTTPS 訪問,如下程式碼所示:

    server {
        listen       443;
        server_name  test.com;
        ssl on;
        ssl_certificate    test.com.crt;
        ssl_certificate_key    test.com.key;
     }

可以把這個證書新增到系統證書裡面,這樣瀏覽器等便能信任,或者直接使用 mkcert 工具一步到位。

客戶端證書

還有一種證書叫客戶端證書,同樣需要向 CA 機構申請一個客戶端證書,和服務端 TLS 證書不一樣的地方是,服務端證書通常是和域名繫結的,而客戶端證書可以給本地的任意可執行檔案進行簽名。

簽名驗證演算法和上文討論的 TLS 證書一致。為什麼可執行檔案需要簽名呢,因為如果不簽名的話,系統會攔截安裝或者執行,如 Mac 雙擊一個未簽名的 dmg 包的提示:

HTTPS虐我千百遍,我卻待她如初戀!

直接不讓你執行了,而 Windows 也有類似的提示,Windows 是會給一個警告:

HTTPS虐我千百遍,我卻待她如初戀!

而當我們執行一個已簽名的 exe 檔案將會是正常的提示,如 Chrome 的提示:

HTTPS虐我千百遍,我卻待她如初戀!

綜上本文主要討論了對稱加密和非對稱加密的原理,並介紹瞭如何利用 RSA 對證書籤名的檢驗以驗證連線伺服器的身份,怎麼利用 ECC 進行資料加密和金鑰交換,介紹了下怎麼生成和使用 HTTPS 證書,並介紹了下客戶端證書。

相信看完本篇,會對 HTTPS 的加解密有一個較為全面的瞭解。

來源:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69940568/viewspace-2660267/,如需轉載,請註明出處,否則將追究法律責任。

相關文章