跳轉到

6.3 憑證與 PKI 簡介#

概念說明#

6.2 節的結尾留下一個缺口:你拿到一把公鑰,憑什麼相信它是「google.com 的」而不是中間人的?公鑰基礎建設(Public Key Infrastructure, PKI)就是回答這個問題的信任體系:由受信任的第三方——憑證授權機構(Certificate Authority, CA)——用自己的簽章,把「身分 ⇄ 公鑰」綁定成憑證(certificate)。

信任鏈的樣子:

根 CA(root,離線保存,預裝在作業系統/瀏覽器)
  └─ 簽發 中繼 CA(intermediate)
        └─ 簽發 終端憑證(你的網站、你的程式)

驗證者沿著鏈逐層用上一層公鑰驗下一層簽章,直到抵達「預裝在系統裡」的信任錨(trust anchor)。

X.509 憑證的關鍵欄位#

欄位 意義
Subject 憑證主體(網域或組織)
Subject Public Key Info 主體的公鑰(如 EC P-256)
Issuer 簽發它的 CA
Validity 有效期間(過期即失效)
SAN(Subject Alternative Name) 覆蓋的網域清單,現代標準
Signature Algorithm / Signature CA 的簽章(如 RSA-PSS、ECDSA)

演算法步驟(瀏覽器驗證 HTTPS 網站時)#

  1. 取得伺服器送來的憑證鏈(終端憑證 + 中繼憑證)。
  2. 用系統信任庫中的公鑰驗證鏈上每層簽章,直至信任錨。
  3. 檢查:網域名稱符合 SAN、尚未過期、簽章演算法與金鑰強度合格。
  4. 查詢撤銷狀態:CRL(撤銷清單)或 OCSP(即時查詢),確認憑證未被提前作廢(現代改用 OCSP Stapling:伺服器附上帶時間戳的查詢結果)。
  5. 全部通過 → 進行 TLS 金鑰交換(9.1 節)。

Python 實作(cryptography 套件)#

# pip install cryptography
from cryptography import x509
from cryptography.x509.oid import NameOID
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
import datetime

# 1) 自簽一張迷你 CA 憑證(教學環境)
ca_key = ec.generate_private_key(ec.SECP256R1())
ca_name = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "My Mini CA")])
ca_cert = (x509.CertificateBuilder()
    .subject_name(ca_name).issuer_name(ca_name)
    .public_key(ca_key.public_key())
    .serial_number(x509.random_serial_number())
    .not_valid_before(datetime.datetime.utcnow())
    .not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=365))
    .add_extension(x509.BasicConstraints(ca=True, path_length=1), critical=True)
    .sign(ca_key, hashes.SHA256()))

# 2) 用它簽發一張「伺服器」憑證
srv_key = ec.generate_private_key(ec.SECP256R1())
srv_cert = (x509.CertificateBuilder()
    .subject_name(x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, "example.test")]))
    .issuer_name(ca_cert.subject)
    .public_key(srv_key.public_key())
    .serial_number(x509.random_serial_number())
    .not_valid_before(datetime.datetime.utcnow())
    .not_valid_after(datetime.datetime.utcnow() + datetime.timedelta(days=30))
    .sign(ca_key, hashes.SHA256()))

# 3) 驗證鏈:用 CA 公鑰驗伺服器憑證的簽章
ca_cert.public_key().verify(
    srv_cert.signature, srv_cert.tbs_certificate_bytes,
    ec.ECDSA(srv_cert.signature_hash_algorithm))
print("憑證鏈驗證通過:CA → example.test")

安全性分析#

  • 信任模型的弱點:你信任的不只是你造訪的網站,而是系統裡所有 CA 對全世界的擔保——任何一家 CA 被入侵或作惡(2011 年 DigiNotar 事件被駭後簽發 google.com 憑證,導致其破產),都能偽造任何網站。防禦機制:憑證透明化(Certificate Transparency,公開記錄所有簽發、可監督濫發)、Certificate Pinning(應用程式內釘選特定公鑰,如 HSTS/HPKP 的繼承者)。
  • 過度信任的反面:企業環境安裝自訂根憑證(用於解密代理)等於把全部 HTTPS 流量交給它——這是合法的中間人,也是駭客常用的持久化手法。
  • 去中心化挑戰:區塊鏈式 PKI、DNSSEC、Signal 的 TOFU(trust on first use)模型都是對「單一 CA 體系」的反思(9.2 節會再遇到)。

練習#

  1. 執行上面的程式碼,把 srv_cert 匯出為 PEM,用 openssl x509 -text 檢視欄位,找出 Subject、Issuer、公鑰演算法。
  2. 故意把 not_valid_after 設成過去時間,再實作一個「驗證函數」檢查有效期,觀察它如何拒絕。
  3. 用瀏覽器查看任意 HTTPS 網站的憑證鏈,畫出「根 CA → 中繼 → 網站」的樹狀圖,並標出每層使用的簽章演算法。