HTTPS 到底保护了什么
很多人以为 HTTPS 只是"加密",实际上它一次性提供三件事,三者缺一不可:
| 能力 | 解决的问题 | 依赖的技术 |
|---|---|---|
| 机密性 | 中间人不能直接读到传输内容 | 对称加密(AES-GCM / ChaCha20-Poly1305) |
| 完整性 | 数据被篡改能被立刻发现 | AEAD 认证标签 / HMAC |
| 身份认证 | 你连上的确实是 example.com 而不是假站 | 证书链 + CA 签名 |
第三点是新手最容易忽略的。没有身份认证的加密毫无意义——你和攻击者之间照样可以建立一条"加密"的通道,只是对端是攻击者而已。证书的本质,就是一个可信第三方(CA)为你"这个域名属于你"这件事背书。
想验证自己有没有把响应头配错,可以对照本站的 HTTP 头速查表 逐条检查;端口层面的常识(443、80 跳转)见 常见端口速查表。
TLS 1.3 握手时序
TLS 1.2 需要 2-RTT 才能开始发数据,TLS 1.3 把它压到 1-RTT,甚至 0-RTT(代价是重放风险)。完整流程如下:
客户端 服务端
| |
|--- (1) ClientHello ------------------------------>|
| 支持的 TLS 版本、密码套件列表 |
| key_share: 客户端的 (EC)DHE 公钥 |
| 支持的签名算法、ALPN(h2/http1.1) |
| SNI: 目标主机名 |
| |
|<-- (2) ServerHello -------------------------------|
| 选定的版本与密码套件 |
| key_share: 服务端的 (EC)DHE 公钥 |
| {证书链} ← 服务端身份认证 |
| {CertificateVerify} ← 用证书私钥签名握手摘要 |
| {Finished} |
| |
|--- (3) Finished + 应用数据 ---------------------->|
| |
|<== 应用数据(对称加密) =========================>|
几个关键点值得单独拎出来:
key_share在 ClientHello 阶段就发出去了。这是 TLS 1.3 比 1.2 快一个 RTT 的根本原因:密钥交换材料第一趟就带上,不等服务端 round trip。CertificateVerify才是真正的身份证明。证书本身是公开的,谁都能转发;服务端必须用证书对应的私钥,对截至此刻的握手记录做一次签名,客户端用证书里的公钥验签通过,才能证明"对端确实持有私钥"。- 证书是明文传输的。它本来就是公开信息,不需要保密,只需要防篡改(由签名保证)。所以不要在证书里放任何敏感信息。
- SNI 也是明文。观察者能看到你访问了哪个域名(这是 ECH / Encrypted Client Hello 要解决的问题,目前普及率仍有限)。
证书链:信任是怎么传递的
浏览器本地只预置了约一百多个根 CA 证书。你的证书不直接由根 CA 签,而是由中间 CA 签,中间 CA 再由根 CA 签。这么做是为了安全——根 CA 的私钥可以离线冷存储在硬件安全模块里,日常签发全部交给中间 CA,一旦中间 CA 泄露只需吊销它,不至于动摇整个根。
根 CA(离线存储,预置在操作系统/浏览器)
└── 中间 CA(在线签发,如 R3 / E1)
└── 叶子证书(你的域名,如 oltool.net)
验证时浏览器从叶子往上走,逐层验签,直到能连上本地某个根证书为止。这带来两个实操要点:
- 必须部署 fullchain,不能只发叶子证书。服务端要把中间证书一起发过去。漏了中间证书就是最常见的不信任故障——详见文末排查表。
- 交叉签名会造成"多路径"。同一个中间证书可能同时被两个根签过(新旧根过渡期),浏览器会尝试所有路径,只要有一条走得通就算可信。这解释了为什么某些老设备能访问、新设备反而报错。
证书类型怎么选
按验证等级
| 等级 | 验证内容 | 签发时间 | 地址栏表现 | 适用场景 |
|---|---|---|---|---|
| DV 域名验证 | 你能控制这个域名 | 秒级~分钟 | 小锁图标 | 博客、工具站、API、绝大多数站点 |
| OV 组织验证 | 域名 + 真实存在的组织(人工审核营业执照) | 1–3 天 | 小锁 + 证书里可查组织名 | 企业官网、B2B 场景 |
| EV 扩展验证 | 最严格的法律实体核查 | 1–2 周 | 现代浏览器已不再显示绿色公司名 | 金融、支付(合规要求时) |
EV 的实际价值已经大幅缩水:Chrome 与 Firefox 先后移除了地址栏的绿色组织名显示,用户看不出区别。除非你的合规审计明确要求 EV,否则 DV + 好的安全实践更划算。
按覆盖域名
| 类型 | 覆盖范围 | 示例 | 注意 |
|---|---|---|---|
| 单域名 | 一个精确主机名 | www.oltool.net |
通常自动附带裸域或 www,签发前确认 |
| 通配符 | 同一级下所有子域 | *.oltool.net |
不匹配多级:a.b.oltool.net 不在覆盖内 |
| 多域名 SAN | 任意多个主机名(可跨域名) | a.com + b.net + *.c.org |
清单写进 SAN 扩展,扩容需重签 |
通配符证书有个实践上的隐患:它让私钥的分发面变大。一张 *.example.com 的私钥要放到所有子域的机器上,任何一台失守都会波及全部子域,且吊销后影响面巨大。大型部署更推荐按服务签发独立证书 + 自动化管理,而不是图省事用一张通配。
获取证书:三条路径
| 路径 | 成本 | 有效期 | 自动化 | 适用 |
|---|---|---|---|---|
| Let's Encrypt / ACME | 免费 | 90 天 | 完善(HTTP-01 / DNS-01 / TLS-ALPN-01) | 公网 Web 服务,首选 |
| 商业 CA | 付费 | 最长约 1 年(持续缩短中) | 视厂商而定 | 需要 OV/EV、保险、IP 证书、合规审计 |
| 自签 / 私有 CA | 免费 | 自定 | 自己搭 | 内网、服务间 mTLS、开发测试 |
ACME 的验证方式怎么选:
HTTP-01:在http://<域名>/.well-known/acme-challenge/放一个文件让 CA 来取。简单,但要求 80 端口可达,且不能签发通配符证书。DNS-01:在 DNS 里加一条_acme-challenge的 TXT 记录。稍麻烦(需要 DNS 服务商 API 凭据),但支持通配符证书,且不要求服务器公网可达——内网机器也能签。生产环境推荐走这条。
私钥的生成可以完全在本地浏览器完成(不上传、不经过网络),比如用本站的 RSA 密钥生成;需要校验摘要或比对指纹时用 哈希生成,算法本身的强度对照见 哈希与加密算法速查表。
私钥与算法:RSA 还是 ECDSA
| 维度 | RSA 2048 | RSA 3072 | ECDSA P-256 | Ed25519 |
|---|---|---|---|---|
| 等效安全强度 | 112 bit | 128 bit | 128 bit | 128 bit |
| 公钥长度 | 256 B | 384 B | 64 B | 32 B |
| 握手开销 | 中 | 高 | 低 | 低 |
| 老客户端兼容 | 极好 | 好 | Android 4+/多数现代环境 | 较差 |
| 推荐度 | 仅作兼容回退 | 一般 | 首选 | 实验性 |
结论:优先 ECDSA P-256,兼容性包袱重时配一张 RSA 做双证书。Nginx 里就是两行,现代客户端自动协商到 ECDSA:
ssl_certificate /etc/letsencrypt/live/oltool.net/fullchain.pem; # ECDSA
ssl_certificate_key /etc/letsencrypt/live/oltool.net/privkey.pem;
ssl_certificate /etc/letsencrypt/live/oltool.net-rsa/fullchain.pem; # RSA 回退
ssl_certificate_key /etc/letsencrypt/live/oltool.net-rsa/privkey.pem;
Nginx 部署:一份能直接用的配置
更多上下文(反向代理、静态资源、location 匹配规则)见 Nginx 配置速查表。
server {
listen 80;
listen [::]:80;
server_name oltool.net www.oltool.net;
# ACME 挑战保持明文可访问,其余全部跳转 HTTPS
location /.well-known/acme-challenge/ { root /var/www/html; }
location / { return 301 https://$host$request_uri; }
}
server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name oltool.net www.oltool.net;
# fullchain 包含中间证书,切勿只填叶子证书
ssl_certificate /etc/letsencrypt/live/oltool.net/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/oltool.net/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3; # TLS 1.0/1.1 已全部废弃
ssl_prefer_server_ciphers off; # 让客户端优先,服务端不强制
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off; # 关闭 ticket 以避免密钥长期复用
# OCSP stapling:由服务端代客户端去问 CA,省一次外部请求
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
# HSTS:强制浏览器后续只走 HTTPS。max-age 先从短值起步,确认无误再加长到 31536000
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
三个容易被忽略的坑:
add_header不会继承。在location块里一旦写了任何add_header,上层的add_header(包括 HSTS)就会被整体丢弃,必须在需要的每一层重写一遍。加上always确保 4xx/5xx 响应也带上。- HSTS 不可逆。一旦发出
max-age=31536000,在整整一年内你无法让用户回落到 HTTP。上生产前先确认全站资源都能走 HTTPS;首次部署建议先用max-age=300试水。 includeSubDomains是陷阱。它对所有子域生效,如果某个内网子域没有 HTTPS,加了这条之后你会直接连不上它。确认全部子域都就绪后再加。
故障排查表
| 症状 | 根因 | 排查命令 | 修复 |
|---|---|---|---|
| 浏览器不信任,桌面正常、手机报错 | 证书链不完整 | openssl s_client -connect host:443 -servername host 看 Certificate chain 段深度 |
改用 fullchain.pem |
NET::ERR_CERT_COMMON_NAME_INVALID |
证书 SAN 不含当前主机名 | openssl x509 -in cert.pem -noout -text | grep -A1 "Subject Alternative Name" |
重签,补全 SAN |
ERR_CERT_DATE_INVALID |
过期或本机时间错误 | openssl x509 -in cert.pem -noout -dates |
续期 / 校准时钟 |
| 页面小锁带感叹号 | 混合内容:HTTPS 页里加载了 http:// 资源 | 浏览器控制台 Mixed Content 提示 / DevTools Network 过滤 | 改协议相对或全站 https |
| 只有部分客户端握手失败 | 算法或 TLS 版本不兼容 | openssl s_client -tls1_2 / curl --tlsv1.2 |
放宽协议或加 RSA 双证书 |
| 多站点同 IP,返回了错证书 | SNI 未生效 / 缺少 default_server | openssl s_client -connect ip:443 -servername a.com 对比两次结果 |
补 server_name 与默认 server |
| OCSP 响应超时拖慢首连 | CA 的 OCSP 端点被墙或不可达 | openssl s_client -status 看 OCSP Response Status |
开启 stapling 或换 CA |
| 证书被吊销但客户端仍放行 | 未配置 stapling 且客户端不做实时校验 | openssl x509 -noout -text 查 CRL/OCSP 地址 |
开 stapling + 缩短有效期 |
生命周期与自动化
一份能长期不管的证书体系,靠的是三件事:
- 自动续期。ACME 客户端 + 定时器,每天检查,到期前 30 天自动续,续完 reload:
# systemd timer(比 cron 更好:可查看上次运行结果与日志) systemctl enable --now certbot-renew.timer - 到期监控。续期会失败(DNS API 变更、端口被挡、额度超限),所以必须独立监控剩余天数,低于 21 天就告警。没有监控的自动续期等于没有自动续期。
- 私钥管控。私钥权限 600、属主 root;不要进 git(进 git 就等于永久泄露,即使后续删除);备份要加密;怀疑泄露立即吊销重签。
上线检查清单
- [ ] 使用
fullchain.pem(含中间证书),而非叶子证书 - [ ] 仅启用 TLS 1.2 / 1.3,关闭 TLS 1.0 / 1.1
- [ ] SAN 覆盖所有会被访问到的主机名(含裸域与 www)
- [ ] 80 端口 301 跳转到 HTTPS,且放行
/.well-known/acme-challenge/ - [ ] 无混合内容(页面内所有资源均为 https)
- [ ] HSTS 已从短
max-age起步验证 - [ ] OCSP stapling 开启且可验证(
openssl s_client -status) - [ ] 自动续期已生效并有独立到期监控
- [ ] 私钥权限 600,未提交进版本库