crypto/tls — 安全传输层
crypto/tls 在 net 的接口之上实现了 TLS 协议,使得 TCP 连接可以被透明地加密和认证。
1. TLS 基础概念速览
| 概念 | 作用 |
|---|---|
| 证书 (Certificate) | 证明服务器/客户端身份 |
| 私钥 (Private Key) | 解密/签名,绝不可外泄 |
| CA 证书 | 签名其他证书的信任锚点 |
| TLS 握手 | 协商加密套件、交换密钥 |
| mTLS | 双向 TLS:服务端也验证客户端证书 |
2. TLS 服务器
2.1 基础 HTTPS 服务器
server := &http.Server{
Addr: ":8443",
}
// 方式 1:直接指定证书文件
log.Fatal(server.ListenAndServeTLS("server.crt", "server.key"))
// 方式 2:通过 TLSConfig 配置更多选项
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
MaxVersion: tls.VersionTLS13,
// 密码套件(仅 TLS 1.2 及以下需要)
CipherSuites: []uint16{
tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,
},
PreferServerCipherSuites: true,
}
server.TLSConfig = tlsConfig
log.Fatal(server.ListenAndServeTLS("server.crt", "server.key"))💡 最佳实践:
- 生产环境最低使用 TLS 1.2,推荐 TLS 1.3。
- TLS 1.3 的密码套件由协议自动协商,不需要手动配置
CipherSuites。- 证书建议使用 Let’s Encrypt 自动管理(golang.org/x/crypto/acme/autocert)。
2.2 开发环境自签名证书
func generateSelfSignedCert() (tls.Certificate, error) {
privKey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return tls.Certificate{}, err
}
template := &x509.Certificate{
SerialNumber: big.NewInt(1),
Subject: pkix.Name{CommonName: "localhost"},
NotBefore: time.Now(),
NotAfter: time.Now().Add(365 * 24 * time.Hour),
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
DNSNames: []string{"localhost", "127.0.0.1"},
}
derBytes, err := x509.CreateCertificate(rand.Reader, template, template,
&privKey.PublicKey, privKey)
if err != nil {
return tls.Certificate{}, err
}
return tls.Certificate{
Certificate: [][]byte{derBytes},
PrivateKey: privKey,
}, nil
}3. TLS 客户端
3.1 基础 HTTPS 客户端
client := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
MinVersion: tls.VersionTLS12,
},
},
}
resp, _ := client.Get("https://example.com")🚨 陷阱:
InsecureSkipVerify: true跳过证书验证,仅用于开发环境。在生产中这样做等于没有 TLS。
3.2 使用自定义 CA 证书
// 加载自定义 CA 证书
caCert, err := os.ReadFile("ca.crt")
if err != nil {
log.Fatal(err)
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
tlsConfig := &tls.Config{
RootCAs: caCertPool, // 使用自定义 CA
MinVersion: tls.VersionTLS12,
}4. mTLS — 双向 TLS
mTLS 要求:服务端验证客户端证书,客户端验证服务端证书。常用于内部微服务间的零信任安全。
4.1 服务端配置
caCert, _ := os.ReadFile("ca.crt")
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
serverCert, _ := tls.LoadX509KeyPair("server.crt", "server.key")
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{serverCert},
ClientAuth: tls.RequireAndVerifyClientCert, // ← 关键
ClientCAs: caCertPool,
MinVersion: tls.VersionTLS12,
}
server := &http.Server{
Addr: ":8443",
TLSConfig: tlsConfig,
}4.2 客户端配置
clientCert, _ := tls.LoadX509KeyPair("client.crt", "client.key")
caCert, _ := os.ReadFile("ca.crt")
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{clientCert}, // 客户端证书
RootCAs: caCertPool,
MinVersion: tls.VersionTLS12,
}
client := &http.Client{
Transport: &http.Transport{TLSClientConfig: tlsConfig},
}4.3 获取对端证书信息
func handler(w http.ResponseWriter, r *http.Request) {
if r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
clientCert := r.TLS.PeerCertificates[0]
fmt.Fprintf(w, "Client CN: %s\n", clientCert.Subject.CommonName)
fmt.Fprintf(w, "Client DNS: %v\n", clientCert.DNSNames)
}
}5. 🔬 TLS 握手流程
Client Server
| |
|--- ClientHello ------------->|
| (支持的版本、密码套件、SNI)|
| |
|<-- ServerHello --------------|
| Certificate |
| (ServerHelloDone) |
| |
|--- (ClientCertificate) ----->| ← mTLS 才有
| ClientKeyExchange |
| (CertificateVerify) |
| ChangeCipherSpec |
| Finished |
| |
|<-- ChangeCipherSpec ---------|
| Finished |
| |
|<===== 加密通信 =============>|🔬 TLS 1.3 优化:握手减少到 1-RTT(往返一次),且大部分加密套件是 AEAD(GCM/ChaCha20-Poly1305)。
6. 🚨 TLS 常见陷阱
| 陷阱 | 影响 | 解决 |
|---|---|---|
InsecureSkipVerify: true |
中间人攻击 | 仅开发环境使用 |
| TLS 1.0/1.1 | 已知漏洞 | 设置 MinVersion: tls.VersionTLS12 |
| 证书过期无人监控 | 服务不可用 | 使用 autocert 或监控 |
| HTTP/2 降级 | TLS 1.2 下需要 ALPN | 设置 NextProtos: []string{"h2", "http/1.1"} |
crypto/rsa 1024 位密钥 |
已可破解 | 使用 RSA 2048+ 或 ECDSA P-256 |
💡 最佳实践:使用
golang.org/x/crypto/acme/autocert自动从 Let’s Encrypt 获取和续期证书。