CA 证书详解:从原理到实践的完整教程 / 第 6 章:OpenSSL 工具
第 6 章:OpenSSL 工具
OpenSSL 是 PKI 生态中最核心的工具集,几乎所有证书操作都可以通过它完成。本章系统讲解 OpenSSL 在证书管理中的常用命令和技巧。
6.1 OpenSSL 基础
版本与配置
# 查看版本
openssl version
# OpenSSL 3.0.2 15 Mar 2022 (Library: OpenSSL 3.0.2 15 Mar 2022)
# 查看完整编译信息
openssl version -a
# 查看默认目录
openssl version -d
# OPENSSLDIR: "/usr/lib/ssl"
# 查看支持的算法
openssl list -public-key-algorithms
openssl list -cipher-algorithms | head -20
openssl list -digest-algorithms | head -20
# 查看支持的椭圆曲线
openssl ecparam -list_curves | head -20
OpenSSL 3.x 变化
| 特性 | OpenSSL 1.1.x | OpenSSL 3.x |
|---|---|---|
| Provider 架构 | 内置 | 模块化 Provider |
| 弃用 API | 直接使用 | 弃用警告 |
| 默认安全级别 | 1 | 1(可配置) |
| OSSL_PARAM | 不支持 | 新参数传递方式 |
| FIPS 支持 | 单独编译 | 内置 FIPS Provider |
# 查看 OpenSSL 3.x 的 Provider
openssl list -providers
# default
# base
6.2 生成密钥对
RSA 密钥
# 生成 RSA 2048 密钥
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out rsa-2048.key
# 生成 RSA 4096 密钥(更高安全性)
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out rsa-4096.key
# 使用传统命令(仍然有效但推荐使用 genpkey)
openssl genrsa -out rsa-2048-legacy.key 2048
# 加密私钥(使用 AES-256-CBC)
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \
-aes-256-cbc -pass pass:MyPassword -out rsa-encrypted.key
# 查看私钥信息
openssl pkey -in rsa-2048.key -text -noout | head -10
ECDSA 密钥
# 生成 ECDSA P-256 密钥
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out ecdsa-p256.key
# 生成 ECDSA P-384 密钥
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-384 -out ecdsa-p384.key
# 查看支持的曲线
openssl ecparam -list_curves | grep -E "prime256v1|secp384r1|secp521r1"
# 查看密钥信息
openssl pkey -in ecdsa-p256.key -text -noout
Ed25519 密钥
# 生成 Ed25519 密钥(需要 OpenSSL 1.1.1+)
openssl genpkey -algorithm Ed25519 -out ed25519.key
# 查看密钥
openssl pkey -in ed25519.key -text -noout
密钥大小对比
| 算法 | 密钥长度 | 安全等效 | 文件大小 | 性能 |
|---|---|---|---|---|
| RSA | 2048 bit | ~112 bit | ~1.7 KB | 较慢 |
| RSA | 4096 bit | ~140 bit | ~3.2 KB | 慢 |
| ECDSA P-256 | 256 bit | ~128 bit | ~230 B | 快 |
| ECDSA P-384 | 384 bit | ~192 bit | ~320 B | 快 |
| Ed25519 | 256 bit | ~128 bit | ~120 B | 最快 |
6.3 生成 CSR(Certificate Signing Request)
基本 CSR 生成
# 使用交互式提示
openssl req -new -key server.key -out server.csr
# 使用 -subj 参数(非交互式)
openssl req -new -key server.key -out server.csr \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrganization/OU=IT/CN=example.com"
# 字段说明:
# C - Country(国家代码,2 个字符)
# ST - State/Province(省/州)
# L - Locality(城市)
# O - Organization(组织名称)
# OU - Organizational Unit(部门)
# CN - Common Name(域名,通常是主域名)
包含 SAN 的 CSR
# 创建配置文件
cat > csr-san.cnf << 'EOF'
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = v3_req
[dn]
C = CN
ST = Beijing
L = Beijing
O = MyOrganization
OU = IT
CN = example.com
[v3_req]
subjectAltName = @alt_names
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
[alt_names]
DNS.1 = example.com
DNS.2 = www.example.com
DNS.3 = api.example.com
DNS.4 = *.staging.example.com
IP.1 = 192.168.1.100
IP.2 = 10.0.0.1
EOF
# 生成 CSR
openssl req -new -key server.key -out server-san.csr -config csr-san.cnf
# 验证 CSR
openssl req -in server-san.csr -text -noout | grep -A20 "Subject Alternative Name"
验证 CSR
# 查看 CSR 详细信息
openssl req -in server.csr -text -noout
# 验证 CSR 签名
openssl req -in server.csr -verify -noout
# 提取 CSR 中的公钥
openssl req -in server.csr -pubkey -noout > csr-pubkey.pem
# 对比密钥和 CSR 是否匹配
KEY_MD5=$(openssl pkey -in server.key -pubout 2>/dev/null | md5sum)
CSR_MD5=$(openssl req -in server.csr -pubkey -noout 2>/dev/null | md5sum)
if [ "$KEY_MD5" = "$CSR_MD5" ]; then
echo "✅ 密钥和 CSR 匹配"
else
echo "❌ 密钥和 CSR 不匹配!"
fi
6.4 自签名证书
基本自签名
# 一步生成自签名证书(RSA)
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout selfsigned.key -out selfsigned.crt \
-days 365 \
-subj "/C=CN/ST=Beijing/O=Dev/CN=localhost"
# ECDSA 自签名
openssl req -x509 -newkey ec -pkeyopt ec_paramgen_curve:P-256 \
-nodes -keyout selfsigned-ec.key -out selfsigned-ec.crt \
-days 365 \
-subj "/C=CN/ST=Beijing/O=Dev/CN=localhost"
生产级自签名配置
cat > selfsigned.cnf << 'EOF'
[req]
default_bits = 2048
prompt = no
default_md = sha256
x509_extensions = v3_ext
distinguished_name = dn
[dn]
C = CN
ST = Beijing
L = Beijing
O = MyOrganization
OU = DevOps
CN = myapp.internal
[v3_ext]
subjectAltName = @alt_names
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth, clientAuth
authorityInfoAccess = @aia
[alt_names]
DNS.1 = myapp.internal
DNS.2 = *.myapp.internal
DNS.3 = localhost
IP.1 = 127.0.0.1
IP.2 = ::1
[aia]
OCSP;URI.0 = http://ocsp.myca.internal/
EOF
# 生成证书
openssl req -x509 -newkey rsa:2048 -nodes \
-keyout myapp.key -out myapp.crt \
-days 365 -config selfsigned.cnf
# 验证
openssl x509 -in myapp.crt -text -noout
从已有 CSR 生成自签名证书
# 从 CSR 签发自签名证书(相当于自己当 CA)
openssl x509 -req -in server.csr \
-signkey server.key \
-out server-selfsigned.crt \
-days 365 \
-extfile selfsigned.cnf -extensions v3_ext
6.5 创建私有 CA
创建 CA 目录结构
mkdir -p ~/my-ca/{certs,crl,newcerts,private}
chmod 700 ~/my-ca/private
touch ~/my-ca/index.txt
echo 1000 > ~/my-ca/serial
CA 配置文件
cat > ~/my-ca/ca.cnf << 'EOF'
[ca]
default_ca = CA_default
[CA_default]
dir = /home/user/my-ca
certs = $dir/certs
crl_dir = $dir/crl
new_certs_dir = $dir/newcerts
database = $dir/index.txt
serial = $dir/serial
RANDFILE = $dir/private/.rand
private_key = $dir/private/ca.key
certificate = $dir/certs/ca.crt
crlnumber = $dir/crlnumber
crl = $dir/crl/ca.crl
crl_extensions = crl_ext
default_crl_days = 30
default_md = sha256
name_opt = ca_default
cert_opt = ca_default
default_days = 375
preserve = no
policy = policy_loose
[policy_loose]
countryName = optional
stateOrProvinceName = optional
organizationName = optional
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
[req]
default_bits = 2048
distinguished_name = req_distinguished_name
string_mask = utf8only
default_md = sha256
x509_extensions = v3_ca
[req_distinguished_name]
countryName = Country Name (2 letter code)
stateOrProvinceName = State or Province Name
localityName = Locality Name
0.organizationName = Organization Name
organizationalUnitName = Organizational Unit Name
commonName = Common Name
emailAddress = Email Address
[v3_ca]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
[v3_intermediate_ca]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints = critical, CA:true, pathlen:0
keyUsage = critical, digitalSignature, cRLSign, keyCertSign
[server_cert]
basicConstraints = CA:FALSE
nsCertType = server
nsComment = "Generated by My CA"
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
keyUsage = critical, digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = ${ENV::CERT_DOMAIN}
DNS.2 = www.${ENV::CERT_DOMAIN}
[crl_ext]
authorityKeyIdentifier = keyid:always
EOF
生成 CA 根证书
# 生成 CA 私钥
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 \
-out ~/my-ca/private/ca.key
chmod 400 ~/my-ca/private/ca.key
# 生成 CA 自签名证书
openssl req -config ~/my-ca/ca.cnf \
-key ~/my-ca/private/ca.key \
-new -x509 -days 7300 -sha256 \
-extensions v3_ca \
-out ~/my-ca/certs/ca.crt \
-subj "/C=CN/ST=Beijing/O=MyCA/CN=My Root CA"
# 验证 CA 证书
openssl x509 -in ~/my-ca/certs/ca.crt -text -noout | head -20
用 CA 签发服务器证书
# 1. 生成服务器私钥
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \
-out server.key
# 2. 生成 CSR
export CERT_DOMAIN="example.com"
openssl req -config ~/my-ca/ca.cnf \
-key server.key \
-new -sha256 \
-out server.csr \
-subj "/C=CN/ST=Beijing/O=MyOrg/CN=example.com"
# 3. CA 签发证书
openssl ca -config ~/my-ca/ca.cnf \
-extensions server_cert \
-days 375 -notext -md sha256 \
-in server.csr \
-out server.crt \
-batch
# 4. 验证证书
openssl verify -CAfile ~/my-ca/certs/ca.crt server.crt
# server.crt: OK
# 5. 查看证书
openssl x509 -in server.crt -text -noout | grep -E "Issuer|Subject|Not"
6.6 验证证书
基本验证
# 使用系统信任存储验证
openssl verify -CApath /etc/ssl/certs server.crt
# 使用指定 CA 验证
openssl verify -CAfile ca.crt server.crt
# 验证证书链
openssl verify -CAfile root-ca.crt -untrusted intermediate-ca.crt leaf.crt
# 验证远程服务器证书
echo | openssl s_client -connect example.com:443 -servername example.com \
-verify 5 -CApath /etc/ssl/certs 2>&1 | grep "Verify"
检查证书详细信息
# 查看证书完整信息
openssl x509 -in cert.pem -text -noout
# 只查看特定字段
openssl x509 -in cert.pem -noout -subject # 主题
openssl x509 -in cert.pem -noout -issuer # 颁发者
openssl x509 -in cert.pem -noout -dates # 有效期
openssl x509 -in cert.pem -noout -serial # 序列号
openssl x509 -in cert.pem -noout -fingerprint # 指纹
openssl x509 -in cert.pem -noout -ocsp_uri # OCSP 地址
openssl x509 -in cert.pem -noout -text | grep -A5 "Subject Alternative" # SAN
# 提取公钥
openssl x509 -in cert.pem -pubkey -noout
# 检查证书是否过期
openssl x509 -in cert.pem -checkend 0
# 如果已过期: Certificate will expire
# 如果未过期: Certificate will not expire
# 检查是否在 30 天内过期
openssl x509 -in cert.pem -checkend 2592000
# 2592000 = 30 * 24 * 3600 秒
对比两个证书
# 对比证书指纹
openssl x509 -in cert1.pem -noout -fingerprint -sha256
openssl x509 -in cert2.pem -noout -fingerprint -sha256
# 对比证书和私钥是否匹配
CERT_MD5=$(openssl x509 -in cert.pem -noout -modulus | md5sum)
KEY_MD5=$(openssl rsa -in key.pem -noout -modulus 2>/dev/null | md5sum)
[ "$CERT_MD5" = "$KEY_MD5" ] && echo "✅ 匹配" || echo "❌ 不匹配"
# 对于 ECDSA 密钥
CERT_PUB=$(openssl x509 -in cert.pem -pubkey -noout)
KEY_PUB=$(openssl pkey -in key.pem -pubout 2>/dev/null)
[ "$CERT_PUB" = "$KEY_PUB" ] && echo "✅ 匹配" || echo "❌ 不匹配"
6.7 格式转换
PEM ↔ DER
# PEM → DER
openssl x509 -in cert.pem -outform DER -out cert.der
# DER → PEM
openssl x509 -in cert.der -inform DER -outform PEM -out cert.pem
# 私钥 PEM → DER
openssl pkey -in key.pem -outform DER -out key.der
# 私钥 DER → PEM
openssl pkey -in key.der -inform DER -outform PEM -out key.pem
创建 PKCS#12 (.p12)
# 将证书和私钥打包为 PKCS#12
openssl pkcs12 -export \
-in cert.pem -inkey key.pem \
-certfile chain.pem \
-out cert.p12 \
-name "my-server" \
-passout pass:changeit
# 查看 PKCS#12 内容
openssl pkcs12 -in cert.p12 -info -nokeys -passin pass:changeit
openssl pkcs12 -in cert.p12 -info -nocerts -passin pass:changeit
# 从 PKCS#12 提取证书
openssl pkcs12 -in cert.p12 -clcerts -nokeys -out cert-extracted.pem -passin pass:changeit
# 从 PKCS#12 提取私钥
openssl pkcs12 -in cert.p12 -nocerts -out key-extracted.pem -passin pass:changeit
# 去除私钥密码保护
openssl pkey -in key-encrypted.pem -out key-decrypted.pem -passin pass:changeit
PKCS#12 → Java Keystore
# 将 PKCS#12 导入 Java Keystore
keytool -importkeystore \
-srckeystore cert.p12 -srcstoretype PKCS12 -srcstorepass changeit \
-destkeystore keystore.jks -deststoretype JKS -deststorepass changeit \
-alias my-server
# 查看 Java Keystore
keytool -list -keystore keystore.jks -storepass changeit
# 导入 CA 证书到 Java Keystore
keytool -import -trustcacerts \
-alias my-ca \
-file ca.crt \
-keystore $JAVA_HOME/lib/security/cacerts \
-storepass changeit
6.8 密钥管理
加密/解密私钥
# 加密私钥
openssl pkey -in key.pem -aes-256-cbc -passout pass:MyPassword -out key-encrypted.pem
# 解密私钥
openssl pkey -in key-encrypted.pem -passin pass:MyPassword -out key-decrypted.pem
# 修改私钥密码
openssl pkey -in key-encrypted.pem -passin pass:OldPassword \
-aes-256-cbc -passout pass:NewPassword -out key-re-encrypted.pem
密钥安全存储
# 设置严格的文件权限
chmod 600 *.key
chmod 644 *.crt *.pem
# 建议的目录权限
chmod 700 /etc/ssl/private/
# 验证私钥不被泄露
ls -la /etc/ssl/private/
# drwx------ 2 root root 4096 ... .
# -rw------- 1 root root 1704 ... server.key
🔒 安全:私钥文件权限必须是
600(仅所有者可读写)。如果私钥被泄露,所有使用该证书的服务都应视为已泄露,需要立即吊销证书并重新签发。
6.9 实用脚本
证书信息一键查看
#!/usr/bin/env bash
# cert-info.sh - 快速查看证书信息
# 用法: ./cert-info.sh <file_or_url>
TARGET="${1:?用法: $0 <file_or_url>}"
if [ -f "$TARGET" ]; then
# 本地文件
openssl x509 -in "$TARGET" -text -noout
elif echo "$TARGET" | grep -qE "^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"; then
# 域名
echo | openssl s_client -connect "${TARGET}:443" -servername "$TARGET" \
-verify 5 -CApath /etc/ssl/certs 2>/dev/null | openssl x509 -text -noout
else
echo "❌ 无效参数: $TARGET"
exit 1
fi
密钥与证书匹配检查
#!/usr/bin/env bash
# check-match.sh - 检查密钥、证书、CSR 是否匹配
# 用法: ./check-match.sh <key> <cert_or_csr>
KEY="${1:?用法: $0 <key> <cert_or_csr>}"
CERT="${2:?用法: $0 <key> <cert_or_csr>}"
# 获取密钥模数
KEY_MOD=$(openssl pkey -in "$KEY" -pubout 2>/dev/null | openssl md5)
# 获取证书/CSR 模数
if head -1 "$CERT" | grep -q "CERTIFICATE"; then
CERT_MOD=$(openssl x509 -in "$CERT" -pubkey -noout 2>/dev/null | openssl md5)
elif head -1 "$CERT" | grep -q "CERTIFICATE REQUEST"; then
CERT_MOD=$(openssl req -in "$CERT" -pubkey -noout 2>/dev/null | openssl md5)
else
echo "❌ 无法识别文件格式"
exit 1
fi
if [ "$KEY_MOD" = "$CERT_MOD" ]; then
echo "✅ 密钥和证书/CSR 匹配"
else
echo "❌ 密钥和证书/CSR 不匹配"
echo " Key MD5: $KEY_MOD"
echo " Cert MD5: $CERT_MOD"
fi
6.10 本章小结
| 命令类别 | 常用命令 | 说明 |
|---|---|---|
| 密钥生成 | openssl genpkey | 推荐的密钥生成方式 |
| CSR 生成 | openssl req -new | 配合 -config 使用 SAN |
| 自签名 | openssl req -x509 | 开发测试用 |
| CA 签发 | openssl ca | 需要 CA 配置文件 |
| 验证 | openssl verify | 验证证书链 |
| 查看 | openssl x509 -text | 查看证书详情 |
| 转换 | openssl x509/pkey/pkcs12 | PEM/DER/PKCS#12 互转 |
📚 扩展阅读
上一章:第 5 章:证书管理 下一章:第 7 章:Let’s Encrypt — 学习 ACME 协议和 Let’s Encrypt 自动化证书签发。