HTTPS 與 TLS 的真實運作方式:握手過程詳解
你加裝了憑證,瀏覽器顯示鎖頭圖示,然後就繼續往下走了。直到某天,一個 API 客戶端因 SSL_ERROR_RX_RECORD_TOO_LONG 而失敗,行動應用程式拒絕你的憑證鏈,或是啟用 HSTS 後出現重新導向迴圈。突然間,HTTPS 變成了一個黑箱。
本文要打開這個黑箱。我們將逐步說明 TLS 握手中實際發生的事、憑證存在的理由、對稱與非對稱加密各自扮演的角色,以及如何在生產環境中除錯你確實會遇到的失敗情況。
HTTPS 究竟是什麼
HTTPS 並不是一個獨立的協定。它就是在 TLS 加密連線上傳送的普通 HTTP。URL 的 scheme 改變了,預設連接埠從 80 變成 443,其他一切——方法、標頭、狀態碼、Cookie——行為都相同。
TLS 在 TCP 之上提供三項保證:
- 機密性——中間人無法讀取流量。
- 完整性——傳輸過程中的竄改會被偵測到。
- 認證——客戶端可以驗證它正在與擁有該網域的伺服器通訊。
第三項保證是最常被低估的。沒有認證的加密毫無價值:能攔截流量的攻擊者只需出示自己的金鑰即可。
TLS 1.3 握手,逐步解析
與 TLS 1.2 相比,TLS 1.3 大幅簡化了握手過程。以下是瀏覽器與伺服器之間的流程:
- ClientHello——客戶端發送支援的 TLS 版本、加密套件、一個隨機值,以及一個 Diffie-Hellman 群組(例如 X25519)的金鑰分享。
- ServerHello——伺服器選擇一個加密套件並發送自己的金鑰分享。雙方現在可以推導出相同的共享密鑰。
- 加密擴展與憑證——伺服器發送其憑證鏈,此時已使用推導出的金鑰加密。
- Finished——雙方對握手會話記錄發送 MAC,以證明沒有任何竄改。
- 應用程式資料——HTTP 請求和回應透過已建立的會話流動。
在 TLS 1.3 中,完整握手只需一個往返,而會話恢復可以達到零往返。TLS 1.2 則需要兩個往返,之後才能傳送任何應用程式資料。
為什麼憑證在金鑰推導之後才發送
在 TLS 1.3 中,憑證是加密的。這意味著被動觀察者無法僅從憑證看出客戶端正在造訪哪個網域,從而減少資訊洩漏。這也意味著那些預期在第一個封包中看到明文憑證的除錯工具會感到困惑。
對稱與非對稱加密
一個常見的誤解是 HTTPS 使用伺服器的公開金鑰加密所有內容。事實並非如此。非對稱密碼學速度慢且受大小限制;它僅用於建立共享密鑰。
| 階段 | 密碼學 | 目的 |
|---|---|---|
| 金鑰交換 | 非對稱(ECDHE) | 在不發送共享密鑰的情況下達成協議 |
| 認證 | 非對稱簽章 | 證明伺服器擁有憑證金鑰 |
| 大量資料 | 對稱(AES-GCM 或 ChaCha20-Poly1305) | 為 HTTP 流量提供快速加密與完整性 |
Diffie-Hellman 金鑰交換具有一個稱為前向保密的寶貴特性:即使伺服器的私鑰日後洩漏,過去記錄的會話也無法被解密,因為會話金鑰從未從該長期金鑰推導而來。
憑證裡面有什麼
X.509 憑證將公開金鑰綁定到網域名稱。它包含:
- 主體(網域)和主體別名(SAN),這才是瀏覽器實際檢查的內容。
- 公開金鑰及其演算法。
- 有效期限。
- 簽發者以及來自該簽發者的簽章。
瀏覽器信任一組固定的根憑證授權單位。你的伺服器發送一個中繼憑證,瀏覽器則從你的葉憑證向上建立一條鏈,直到受信任的根憑證。如果缺少中繼憑證,桌面瀏覽器可能仍能運作,因為它們會快取中繼憑證,而行動客戶端和命令列工具則會失敗。這是生產環境中最常見的 TLS 錯誤。
實用除錯步驟
當 HTTPS 行為異常時,由外而內逐步檢查。
- 檢查憑證鏈:使用
openssl s_client -connect example.com:443 -servername example.com -showcerts。確認伺服器發送完整的鏈,而不僅僅是葉憑證。 - 檢查到期日——過期的憑證會導致硬性失敗,沒有變通辦法。
- 檢查主機名稱——SAN 列表必須包含客戶端請求的確切主機名稱,包括任何
www前綴。 - 檢查協定版本——如果舊版客戶端僅支援 TLS 1.0,而你的伺服器要求 TLS 1.2+,握手會在 HTTP 之前就失敗。
- 檢查重新導向——從 HTTPS 重新導向到 HTTP 再導回會造成迴圈;應在伺服器配置中修正,而非應用程式碼中。
# 檢查憑證鏈和協商後的協定
openssl s_client -connect example.com:443 \
-servername example.com -showcerts < /dev/null 2>&1 | \
openssl x509 -noout -subject -issuer -dates
# 測試特定的 TLS 版本
openssl s_client -connect example.com:443 -tls1_2 < /dev/null在 Nginx 中,ssl_certificate 指令應指向一個檔案,其中包含葉憑證,後面接著中繼憑證,順序就是如此。以錯誤的順序串接它們會產生難以察覺的鏈錯誤。
HSTS 與設定錯誤的代價
HTTP 嚴格傳輸安全性(HSTS)告訴瀏覽器在給定期間內對某個網域使用 HTTPS。它很強大且不留餘地:一旦瀏覽器看到較長的 max-age,即使你之後在伺服器上停用 HTTPS,它也會拒絕純 HTTP。
從較短的 max-age 開始,確認一切正常後再增加。只有在每個子網域都提供有效的 HTTPS 後,才加入 includeSubDomains。預載幾乎是永久性的——只有在你確定時才請求。
常見錯誤設定
- 混合內容——HTTPS 頁面透過 HTTP 載入指令碼或圖片。瀏覽器會封鎖主動混合內容,並對被動內容發出警告。
- 弱加密套件——為了你不再需要的相容性而保留 CBC 模式加密套件和舊協定版本。
- 缺少 OCSP 或 CRL 處理——撤銷檢查在實務上不可靠;短生命週期憑證是更穩健的答案。
- 憑證自動化缺口——續約正常運作,但缺少讓伺服器載入新憑證的重新載入步驟。
將憑證續約視為部署管道,而非行事曆提醒。如果續約需要人工介入,它最終會在最糟糕的時刻失敗。
常見問題
HTTPS 會加密我造訪的網域名稱嗎?
網域會出現在 TLS SNI 擴展中,通常在網路上是可見的。路徑和查詢字串是加密的,但網路觀察者通常可以看到你連線到哪個主機。
為什麼我的網站在瀏覽器中正常,但在 curl 或行動應用程式中失敗?
幾乎總是缺少中繼憑證。瀏覽器通常會快取先前造訪的中繼憑證,而 curl 和許多行動 HTTP 堆疊要求伺服器發送完整的鏈。
TLS 1.3 一定比 TLS 1.2 好嗎?
對於現代客戶端,是的——更少的往返、更小的加密套件集,且預設提供前向保密。僅在你仍服務無法協商 1.3 的客戶端時,才保持 TLS 1.2 啟用。
當你需要檢查終止 TLS 的伺服器日誌時,快速瀏覽 Nginx 日誌分析器 可以幫助你在握手失敗、重新導向迴圈和不尋常的客戶端行為變成故障之前發現它們。