openssl —— 证书 / 密钥 / TLS 调试工具箱
一句话定义
openssl 是 Linux 上最全能的密码学命令行工具:看证书、生成 key、签 CA、测 TLS、加解密、base64 ... 几乎所有"和加密 / 证书有关"的命令行操作都靠它。K8s 全靠 TLS 跑——证书排错绕不开 openssl。
典型场景
- 看一个 cert 文件里有啥:
openssl x509 -in cert.crt -text - 测远端 TLS 握手:
openssl s_client -connect host:443 - 看证书过期时间:
openssl x509 -enddate -noout - 生成自签 CA + server cert(K8s 学习用)
- base64 / 哈希等小工具
子命令架构
openssl <子命令> <参数...>
最常用:
| 子命令 | 用途 |
|---|---|
x509 | 看 / 操作 X.509 证书 |
rsa / ec / genpkey | 生成 / 查看私钥 |
req | 生成 CSR(证书请求) |
s_client / s_server | TLS 客户端 / 服务端 |
verify | 验证证书链 |
pkcs12 | PKCS12 格式(.p12 / .pfx) |
dgst | 哈希 |
enc | 对称加密 |
rand | 随机数 |
每个子命令独立 -help:
openssl x509 -help
openssl s_client -help
看证书:openssl x509
# 全文本
openssl x509 -in cert.crt -text -noout
# 只看关键字段
openssl x509 -in cert.crt -subject -noout
# subject=CN = www.example.com, O = Example Corp
openssl x509 -in cert.crt -issuer -noout
# issuer=CN = DigiCert Global Root CA, ...
openssl x509 -in cert.crt -enddate -noout
# notAfter=May 27 12:00:00 2027 GMT
openssl x509 -in cert.crt -startdate -enddate -noout
# notBefore=...
# notAfter=...
openssl x509 -in cert.crt -fingerprint -noout
# SHA1 Fingerprint=AA:BB:CC:...
openssl x509 -in cert.crt -fingerprint -sha256 -noout
# SHA256 Fingerprint=...
openssl x509 -in cert.crt -serial -noout
openssl x509 -in cert.crt -ext subjectAltName -noout
# X509v3 Subject Alternative Name:
# DNS:www.example.com, DNS:example.com
-noout 阻止把证书原文重新输出。
快速看 K8s apiserver 证书
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -text -noout \
| grep -E 'Subject:|Subject Alternative Name:|Not (Before|After)'
Subject Alternative Name 是关键 —— 必须包含所有访问 apiserver 的 IP / 域名(VIP / 节点 IP / kubernetes.default 等)。
看远端服务的 TLS 证书:s_client
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
-servername 设 SNI(关键,让对端知道你要哪个虚拟主机的证书)。
看完整握手细节
openssl s_client -connect example.com:443 -servername example.com
# 显示:
# handshake 过程
# 提供的 cert chain
# 选用的 cipher
# session info
# ... 等输入 / Ctrl-D 关闭
看证书过期
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -enddate
# notAfter=Apr 15 23:59:59 2027 GMT
封装成监控脚本:
expire=$(echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -enddate | cut -d= -f2)
expire_ts=$(date -d "$expire" +%s)
now_ts=$(date +%s)
days_left=$(( (expire_ts - now_ts) / 86400 ))
echo "Days until expiry: $days_left"
看证书链是不是完整
openssl s_client -connect example.com:443 -showcerts -servername example.com < /dev/null
# 显示完整 cert chain
# Certificate chain
# 0 s:/CN=example.com
# i:/CN=DigiCert SHA2
# 1 s:/CN=DigiCert SHA2
# i:/CN=DigiCert Global Root
# ---
# Verify return code: 0 (ok)
Verify return code: 0 (ok) —— 链验证通过。 Verify return code: 21 —— 中间证书缺失(最常见)。
生成私钥
# RSA 2048
openssl genrsa -out server.key 2048
# RSA 4096
openssl genrsa -out server.key 4096
# ECDSA P-256(现代推荐)
openssl ecparam -genkey -name prime256v1 -out server.key
# Ed25519(最现代)
openssl genpkey -algorithm ED25519 -out server.key
# 带密码保护
openssl genrsa -aes256 -out server.key 2048 # 提示输密码
K8s 证书一般 RSA 2048。SSL/TLS 现代场景用 ECDSA / Ed25519。
生成 CSR(让 CA 签名)
openssl req -new -key server.key -out server.csr -subj "/CN=example.com/O=Example Corp"
# 多 SAN(必加,浏览器不再认只看 CN 的证书)
openssl req -new -key server.key -out server.csr \
-subj "/CN=example.com" \
-addext "subjectAltName=DNS:example.com,DNS:www.example.com,IP:1.2.3.4"
CSR 是给 CA / 自签的输入。
看 CSR 内容
openssl req -in server.csr -text -noout
自签证书(学习 / 实验用)
全套自签 CA + server cert
# 1. 生成 CA 私钥
openssl genrsa -out ca.key 4096
# 2. 自签 CA 证书
openssl req -x509 -new -nodes \
-key ca.key \
-sha256 -days 3650 \
-subj "/CN=My Self-Signed CA" \
-out ca.crt
# 3. 生成 server 私钥
openssl genrsa -out server.key 2048
# 4. 生成 CSR
openssl req -new -key server.key \
-subj "/CN=example.local" \
-out server.csr
# 5. 用 CA 签 server cert
openssl x509 -req \
-in server.csr \
-CA ca.crt -CAkey ca.key -CAcreateserial \
-out server.crt \
-days 365 -sha256 \
-extfile <(echo "subjectAltName=DNS:example.local,DNS:*.example.local")
# 6. 看结果
openssl x509 -in server.crt -text -noout
部署到 web server (nginx / haproxy) —— client 要信任 ca.crt:
# Ubuntu / Debian
cp ca.crt /usr/local/share/ca-certificates/my-ca.crt
update-ca-certificates
一步生成自签(不分 CA)
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout server.key -out server.crt \
-days 365 \
-subj "/CN=localhost" \
-addext "subjectAltName=DNS:localhost,IP:127.0.0.1"
-nodes = no DES = 不给私钥设密码。
验证证书
openssl verify -CAfile ca.crt server.crt
# server.crt: OK
# 含中间证书
openssl verify -CAfile root-ca.crt -untrusted intermediate.crt server.crt
验证私钥和证书匹配
openssl x509 -in server.crt -noout -modulus | openssl md5
openssl rsa -in server.key -noout -modulus | openssl md5
# 两个 md5 应该一样 —— 一样表示 key 和 cert 配对
K8s 证书排错常用。
PKCS12 格式(.p12 / .pfx)
某些工具(Java keystore / Windows)用 PKCS12 而不是 PEM。
PEM → PKCS12
openssl pkcs12 -export \
-in server.crt \
-inkey server.key \
-out server.p12 \
-name "my-cert" \
-passout pass:password
PKCS12 → PEM
openssl pkcs12 -in server.p12 -nokeys -out server.crt
openssl pkcs12 -in server.p12 -nocerts -nodes -out server.key
看 K8s 证书全套
kubeadm 集群的 PKI 都在 /etc/kubernetes/pki/:
ls /etc/kubernetes/pki/
# apiserver.crt etcd/
# apiserver.key apiserver-etcd-client.crt
# apiserver-kubelet-client.crt
# ca.crt ca.key
# front-proxy-ca.crt ...
# sa.key sa.pub
# 批量看所有证书的过期时间
for f in /etc/kubernetes/pki/*.crt /etc/kubernetes/pki/etcd/*.crt; do
echo "=== $f ==="
openssl x509 -in $f -noout -subject -enddate
done
或者用 kubeadm 自带:
kubeadm certs check-expiration # 详见 kubeadm.md
TLS 服务端测试:s_server
启动一个 TLS 服务(测试 client 用):
openssl s_server -accept 4433 -cert server.crt -key server.key
# 监听 4433
另一个终端:
openssl s_client -connect localhost:4433
测试 mTLS:
openssl s_server -accept 4433 -cert server.crt -key server.key \
-CAfile ca.crt -Verify 1
# -Verify 1 要求 client 出示 cert
openssl s_client -connect localhost:4433 \
-cert client.crt -key client.key \
-CAfile ca.crt
哈希 / base64 / 随机数
# 哈希
echo -n "hello" | openssl dgst -sha256
# SHA2-256(stdin)= 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
# 文件哈希
openssl dgst -sha256 file.bin
# Base64
echo "hello" | openssl base64
# aGVsbG8K
echo "aGVsbG8K" | openssl base64 -d
# 随机字节
openssl rand -hex 16 # 32 字符 hex
# a1b2c3d4...
openssl rand -base64 32 # base64 编码
# 用作密码 / API key
openssl rand -out file 1024 # 1024 字节随机数据
对称加密 / 解密
# 加密
openssl enc -aes-256-cbc -salt -in plain.txt -out encrypted.bin -k "my-password"
# 解密
openssl enc -aes-256-cbc -d -in encrypted.bin -out plain.txt -k "my-password"
# 用 PBKDF2 派生密钥(更安全)
openssl enc -aes-256-cbc -pbkdf2 -in plain.txt -out enc.bin
现代场景用
age/gpg替代openssl enc(更现代、更简单、更安全)。
实战场景
1. 排查"x509: certificate signed by unknown authority"
x509: certificate signed by unknown authority
说明 server 提供的证书链不完整 / client 不信任根 CA。
# 看 server 完整链
echo | openssl s_client -connect server:443 -showcerts -servername server 2>/dev/null \
| sed -n '/BEGIN CERT/,/END CERT/p'
# 看 client 的根 CA
ls /etc/ssl/certs/ | head
# 或
cat /etc/ssl/certs/ca-certificates.crt | grep -c BEGIN
修:
- server 配完整 chain(intermediate + leaf)
- client 装对应根 CA
2. K8s apiserver SAN 缺少节点 IP
x509: certificate is valid for kubernetes.default.svc, not 10.0.24.99
apiserver 证书 SAN 没包含 10.0.24.99(新 VIP)。重生成证书:
# 备份
cp /etc/kubernetes/pki/apiserver.* /tmp/
# 删旧 cert
rm /etc/kubernetes/pki/apiserver.crt /etc/kubernetes/pki/apiserver.key
# 在 kubeadm-config 中加新 SAN
kubectl -n kube-system get cm kubeadm-config -o yaml > /tmp/kubeadm-config.yaml
# 编辑 apiServer.certSANs 加上新 IP,apply 回去
# 让 kubeadm 重生成
kubeadm init phase certs apiserver
# 看新证书
openssl x509 -in /etc/kubernetes/pki/apiserver.crt -text -noout | grep -A 1 'Subject Alternative'
# 重启 apiserver pod(动 manifest)
touch /etc/kubernetes/manifests/kube-apiserver.yaml
3. 私有 registry 自签证书
containerd 信任自签 registry:
# 节点上
mkdir -p /etc/containerd/certs.d/registry.example.com
cp registry-ca.crt /etc/containerd/certs.d/registry.example.com/ca.crt
# /etc/containerd/config.toml 启用 certs.d:
# [plugins."io.containerd.grpc.v1.cri".registry]
# config_path = "/etc/containerd/certs.d"
systemctl restart containerd
4. 用 openssl 看 etcd 端点
echo | openssl s_client \
-connect 10.0.24.28:2379 \
-cert /etc/kubernetes/pki/etcd/healthcheck-client.crt \
-key /etc/kubernetes/pki/etcd/healthcheck-client.key \
-CAfile /etc/kubernetes/pki/etcd/ca.crt \
2>/dev/null | head -30
mTLS 客户端测试 etcd。
常见踩坑
坑 1:openssl s_client 不加 -servername
openssl s_client -connect example.com:443
# 拿到错的证书(SNI 没设、server 给默认)
永远加 -servername(SNI):
openssl s_client -connect example.com:443 -servername example.com
坑 2:证书过期但本机时间不对
notAfter=2027-05-27
# 你机器时间: 2030-01-01
# 报错: certificate has expired
修:先校时(详见 chrony.md)。
坑 3:私钥被密码保护、自动化失败
openssl x509 -in cert.crt -text -noout # 不需要密码
openssl rsa -in encrypted.key -text -noout # 提示输密码 → 脚本卡死
自动化场景用 unprotected key + OS 文件权限:
openssl rsa -in encrypted.key -out plain.key # 解密保存
chmod 600 plain.key
或者用 K8s Secret(环境变量取密码)。
坑 4:忘了 SAN
openssl req -new -key key -out csr -subj "/CN=example.com"
# 浏览器拒:NET::ERR_CERT_COMMON_NAME_INVALID
现代浏览器只看 SAN、不看 CN(自 2017 Chrome 起)。永远加 SAN:
openssl req -new -key key -out csr \
-subj "/CN=example.com" \
-addext "subjectAltName=DNS:example.com"
坑 5:用 openssl rand 生密码、忘了去掉特殊字符
PASSWORD=$(openssl rand -base64 16)
# 输出含 /+= 等,URL / SQL 转义麻烦
openssl rand -hex 16 # 只含 0-9a-f
# 或
openssl rand -base64 32 | tr -d "+/="
坑 6:PEM 文件格式问题
cat cert.crt
# ... 看着对 ...
openssl x509 -in cert.crt -text
# unable to load certificate
可能:
- DOS 行尾(
\r\n)→dos2unix cert.crt - 多 cert 拼接错乱 → 每个证书必须
-----BEGIN CERTIFICATE-----到-----END CERTIFICATE-----完整 + 中间不能有空行外的字符 - DER 格式(二进制)误当 PEM →
openssl x509 -inform DER -in cert.der
坑 7:CA 和 server cert 不匹配
openssl verify -CAfile ca.crt server.crt
# error 20 at 0 depth lookup: unable to get local issuer certificate
CA 不是签 server 的那个。看:
openssl x509 -in server.crt -issuer -noout
# issuer=CN = Some Other CA ← 不是你的 ca.crt 的 CN
要么换对的 CA、要么重签 server.crt。
坑 8:在线测但跨防火墙 / 代理
openssl s_client -connect example.com:443
# Connection timeout
防火墙 / 代理。用代理:
openssl s_client -proxy proxy.example.com:8080 -connect example.com:443
或者节点上跑(绕过本地网络)。
坑 9:用 -md5 等弱算法
openssl x509 ... -md5
MD5 / SHA1 已不安全。用 SHA256 或更高。
坑 10:openssl 命令换版本行为变
老 openssl 1.0 / 1.1 / 3.x 有些参数名 / 默认行为不同。生产脚本里查对版本:
openssl version
# OpenSSL 3.0.2 ...
不一致时 stack overflow / man page 多读几个版本。
关联命令
- curl ——
curl --cert / --key / --cacert测 mTLS - kubeadm —— K8s PKI 在 /etc/kubernetes/pki/
- etcdctl —— etcd mTLS 全套
- ssh-keygen —— ssh 用 openssl 的算法,但工具不同
gpg/age—— 现代加密工具cfssl—— Cloudflare 的更易用 PKI 工具step(smallstep) —— 现代 PKI 工具集