openssl —— 证书与加密的全能工具

运维工具手册 11-安全与审计 7.0 KB 原文

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. 常见坑

  1. 忘记 -servername:SNI 场景拿到错误证书,验证结果不可信。
  2. subjectAltName 缺失:现代浏览器/客户端只看 SAN,不看 CN。
  3. 证书链顺序错:必须是 服务器证书 → 中间证书 → 根证书(根通常不放)。
  4. -nodes 用于生产:私钥无口令,泄露即失守。
  5. openssl enc 不加 -pbkdf2:老 KDF 不安全,且不同版本解密可能失败。
  6. 系统时间不对:certificate is not yet valid,先 timedatectl 校时。
  7. 私钥权限:必须 600,Nginx 的 key 权限过宽会拒绝加载。
  8. 根证书与中间证书混淆:客户端信任库只装根,服务端只发中间。
  9. -days 超 825 天:公开 CA 有硬性限制,苹果/Chrome 会拒绝。
  10. openssl verify 不检查 SNI:它只验本地文件,域名匹配要额外看 -verify_hostname。
  11. 1.0.2 已 EOL:不支持 TLS 1.3,尽快升级。
  12. 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