openssl —— 证书与加密的全能工具
openssl —— 证书与加密的全能工具
分类:11 安全与审计 适用:OpenSSL 1.1.1 / 3.x(1.0.2 已 EOL) 一句话:查证书到期、验证证书链、自签证书、加解密文件,TLS 相关问题的第一工具。
1. 它解决什么问题
- 证书什么时候过期?域名对不对?证书链完不完整?
- 内网服务要自签证书,或搭私有 CA。
- 验证某台服务器的 TLS 握手、协议版本、加密套件。
- 生成随机密码、计算摘要、加解密文件。
2. 证书查看
# 本地证书文件
openssl x509 -in cert.pem -noout -text
openssl x509 -in cert.pem -noout -subject -issuer -dates
openssl x509 -in cert.pem -noout -enddate
openssl x509 -in cert.pem -noout -fingerprint -sha256
openssl x509 -in cert.pem -noout -serial
# 一行看完关键信息
openssl x509 -in cert.pem -noout -subject -issuer -dates -ext subjectAltName
# 从私钥导出公钥
openssl rsa -in key.pem -pubout
openssl pkey -in key.pem -pubout
# 校验证书与私钥是否匹配(两者 modulus 应一致)
openssl x509 -in cert.pem -noout -modulus | openssl md5
openssl rsa -in key.pem -noout -modulus | openssl md5
3. 在线检查(不落地)
# 抓取服务器证书
openssl s_client -connect example.com:443 -servername example.com </dev/null 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
# 看完整证书链
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
# 只看到期时间
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null \
| openssl x509 -noout -enddate
# 验证链是否完整(关键)
openssl s_client -connect example.com:443 -servername example.com -verify_return_error </dev/null
-servername 必须加:一个 IP 上可能挂多个站点(SNI),不加会拿到默认证书。
4. 证书链验证
openssl verify -CAfile ca-bundle.crt server.crt
openssl verify -CAfile chain.pem -untrusted intermediate.pem server.crt
常见错误:
| 错误 | 含义 | 处理 |
|---|---|---|
unable to get local issuer certificate |
缺中间证书 | 服务端配置里补 fullchain |
certificate has expired |
证书过期 | 续签 |
self signed certificate |
自签,不在信任库 | 客户端加 CA 或换正式证书 |
unable to verify the first certificate |
链不完整 | 同上 |
certificate is not yet valid |
系统时间不对 | 校时 |
生产最常见的问题就是"缺中间证书":浏览器能打开(浏览器会自动补链),但 Java/curl/Python 客户端报错。
5. 生成自签证书
一条命令(测试用)
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout key.pem -out cert.pem -days 365 \
-subj "/C=CN/ST=Zhejiang/L=Hangzhou/O=Example/CN=internal.example.com" \
-addext "subjectAltName=DNS:internal.example.com,DNS:*.internal.example.com,IP:10.0.0.5"
-nodes 表示私钥不加密(无 passphrase),生产慎用。
-addext subjectAltName 必须包含实际访问的域名/IP,否则现代客户端不认。
标准流程(CSR + CA 签发)
# 1. 生成私钥
openssl genrsa -out server.key 2048
# 或更现代
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out server.key
# EC 密钥(更快)
openssl ecparam -genkey -name prime256v1 -out server.key
# 2. 生成 CSR
openssl req -new -key server.key -out server.csr \
-subj "/C=CN/O=Example/CN=app.example.com" \
-addext "subjectAltName=DNS:app.example.com,DNS:*.app.example.com"
# 3. 查看 CSR
openssl req -in server.csr -noout -text
# 4. 用 CA 签发
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out server.crt -days 365 -sha256 \
-extfile <(printf "subjectAltName=DNS:app.example.com,DNS:*.app.example.com")
# 5. 合并成 fullchain(Nginx 用)
cat server.crt intermediate.crt > fullchain.crt
6. 私钥格式转换
# PKCS#12(.p12/.pfx,Windows/Java 常用)
openssl pkcs12 -export -in cert.pem -inkey key.pem -out cert.p12 -name "app"
openssl pkcs12 -in cert.p12 -nodes -out all.pem # 解出证书+私钥
openssl pkcs12 -in cert.p12 -nokeys -out cert.pem
openssl pkcs12 -in cert.p12 -nocerts -nodes -out key.pem
# PEM ↔ DER
openssl x509 -in cert.pem -outform der -out cert.der
openssl x509 -in cert.der -inform der -out cert.pem
# 去掉私钥口令
openssl rsa -in key-encrypted.pem -out key.pem
7. 加解密与摘要
# 摘要
openssl dgst -sha256 file.tar.gz
openssl dgst -sha256 -hmac "secret" file
# 对称加密(带 salt + PBKDF2)
openssl enc -aes-256-cbc -pbkdf2 -salt -in plain.txt -out enc.bin
openssl enc -d -aes-256-cbc -pbkdf2 -in enc.bin -out plain.txt
# 非对称签名/验签
openssl dgst -sha256 -sign private.key -out sig.bin file
openssl dgst -sha256 -verify <(openssl pkey -in private.key -pubout) -signature sig.bin file
# 生成随机密码
openssl rand -base64 24
openssl rand -hex 16
# 生成密码哈希(shadow 格式)
openssl passwd -6 'MyPassword'
openssl enc默认弱 KDF,必须加-pbkdf2;老命令不带-pbkdf2的解密结果可能不同。
8. TLS 诊断
# 支持的协议版本
openssl s_client -connect example.com:443 -tls1_2 </dev/null
openssl s_client -connect example.com:443 -tls1_3 </dev/null
# 加密套件
openssl ciphers -v 'HIGH:!aNULL'
openssl s_client -connect example.com:443 -cipher 'ECDHE-RSA-AES128-GCM-SHA256' </dev/null
# 握手耗时
openssl s_time -connect example.com:443 -new -time 10
# 检查是否支持 OCSP stapling
openssl s_client -connect example.com:443 -status </dev/null | grep -A3 'OCSP'
9. 实战场景
9.1 批量检查证书到期(巡检脚本)
#!/bin/bash
for host in app.example.com api.example.com admin.example.com; do
end=$(echo | openssl s_client -connect "$host:443" -servername "$host" 2>/dev/null \
| openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
if [ -z "$end" ]; then
echo "$host: 连接失败"
continue
fi
days=$(( ( $(date -d "$end" +%s) - $(date +%s) ) / 86400 ))
printf "%-30s %s 剩余 %d 天\n" "$host" "$end" "$days"
done
配合监控告警,剩余 30 天开始提醒。
9.2 排查"Java 客户端报 PKIX 错误"
# 1. 看服务端是否发送完整链
openssl s_client -connect host:443 -servername host -showcerts </dev/null | grep -E '^ [0-9] s:|i:'
# 2. 如果只有一张证书,说明缺中间证书
# 3. 用 fullchain 替换 Nginx 的 ssl_certificate
9.3 为内网服务签证书
# 建私有 CA(只做一次)
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
-out ca.crt -subj "/CN=Internal CA"
# 为每个服务签
openssl genrsa -out app.key 2048
openssl req -new -key app.key -out app.csr -subj "/CN=app.internal"
openssl x509 -req -in app.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out app.crt -days 825 -sha256 \
-extfile <(printf "subjectAltName=DNS:app.internal,IP:10.0.0.5")
# 分发 ca.crt 到客户端信任库
9.4 校验下载文件完整性
openssl dgst -sha256 app.tar.gz
# 与官网公布的 sha256 比对
10. 常见坑
- 忘记
-servername:SNI 场景拿到错误证书,验证结果不可信。 subjectAltName缺失:现代浏览器/客户端只看 SAN,不看 CN。- 证书链顺序错:必须是
服务器证书 → 中间证书 → 根证书(根通常不放)。 -nodes用于生产:私钥无口令,泄露即失守。openssl enc不加-pbkdf2:老 KDF 不安全,且不同版本解密可能失败。- 系统时间不对:
certificate is not yet valid,先timedatectl校时。 - 私钥权限:必须 600,Nginx 的 key 权限过宽会拒绝加载。
- 根证书与中间证书混淆:客户端信任库只装根,服务端只发中间。
-days超 825 天:公开 CA 有硬性限制,苹果/Chrome 会拒绝。openssl verify不检查 SNI:它只验本地文件,域名匹配要额外看-verify_hostname。- 1.0.2 已 EOL:不支持 TLS 1.3,尽快升级。
- PFX 密码含特殊字符:
-passin pass:xxx要用引号。
11. 速查表
openssl version -a
openssl x509 -in c.pem -noout -subject -issuer -dates -ext subjectAltName
openssl x509 -in c.pem -noout -fingerprint -sha256
echo | openssl s_client -connect h:443 -servername h 2>/dev/null | openssl x509 -noout -enddate
openssl s_client -connect h:443 -servername h -showcerts </dev/null
openssl s_client -connect h:443 -servername h -verify_return_error </dev/null
openssl verify -CAfile ca.crt server.crt
openssl req -x509 -newkey rsa:2048 -nodes -keyout k.pem -out c.pem -days 365 \
-subj "/CN=x" -addext "subjectAltName=DNS:x"
openssl pkcs12 -export -in c.pem -inkey k.pem -out c.p12
openssl dgst -sha256 file
openssl enc -aes-256-cbc -pbkdf2 -salt -in f -out f.enc
openssl rand -base64 24