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 網站時)#
- 取得伺服器送來的憑證鏈(終端憑證 + 中繼憑證)。
- 用系統信任庫中的公鑰驗證鏈上每層簽章,直至信任錨。
- 檢查:網域名稱符合 SAN、尚未過期、簽章演算法與金鑰強度合格。
- 查詢撤銷狀態:CRL(撤銷清單)或 OCSP(即時查詢),確認憑證未被提前作廢(現代改用 OCSP Stapling:伺服器附上帶時間戳的查詢結果)。
- 全部通過 → 進行 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 節會再遇到)。
練習#
- 執行上面的程式碼,把
srv_cert匯出為 PEM,用openssl x509 -text檢視欄位,找出 Subject、Issuer、公鑰演算法。 - 故意把
not_valid_after設成過去時間,再實作一個「驗證函數」檢查有效期,觀察它如何拒絕。 - 用瀏覽器查看任意 HTTPS 網站的憑證鏈,畫出「根 CA → 中繼 → 網站」的樹狀圖,並標出每層使用的簽章演算法。