传输层加密实战:证书生成与双向认证
讲传输层加密,光看理论是不行的。我早年刚接触TLS时,照着文档折腾了一整天,结果客户端连不上,报了个“tls: bad certificate”——其实就是证书链没配对。从那以后我做任何加密项目,一定把证书生成、配置、验证这套流程手打一遍。今天咱们就来走完全程:从OpenSSL命令到双向认证服务器。
1. 理解传输层加密与双向认证
单向认证大家很熟:客户端验证服务器证书,确保连接对象的身份。双向认证就是反过来再加一道——服务端也要验证客户端证书。说白了,你们俩得互相亮身份证。
| 特性 | 单向认证 | 双向认证 |
|---|---|---|
| 客户端验证服务器 | ✅ | ✅ |
| 服务器验证客户端 | ❌ | ✅ |
| 适用场景 | HTTPS网站 | API间通信、IT设备、金融接口 |
| 客户端需要证书 | 不需要 | 需要 |
我个人习惯在Tokenization接口中用双向认证——既能加密信道,又能确保调用方身份。当然,证书管理成本也高一些,不过安全嘛,值得。
2. 准备OpenSSL环境
基本上你的Linux/Mac都自带OpenSSL。检查版本:
openssl version
我建议至少1.1.1以上,支持TLS 1.3。如果版本太旧,记得升级——我以前被旧版OpenSSL坑过,生出的证书兼容性出问题。
3. 生成CA根证书
证书链的起点。CA相当于“发证机关”,自己给自己签。步骤很简单:
- 生成CA私钥
- 用私钥自签名根证书
# 生成CA私钥(2048位RSA)
openssl genrsa -out ca.key 2048
# 自签名根证书,有效期10年
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
-out ca.crt \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg/CN=MyRootCA"
4. 生成服务器证书
服务器证书用于“证明你是这个域名/IP”。步骤:生成私钥 → 生成证书签名请求(CSR) → 用CA给CSR签名。
# 生成服务器私钥
openssl genrsa -out server.key 2048
# 生成CSR(注意CN填服务器域名或IP)
openssl req -new -key server.key -out server.csr \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg/CN=api.myservice.com"
# 用CA签名,生成服务器证书(有效期2年)
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out server.crt -days 730 -sha256
嗯,这里要注意SAN(Subject Alternative Name)。老浏览器只认CN的时代过去了,现在必须加SAN。我习惯用扩展配置文件来加SAN:
# server.ext 内容
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = api.myservice.com
DNS.2 = localhost
IP.1 = 127.0.0.1
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out server.crt -days 730 -sha256 -extfile server.ext
5. 生成客户端证书(双向认证)
客户端证书和服务器流程几乎一样,区别在于用途是clientAuth。我一般把它们放在一个目录下方便管理。
# 客户端私钥
openssl genrsa -out client.key 2048
# 客户端CSR
openssl req -new -key client.key -out client.csr \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg/CN=client-001"
# 签名客户端证书
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out client.crt -days 365 -sha256 \
-extfile <(echo "extendedKeyUsage=clientAuth")
6. 配置TLS服务器(双向认证)
我拿Go写个简单示例,因为Go标准库crypto/tls支持双向认证很直接。Nginx、Gin、Spring Boot也都类似,原理一样。
package main
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"log"
"net/http"
)
func main() {
// 加载CA证书,用于验证客户端
caCert, _ := ioutil.ReadFile("ca.crt")
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
// 加载服务器证书和私钥
serverCert, _ := tls.LoadX509KeyPair("server.crt", "server.key")
// TLS配置:强制双向认证
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{serverCert},
ClientCAs: caCertPool,
ClientAuth: tls.RequireAndVerifyClientCert,
MinVersion: tls.VersionTLS12,
}
// 启动HTTPS服务器
server := &http.Server{
Addr: ":8443",
TLSConfig: tlsConfig,
Handler: http.HandlerFunc(handler),
}
log.Fatal(server.ListenAndServeTLS("", "")) // 证书已加载到TLSConfig,传空
}
func handler(w http.ResponseWriter, r *http.Request) {
// 从请求中提取客户端证书信息
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
cert := r.TLS.PeerCertificates[0]
w.Write([]byte("Hello, " + cert.Subject.CommonName))
} else {
w.Write([]byte("No client cert"))
}
}
关键参数:ClientAuth = tls.RequireAndVerifyClientCert。少了这行,服务端就不会要求客户端出示证书,双向认证就废了。
7. 验证双向认证
用curl测试,必须带上客户端证书和私钥,同时指定信任的CA(用来验证服务器证书):
curl -v --cacert ca.crt \
--cert client.crt \
--key client.key \
https://localhost:8443/
如果一切正常,你会看到服务端返回“Hello, client-001”。我遇到过最坑的情况是客户端证书路径写错了,curl报“curl: (58) unable to set private key file”。另一个常见问题是服务器证书没带SAN,现代Go/tls会直接拒绝——你想想看,产品环境里这得多尴尬。
CA自签 → 服务器证书/客户端证书。服务器配置RequireAndVerifyClientCert是关键。我曾经在生产环境把ClientAuth写成了RequestClientCert,结果客户端不传证书也能访问——排查了半天。所以你配置完一定用curl手动验证,确保不传客户端证书时服务端会拒绝。