证书和 PKI:在互联网上建立信任
Published: Sat Feb 01 2025 | Modified: Sat Feb 07 2026 , 9 minutes reading.
1. 为什么要关心这个问题?
你访问 https://bank.example.com。你的浏览器显示了一个锁标志。它怎么知道这真的是你的银行而不是攻击者?
答案是证书——将公钥绑定到身份的数字文档。没有它们,HTTPS 将毫无意义。
理解 PKI 能帮助你:
- 调试 TLS 证书错误
- 为你的应用程序设置 HTTPS
- 理解为什么「直接点击忽略警告」是危险的
- 知道何时以及如何使用客户端证书
2. 定义
数字证书(具体来说是 X.509)绑定了:
- 公钥
- 身份(域名、组织等)
- 来自可信机构的签名
公钥基础设施 (PKI) 是一个系统,包括:
- 颁发证书的证书颁发机构 (CA)
- 颁发和撤销证书的政策
- 包含可信根证书的信任库
证书 = 「我,可信 CA,证明
公钥 XYZ 属于 bank.example.com」
+ CA 的签名3. 证书内部有什么
X.509 证书结构
Certificate:
Version: 3
Serial Number: 04:00:00:00:00:01:2F:4E:E1:5B:3D
Signature Algorithm: sha256WithRSAEncryption
Issuer: CN=DigiCert Global Root CA
Validity:
Not Before: Mar 08 12:00:00 2023 GMT
Not After: Mar 08 12:00:00 2024 GMT
Subject: CN=www.example.com
Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
EC Public Key:
pub: 04:AB:CD:...
ASN1 OID: prime256v1
X509v3 Extensions:
Subject Alternative Name:
DNS:www.example.com
DNS:example.com
Basic Constraints:
CA:FALSE
Key Usage:
Digital Signature, Key Encipherment
Extended Key Usage:
TLS Web Server Authentication关键字段解释
Subject:证书是给谁的
CN=www.example.com(通用名称)
O=Example Inc(组织)
Issuer:谁签署了证书
为此身份担保的 CA
Validity:证书何时有效
Not Before / Not After 日期
Subject Alternative Name (SAN):
涵盖的其他域名
现代浏览器要求这个
Key Usage:
密钥可以用于什么
数字签名、密钥加密等
Extended Key Usage:
更具体的用途
TLS Web 服务器认证
代码签名等4. 信任链
信任如何工作
根 CA(自签名,在浏览器信任库中)
│
│ 签署
▼
中间 CA 证书
│
│ 签署
▼
终端实体证书(你的网站)
浏览器验证:
1. 终端证书由中间 CA 签署
2. 中间 CA 由根 CA 签署
3. 根 CA 在信任库中
4. 所有证书有效(未过期、未撤销)为什么需要中间 CA?
安全性:根密钥极其珍贵
- 存储在离线 HSM 中
- 很少使用(只用于签署中间 CA)
- 如果泄露,整个 PKI 崩溃
灵活性:
- 如果中间 CA 泄露可以撤销
- 不同中间 CA 用于不同目的
- 更短的有效期
纵深防御:
- 多一层需要攻破
- 可以轮换中间 CA 而不改变根 CA5. 证书颁发机构 (CA)
CA 做什么
1. 验证证书申请者的身份
- 域名验证 (DV):证明你控制域名
- 组织验证 (OV):验证组织存在
- 扩展验证 (EV):广泛的法律验证
2. 颁发证书
- 用 CA 的私钥签名
- 包含适当的约束
3. 维护撤销信息
- CRL(证书撤销列表)
- OCSP(在线证书状态协议)
4. 保护他们的密钥
- 根密钥在 HSM 中
- 严格的访问控制
- 定期审计主要 CA
商业:
- DigiCert
- Sectigo(前身 Comodo)
- GlobalSign
- Entrust
免费:
- Let's Encrypt(自动化 DV 证书)
- ZeroSSL
私有:
- 你组织的内部 CA
- 用于内部服务、客户端证书6. 获取证书
使用 Let’s Encrypt(免费、自动化)
# 安装 certbot
sudo apt install certbot
# 获取证书(独立模式)
sudo certbot certonly --standalone -d example.com -d www.example.com
# 证书文件:
# /etc/letsencrypt/live/example.com/fullchain.pem (证书 + 中间证书)
# /etc/letsencrypt/live/example.com/privkey.pem (私钥)
# 自动续期(每天运行两次)
sudo certbot renew使用 ACME 协议(编程方式)
# 使用 acme 库的概念示例
from acme import client, messages
from cryptography.hazmat.primitives.asymmetric import ec
# 生成账户密钥
account_key = ec.generate_private_key(ec.SECP256R1())
# 创建 ACME 客户端
acme_client = client.ClientV2(
directory='https://acme-v02.api.letsencrypt.org/directory',
key=account_key
)
# 请求域名证书
order = acme_client.new_order(['example.com'])
# 完成验证(证明域名所有权)
for auth in order.authorizations:
challenge = get_http_challenge(auth)
# 部署验证响应
# ...
acme_client.answer_challenge(challenge)
# 完成并获取证书
certificate = acme_client.finalize_order(order, csr)7. 证书验证
浏览器检查什么
1. 签名链有效
└─ 每个证书由其颁发者签名
2. 根 CA 可信
└─ 在浏览器/操作系统信任库中
3. 证书未过期
└─ 当前时间在有效期内
4. 证书未被撤销
└─ 检查 CRL 或 OCSP
5. 域名匹配
└─ Subject 或 SAN 包含该域名
6. 密钥用途适当
└─ TLS Web 服务器认证
7. 证书策略满足
└─ 各种 X.509 约束验证代码示例
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from cryptography.x509.oid import ExtensionOID
import ssl
import socket
import datetime
def validate_server_certificate(hostname, port=443):
"""验证服务器的证书"""
# 获取证书
context = ssl.create_default_context()
with socket.create_connection((hostname, port)) as sock:
with context.wrap_socket(sock, server_hostname=hostname) as ssock:
der_cert = ssock.getpeercert(binary_form=True)
cert = x509.load_der_x509_certificate(der_cert, default_backend())
# 检查有效期
now = datetime.datetime.utcnow()
if now < cert.not_valid_before or now > cert.not_valid_after:
return False, "证书已过期或尚未生效"
# 检查主题备用名称
try:
san = cert.extensions.get_extension_for_oid(
ExtensionOID.SUBJECT_ALTERNATIVE_NAME
)
names = san.value.get_values_for_type(x509.DNSName)
if hostname not in names:
return False, f"主机名 {hostname} 不在 SAN 中"
except x509.ExtensionNotFound:
return False, "没有 SAN 扩展"
return True, "证书有效"
# 检查
valid, message = validate_server_certificate("www.google.com")
print(f"有效: {valid}, 消息: {message}")8. 证书撤销
为什么撤销很重要
需要撤销的场景:
- 私钥泄露
- 证书颁发错误
- 域名所有权变更
- 组织不再可信
没有撤销检查:
- 持有被盗密钥的攻击者可以冒充网站
- 直到证书自然过期(最长 398 天!)CRL vs OCSP
CRL(证书撤销列表):
┌────────────────────────────────┐
│ 已撤销序列号的大列表 │
│ 定期下载 │
│ 可能有几兆字节 │
│ 更新之间有延迟 │
└────────────────────────────────┘
OCSP(在线证书状态协议):
┌────────────────────────────────┐
│ 查询:「这个证书被撤销了吗?」 │
│ 响应:「是/否」 │
│ 实时 │
│ 隐私问题(CA 看到查询) │
└────────────────────────────────┘
OCSP Stapling:
┌────────────────────────────────┐
│ 服务器获取 OCSP 响应 │
│ 附加到 TLS 握手 │
│ 客户端获得新鲜证明 │
│ 没有隐私泄露给 CA │
└────────────────────────────────┘9. 常见证书问题
证书错误和解决方案
错误:NET::ERR_CERT_DATE_INVALID
原因:证书已过期或尚未生效
修复:续期证书,检查服务器时钟
错误:NET::ERR_CERT_AUTHORITY_INVALID
原因:CA 不可信
修复:使用知名 CA,或安装根证书
错误:NET::ERR_CERT_COMMON_NAME_INVALID
原因:域名不在证书中
修复:获取包含正确 SAN 的证书
错误:SSL_ERROR_BAD_CERT_DOMAIN
原因:主机名不匹配
修复:使用正确的主机名访问
错误:证书链不完整
原因:缺少中间证书
修复:配置服务器使用完整链调试证书
# 查看证书详情
openssl s_client -connect example.com:443 -showcerts
# 检查证书日期
openssl s_client -connect example.com:443 2>/dev/null | \
openssl x509 -noout -dates
# 检查证书链
openssl s_client -connect example.com:443 -showcerts 2>/dev/null | \
openssl x509 -noout -issuer -subject
# 验证证书
openssl verify -CAfile ca-bundle.crt certificate.pem10. 客户端证书
用途
服务器证书:服务器向客户端证明身份
客户端证书:客户端向服务器证明身份
用例:
- 服务间认证的双向 TLS (mTLS)
- 无需密码的用户认证
- 物联网设备认证
- VPN 认证设置 mTLS
import ssl
import socket
# 服务器端
server_context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
server_context.load_cert_chain('server.crt', 'server.key')
server_context.load_verify_locations('client-ca.crt')
server_context.verify_mode = ssl.CERT_REQUIRED # 要求客户端证书
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.bind(('0.0.0.0', 8443))
sock.listen()
with server_context.wrap_socket(sock, server_side=True) as ssock:
conn, addr = ssock.accept()
# conn.getpeercert() 返回客户端证书
# 客户端
client_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
client_context.load_cert_chain('client.crt', 'client.key')
client_context.load_verify_locations('server-ca.crt')
with socket.create_connection(('server.example.com', 8443)) as sock:
with client_context.wrap_socket(sock, server_hostname='server.example.com') as ssock:
# 使用双向认证连接
pass11. 自签名证书
何时使用
适合:
- 开发和测试
- 内部服务(使用私有 CA)
- 学习和实验
不适合:
- 公开网站
- 被外部客户端使用的生产 API
- 任何信任重要的地方创建自签名证书
# 生成私钥
openssl genrsa -out server.key 2048
# 生成自签名证书
openssl req -new -x509 -key server.key -out server.crt -days 365 \
-subj "/CN=localhost"
# 带 SAN 扩展(现代浏览器要求)
openssl req -new -x509 -key server.key -out server.crt -days 365 \
-subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"12. 证书透明度
它解决什么问题
如果 CA 为你的域名颁发了证书会怎样?
- 你可能永远不知道
- 攻击者可以拦截流量
- CA 泄露是灾难性的
证书透明度:
- 所有证书记录到公共日志
- 你可以监控你的域名的日志
- 错误颁发的证书可被检测如何工作
1. CA 颁发证书
2. CA 提交到 CT 日志
3. 日志返回签名证书时间戳 (SCT)
4. 证书包含 SCT
5. 浏览器验证 SCT 存在
6. 域名所有者监控日志
工具:
- crt.sh(搜索 CT 日志)
- Google 证书透明度
- Cert Spotter(监控)13. 本章小结
三点要记住:
证书将密钥绑定到身份。 证书是 CA 的签名声明,表明公钥属于特定域名或实体。
信任来自根 CA。 你的浏览器信任约 100 个根 CA。那些 CA 签署中间 CA,中间 CA 签署终端证书。如果任何环节断裂,信任就失败了。
证书管理是运营工作。 续期、撤销和链配置是持续的工作。尽可能使用 Let’s Encrypt 自动化。
14. 下一步
我们已经介绍了非对称密码学、签名和证书。但还有另一个基本原语:确保数据没有被修改,而不需要证明谁修改了它。
在下一篇文章中:HMAC 和数据完整性——当你已经共享密钥时,如何用对称密钥检测篡改。
