HTTPS 通讯和证书
理解加密
假设客户端要给服务器发送:
password=123456
普通 HTTP 相当于直接发送:
客户端 ----------------------> 服务器
password=123456
如果有人抓包:
攻击者
↓
password=123456
内容可能直接被看到。
因此我们希望先把数据加密。
例如:
password=123456
↓
加密
↓
7af91cd923....
服务器收到之后再解密。
问题是:
客户端和服务器到底使用什么密钥?
这就引出了现代 HTTPS 最核心的几个概念。
对称加密
最容易理解的加密方式就是:
同一把密钥
既可以加密,也可以解密。
例如:
密钥:
ABC123
客户端:
明文
↓
ABC123 加密
↓
密文
服务器:
密文
↓
ABC123 解密
↓
明文
这叫:
对称加密
常见算法包括:
AES
ChaCha20
它最大的优势是:
非常快。
所以真正的 HTTPS 数据传输阶段,大量数据主要使用的就是对称加密。
但是它存在一个严重问题。
客户端必须提前知道:
ABC123
服务器也必须知道:
ABC123
那么:
这把密钥最开始怎么安全地传过去?
如果直接通过网络发送:
客户端 ------ ABC123 ------> 服务器
攻击者只需要在中间截获一次:
ABC123
后面的通信就可能失去安全性。
因此还需要另外一种技术。
非对称加密
非对称加密不是只有一把密钥,而是一对密钥:
Private Key
私钥
Public Key
公钥
它们在数学上存在对应关系。
可以把它们暂时理解成:
公钥:可以公开
私钥:必须保密
例如服务器拥有:
server.key
↓
服务器私钥
server public key
↓
服务器公钥
其中:
私钥
只能保存在服务器。
而:
公钥
可以发送给任何客户端。
于是客户端可以利用服务器提供的信息建立安全通信。
现代 TLS 的实际密钥协商机制比“直接用公钥加密一个对称密钥”更加复杂,通常会使用类似 ECDHE 的密钥交换算法。
但在入门阶段可以先建立这样一个模型:
非对称密码体系
↓
帮助客户端和服务器安全建立共享秘密
↓
生成会话密钥
↓
后续大量数据使用对称加密
因此 HTTPS 并不是简单地:
所有数据全部使用 RSA 加密
而更接近:
TLS 握手
↓
身份认证 + 密钥协商
↓
得到 Session Key
↓
AES / ChaCha20 等对称算法
↓
高速加密后续 HTTP 数据
✳️ HTTPS通信大体过程
HTTPS通信包括:
- 步骤 1
浏览器 向服务器发起连接请求
- 步骤 2
服务器向浏览器发送自己的数字证书。证书中包含一个公钥用来加密信息,注意这里 私钥由服务器秘密保存
- 步骤 3
浏览器 收到服务器的证书后,它会去验证这个数字证书到底是不是服务器的,数字证书有没有什么问题
验证方法就是 检查 颁发这个数字证书机构 签名是否正确。 颁发证书的机构会 说明这个网站的信息:包括名称、网址、证书有效期等。 然后 颁发机构会使用 自己的私钥对 这个信息进行 签名。 由于颁发机构的公钥都是 公布于众的, 可以在它的官方网站获取到,浏览器 可以用颁发机构公钥 来验证签名。
验证一致后,浏览器 会发送一个随机的字符串给服务器用私钥去加密,服务器把加密的结果返回给浏览器 ,浏览器 用公钥解密这个返回结果,如果解密结果与之前生成的随机字符串一致,那说明对方确实是私钥的持有者,或者说对方确实是服务器。
- 步骤 4
验证服务器的身份后,浏览器 再生成一个 对称加密算法和密钥,用于后面的通信的加密和解密。
这个对称加密算法和密钥,浏览器 会用公钥加密后发送给服务器,别人截获了也没用,因为只有服务器手中有可以解密的私钥。这样,后面服务器和浏览器 就都可以用对称加密算法来加密和解密通信内容了。
大家可以发现 公私钥的加解密 只在连接建立的时候使用一下,用来验证服务端身份 和 加密传输 对称加密算法和密钥。
真正的数据传输会使用对称加密算法和另外的密钥进行。
数字证书 和 CA
✳️ 为何有了公钥还需要证书
这里又出现一个问题。
假设你访问:
https://git.company.local
服务器给你一个公钥:
Public Key A
你怎么知道:
Public Key A 真的是公司 GitLab 服务器的?
攻击者完全可以进行中间人攻击:
浏览器
│
▼
攻击者
│
▼
真正服务器
攻击者告诉浏览器:
这是 git.company.local 的公钥。
如果浏览器无条件相信,那么 HTTPS 仍然存在问题。
所以我们需要一种机制证明:
这个公钥
确实属于
git.company.local
这就是:
数字证书。
✳️ 数字证书是什么
可以把 HTTPS 证书 理解成服务器的:
数字身份证。
证书里面通常包含:
网站域名
公钥
证书颁发者
证书有效期
数字签名
证书用途
SAN 域名信息
例如:
Certificate
Subject:
CN = git.company.local
SAN:
DNS:git.company.local
DNS:www.git.company.local
Public Key:
...
Issuer:
Company Root CA
Valid:
2026-08-01
↓
2027-08-01
Signature:
...
这里最重要的内容之一就是:
Public Key
证书实际上把:
身份信息
+
公钥
绑定到了一起。
✳️ CA 到底是什么
CA 全称:
Certificate Authority
中文通常叫:
证书颁发机构
CA 的主要工作就是:
证明某个证书中的身份和公钥之间的关系。
例如:
Company Root CA
│
│ 签名
▼
git.company.local 证书
客户端如果相信:
Company Root CA
那么它就可以进一步相信:
由 Company Root CA 签发的证书
因此整个体系实际上是:
我不认识你的服务器
│
▼
但我信任你的 CA
│
▼
CA 证明你的服务器证书是真的
│
▼
于是我信任你的服务器
这就是:
PKI
也就是 Public Key Infrastructure,公钥基础设施。
✳️ 浏览器为什么天然信任一些网站
打开操作系统或者浏览器的证书管理器,可以看到很多:
Root CA
例如一些商业证书机构的根证书。
这些证书在操作系统或者浏览器安装时就已经被加入:
Trusted Root Certification Authorities
也就是:
受信任的根证书颁发机构。
因此访问一个公网网站时,可能形成:
浏览器信任
Root CA
│
▼
Intermediate CA
│
▼
www.example.com
只要整个证书链可以一直追溯到浏览器已经信任的根 CA,浏览器就会认为:
证书可信
✳️ 根证书、中间证书和服务器证书
真实 PKI 中通常不是根 CA 直接给所有服务器签证书。
更常见的是:
Root CA
│
▼
Intermediate CA
│
▼
Server Certificate
即:
根 CA
↓
中间 CA
↓
网站证书
原因之一是:
根 CA 的私钥极其重要,不应该频繁用于日常签发。
大型企业一般也会采用类似结构:
Company Root CA
│
▼
Company Internal Server CA
│
├── git.company.local
├── jenkins.company.local
├── harbor.company.local
└── api.company.local
✳️ 自签名证书
“自签名证书” 是服务器网站自己给自己签名发证书:
server.key
│
▼
server.crt
也就是:
服务器证书
Issuer = 自己
Subject = 自己
这种证书浏览器默认不会信任。
✳️ 企业内部CA证书
企业内部更推荐的做法是:
自己建立 CA
│
▼
CA 给内部服务器签证书
例如:
Company Root CA
│
├── git.company.local
├── jenkins.company.local
└── api.company.local
这虽然也是:
自建证书体系
但从 PKI 结构来看,比每台服务器自己生成一张自签名证书更加规范。
这种类型的证书,有的时候也被称作企业自签名证书。
整个证书体系中最重要的一条原则:
Certificate
可以公开
Private Key
绝对不能公开
例如:
company-root-ca.crt
可以安装在所有公司电脑。
但是:
company-root-ca.key
必须受到最高等级保护。
如果 Root CA 私钥泄露,攻击者理论上就能够签发:
假的 GitLab 证书
假的 Jenkins 证书
假的 API 证书
而公司电脑仍可能信任这些证书。
因此 CA 私钥泄露是:
整个内部 PKI 的严重安全事故。
证书体系中的个重要文件
学习 OpenSSL 时,经常会看到:
.key
.csr
.crt
.pem
这些文件非常容易混淆。
✳️ Private Key
例如:
server.key
这是:
私钥。
必须严格保密。
✳️ CSR
例如:
server.csr
全称:
Certificate Signing Request
也就是:
证书签名请求。
服务器生成:
私钥
+
CSR
然后把 CSR 交给 CA。
CSR 中通常包含:
公钥
域名
组织信息
SAN
但是:
CSR 中不包含服务器私钥。
✳️ Certificate
例如:
server.crt
这是 CA 最终签发的:
数字证书。
✳️ PEM
.pem 更多表示:
一种文本编码格式。
打开以后经常可以看到:
-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----
或者:
-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----
因此:
.pem
并不能单独说明它究竟是:
证书
还是
私钥
要看文件内部内容。
企业内部证书签发使用流程
创建自己的企业根 CA
首先建立目录:
mkdir company-ca
cd company-ca
生成 CA 私钥:
openssl genrsa -out company-root-ca.key 4096
这里:
company-root-ca.key
就是整个企业 CA 最重要的文件之一。
它可以用来:签发其他证书,因此必须严格保护。
然后创建根证书:
openssl req \
-x509 \
-new \
-key company-root-ca.key \
-sha256 \
-days 3650 \
-out company-root-ca.crt
过程中 OpenSSL 会要求填写:
Country
State
City
Organization
Organizational Unit
Common Name
例如:
Organization:
MyCompany
Common Name:
MyCompany Root CA
最终得到:
company-root-ca.key
company-root-ca.crt
两者关系:
company-root-ca.key
│
│ 私钥
▼
Company Root CA
│
▼
company-root-ca.crt
其中:company-root-ca.key 绝对不能发给客户端。
客户端只需要:company-root-ca.crt
查看根证书内容
执行:
openssl x509 \
-in company-root-ca.crt \
-text \
-noout
可以查看:
Issuer
Subject
Validity
Public Key
Signature Algorithm
Extensions
这是以后排查 HTTPS 问题非常重要的命令。
为内部网站生成私钥
假设企业内部网站是:
git.company.local
创建服务器私钥:
openssl genrsa \
-out git.company.local.key \
2048
现在服务器拥有:
git.company.local.key
这个文件必须:
只保存在服务器端。
创建服务器 CSR
执行:
openssl req \
-new \
-key git.company.local.key \
-out git.company.local.csr
这里:
CSR
就是服务器向 CA 提交的:
证书申请。
可以查看 CSR:
openssl req \
-in git.company.local.csr \
-text \
-noout
给服务器证书配置 SAN
证书颁发给的目标域名,以前主要使用这个字段:
CN
Common Name
例如:
CN=git.company.local
但是现代浏览器验证域名时,重点使用的是:
Subject Alternative Name
简称:
SAN
例如:
subjectAltName = DNS:git.company.local
如果一个证书需要支持多个域名:
DNS:git.company.local
DNS:git
DNS:git.internal.company.com
都应该放到 SAN 中。
如果直接使用 IP:
https://192.168.10.20
那么 SAN 中应该包含:
IP:192.168.10.20
而不是写成:
DNS:192.168.10.20
SAN信息是放在额外的一个文件中,我们创建:
git.ext
内容:
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage=digitalSignature,keyEncipherment
extendedKeyUsage=serverAuth
subjectAltName=@alt_names
[alt_names]
DNS.1=git.company.local
DNS.2=git
如果还需要通过 IP 访问:
IP.1=192.168.10.20
于是:
git.company.local
git
192.168.10.20
都可以写入同一张证书。
使用自己的 CA 签发服务器证书
执行:
openssl x509 \
-req \
-in git.company.local.csr \
-CA company-root-ca.crt \
-CAkey company-root-ca.key \
-CAcreateserial \
-out git.company.local.crt \
-days 825 \
-sha256 \
-extfile git.ext
现在得到:
git.company.local.key
git.company.local.csr
git.company.local.crt
它们的作用分别是:
git.company.local.key
↓
服务器私钥
git.company.local.csr
↓
证书申请文件
git.company.local.crt
↓
CA 签发的服务器证书
验证证书是否由自己的 CA 签发
执行:
openssl verify \
-CAfile company-root-ca.crt \
git.company.local.crt
如果正确,会看到:
git.company.local.crt: OK
这实际上就是一次最简单的:
证书链验证。
使用 OpenSSL 查看服务器证书
执行:
openssl x509 \
-in git.company.local.crt \
-text \
-noout
重点观察:
Issuer
Subject
Validity
Subject Alternative Name
Public Key
Key Usage
Extended Key Usage
尤其确认:
X509v3 Subject Alternative Name:
DNS:git.company.local
是否存在。
在 Nginx 中部署 HTTPS
假设已有:
git.company.local.crt
git.company.local.key
复制到:
/etc/nginx/ssl/
例如:
/etc/nginx/ssl/git.company.local.crt
/etc/nginx/ssl/git.company.local.key
然后配置 Nginx:
server {
listen 443 ssl;
server_name git.company.local;
ssl_certificate
/etc/nginx/ssl/git.company.local.crt;
ssl_certificate_key
/etc/nginx/ssl/git.company.local.key;
location / {
root /usr/share/nginx/html;
index index.html;
}
}
检查配置:
nginx -t
重新加载:
nginx -s reload
现在服务器已经可以:
HTTPS
通信。
但是浏览器此时仍然有可能报警。
为什么部署 HTTPS 后浏览器仍然报警
因为:
服务器有证书
并不代表:
客户端相信这个证书
浏览器会检查:
服务器证书
│
▼
谁签发的?
│
▼
Company Root CA
│
▼
这个 CA 我信任吗?
如果操作系统没有安装:
company-root-ca.crt
那么浏览器会认为:
Unknown CA
所以还需要:
把企业 Root CA 安装到客户端的受信任根证书库。
Windows 信任企业根 CA
在 Windows 中打开:
certmgr.msc
进入:
Trusted Root Certification Authorities
↓
Certificates
导入:
company-root-ca.crt
也就是:
受信任的根证书颁发机构
注意:
客户端导入的是 CA 公共证书,不是 CA 私钥。
绝对不能把:
company-root-ca.key
复制给普通客户端。
重新打开浏览器以后访问:
https://git.company.local
如果:
域名
证书
证书链
有效期
都正确,浏览器就应该正常信任。
Linux 信任企业 CA
Debian / Ubuntu 系统通常可以:
sudo cp company-root-ca.crt \
/usr/local/share/ca-certificates/company-root-ca.crt
然后:
sudo update-ca-certificates
系统就会把这个 CA 加入系统信任库。
之后:
curl https://git.company.local
也应该能够正常验证证书。
hosts 和内部 DNS
HTTPS 证书解决的是:
身份认证
+
加密
但它并不会自动解决:
域名解析
例如:
git.company.local
仍然需要解析到:
192.168.10.20
小型实验可以修改 hosts:
192.168.10.20 git.company.local
企业环境更合理的方式则是:
Internal DNS
例如:
git.company.local
↓
192.168.10.20
jenkins.company.local
↓
192.168.10.21
harbor.company.local
↓
192.168.10.22
于是整个企业内部形成:
Internal DNS
+
Internal CA
+
HTTPS
这才是一套相对完整的内部基础设施。
HTTPS 完整访问过程
现在可以把整个知识串起来。
用户输入:
https://git.company.local
首先进行 DNS 查询:
git.company.local
↓
192.168.10.20
浏览器连接:
192.168.10.20:443
服务器返回:
git.company.local 证书
浏览器检查:
域名是否匹配
↓
有效期是否正常
↓
用途是否正确
↓
证书签名是否正确
↓
CA 是否可信
浏览器发现:
Issuer:
Company Root CA
而:
Company Root CA
已经存在于本机:
Trusted Root CA Store
于是浏览器接受服务器身份。
TLS 随后完成:
密钥协商
生成本次通信使用的:
会话密钥
之后:
HTTP Request
HTTP Response
都会在 TLS 的保护下传输。
用一张图理解整个 HTTPS 信任体系
Company Root CA
───────────────
Root CA Certificate
Root CA Private Key
│
│ 签名
▼
git.company.local
─────────────────
Server Certificate
Server Private Key
│
│
▼
Windows / Linux / Browser
│
│ 信任
▼
Company Root CA
│
│ 验证
▼
git.company.local Certificate
│
▼
TLS
│
▼
HTTPS
Docker 中部署内部 HTTPS
如果 Nginx 运行在 Docker 中,可以建立:
ssl/
├── git.company.local.crt
└── git.company.local.key
Compose:
services:
nginx:
image: nginx:latest
ports:
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./ssl:/etc/nginx/ssl:ro
这里:
证书
+
私钥
挂载到容器内部。
注意:
Docker 并不会改变 HTTPS 的原理。
本质仍然是:
Browser
↓
TCP 443
↓
Docker Port Mapping
↓
Nginx
↓
TLS Certificate
HTTPS 握手到底发生了什么
学习到这里以后,可以进一步介绍 TLS 握手。
可以将现代 TLS 过程简化为:
Client
│
│ ClientHello
▼
Server
│
│ ServerHello
│ Certificate
▼
Client
浏览器验证:
Certificate
确认服务器身份。
随后双方进行:
Key Exchange
生成共同的:
Session Key
之后:
HTTP Request
HTTP Response
通过对称加密保护。
因此:
证书
主要参与:
身份认证
而真正大量 HTTP 数据并不是简单地全部使用证书中的公钥直接加密。