3. OpenSSL 实战:安装、密钥对、CSR 与自签名证书
说实话,搞 TLS 这么多年,我见过太多人栽在 OpenSSL 的基本操作上。不是命令记不住,而是对背后的原理一知半解。今天咱们就把它彻底捋清楚。
OpenSSL 是 TLS 世界的瑞士军刀。你几乎所有的证书操作,都离不开它。但它的命令行参数确实有点多,容易让人头大。别急,我带你一步步走一遍。
本章核心目标: 从零搭建一套完整的证书签发环境。你会亲手安装 OpenSSL,生成 RSA 和 ECC 两种密钥对,创建证书签名请求(CSR),最后给自己签发一张自签名证书。这套流程,是后续所有 TLS 配置的基础。
3.1 安装 OpenSSL:别用太老的版本
我个人习惯,在任何新服务器上第一件事就是检查 OpenSSL 版本。你想想看,如果底层库有漏洞,上面再安全的配置也是白搭。
# 检查当前版本
openssl version
# 输出示例:OpenSSL 3.0.12 24 Oct 2023 (Library: OpenSSL 3.0.12 24 Oct 2023)
版本红线: 生产环境请使用 OpenSSL 1.1.1 以上版本。1.0.2 系列已在 2019 年底停止支持。我去年还遇到过客户用 1.0.1 跑 HTTPS,心脏出血漏洞都没补——这太危险了。
不同系统的安装方式略有差异,我整理了一张表,方便你对照操作:
| 操作系统 | 安装方式 | 推荐版本 |
|---|---|---|
| Ubuntu/Debian | apt install openssl libssl-dev |
≥ 1.1.1 |
| CentOS/RHEL 7 | yum install openssl openssl-devel |
≥ 1.1.1(需 EPEL) |
| CentOS/RHEL 8+ | dnf install openssl openssl-devel |
≥ 3.0 |
| macOS | brew install openssl@3 |
≥ 3.0 |
| 源码编译 | ./config && make && make install |
最新稳定版 |
我的建议: 如果你在 macOS 上开发,别用系统自带的 LibreSSL。用 Homebrew 装 OpenSSL 3.x,然后配置 PATH 优先使用它。不然你后面生成 ECC 密钥时会遇到一些奇怪的问题。
3.2 生成密钥对:RSA 还是 ECC?
这是每次做 TLS 都要面对的选择。说白了,RSA 是老兵,兼容性最好;ECC 是新锐,性能更优。我个人现在所有新项目都优先用 ECC。
3.2.1 生成 RSA 密钥对
# 生成 RSA 2048 位私钥
openssl genrsa -out server.key 2048
# 从私钥提取公钥
openssl rsa -in server.key -pubout -out server.pub
这里有个细节——2048 位是当前的最低安全标准。1024 位已经被破解,别用了。4096 位虽然更安全,但握手时计算量更大,性能开销明显。
3.2.2 生成 ECC 密钥对
# 列出支持的椭圆曲线
openssl ecparam -list_curves
# 生成 P-256(prime256v1)私钥
openssl ecparam -genkey -name prime256v1 -out ecdsa.key
# 从私钥提取公钥
openssl ec -in ecdsa.key -pubout -out ecdsa.pub
曲线选择建议: 生产环境用 prime256v1(即 NIST P-256)。secp384r1 也可以,但兼容性稍差。不要用 secp256k1(比特币用的那条曲线),浏览器不支持。
我在项目中遇到过一件事:有团队用了 Ed25519 曲线,性能确实好,但他们的 CDN 不支持,折腾了两天才发现是曲线兼容性问题。所以,除非你完全掌控整个链路,否则老老实实用 P-256。
3.3 创建 CSR:证书签名请求
CSR 就是你的证书申请书。里面包含你的公钥和身份信息,交给 CA 签名后就变成正式证书。自己玩的话,我们也可以自己签。
# 使用现有私钥创建 CSR
openssl req -new -key server.key -out server.csr
# 你会被交互式地询问以下信息:
# Country Name (2 letter code) [AU]: CN
# State or Province Name (full name) [Some-State]: Beijing
# Locality Name (eg, city) []: Beijing
# Organization Name (eg, company) [Internet Widgits Pty Ltd]: MyCompany
# Organizational Unit Name (eg, section) []: DevOps
# Common Name (e.g. server FQDN or YOUR name) []: www.example.com
# Email Address []: admin@example.com
Common Name 陷阱: 很多人以为 CN 可以随便填。实际上,浏览器现在主要看 Subject Alternative Names(SAN),但 CN 仍然是传统校验字段。我建议 CN 填写主域名,SAN 里放所有备用域名。
如果你不想交互式输入,可以用 -subj 参数一步到位:
openssl req -new -key server.key -out server.csr \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/OU=DevOps/CN=www.example.com"
3.3.1 带 SAN 扩展的 CSR
现在的浏览器强制要求 SAN。没有 SAN 的证书,Chrome 会直接报不安全。所以创建 CSR 时必须带上 SAN 扩展。
# 创建扩展配置文件 san.cnf
cat > san.cnf << 'EOF'
[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req
prompt = no
[req_distinguished_name]
C = CN
ST = Beijing
L = Beijing
O = MyCompany
OU = DevOps
CN = www.example.com
[v3_req]
keyUsage = keyEncipherment, digitalSignature
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = www.example.com
DNS.2 = example.com
DNS.3 = api.example.com
EOF
# 使用配置文件生成 CSR
openssl req -new -key server.key -out server.csr -config san.cnf
我的习惯: 把 SAN 配置文件版本化管理,和私钥分开存储。这样以后加域名时,改配置文件重新签发就行,不用重新生成私钥。
3.4 自签名证书:自己的 CA 自己当
自签名证书就是自己给自己当 CA。开发环境、内网测试用这个最方便。但记住,生产环境千万别用——浏览器会弹大红屏警告。
3.4.1 一步到位生成自签名证书
# 直接生成自签名证书(包含私钥和证书)
openssl req -x509 -newkey rsa:2048 -keyout server.key \
-out server.crt -days 365 -nodes \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyCompany/CN=www.example.com"
-nodes 参数表示不加密私钥。如果你需要密码保护,去掉这个参数就行。不过服务器启动时得手动输入密码,自动化部署会麻烦一些。
3.4.2 分步签发:更灵活的方式
我更喜欢分步操作,这样每一步都能检查中间结果:
# 第一步:生成私钥
openssl genrsa -out myca.key 2048
# 第二步:生成自签名根证书(CA 证书)
openssl req -x509 -new -key myca.key -out myca.crt -days 3650 \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyCA/CN=My Root CA"
# 第三步:用根证书签发服务器证书
openssl x509 -req -in server.csr -CA myca.crt -CAkey myca.key \
-CAcreateserial -out server.crt -days 365 -extensions v3_req \
-extfile san.cnf
关键参数说明:
-CAcreateserial:自动创建序列号文件,避免序列号冲突-days 365:证书有效期。我建议开发环境 1 年,测试环境最长 2 年-extfile san.cnf:指定 SAN 扩展配置,这一步最容易漏
3.5 验证证书:别等到部署才发现问题
证书签完之后,一定要验证。我吃过这个亏——有一次签完证书直接部署,结果浏览器报错,查了半天发现是 SAN 没配进去。
# 查看证书内容
openssl x509 -in server.crt -text -noout
# 验证证书链
openssl verify -CAfile myca.crt server.crt
# 检查 SAN 信息
openssl x509 -in server.crt -noout -ext subjectAltName
重点关注这几项:
- Validity:有效期是否正确
- Subject:CN 是否匹配你的域名
- X509v3 Subject Alternative Name:SAN 列表是否完整
- Signature Algorithm:签名算法,推荐 sha256WithRSAEncryption 或 ecdsa-with-SHA256
常见错误: 如果你看到 error 18 at 0 depth lookup: self signed certificate,这是正常的——自签名证书本来就不在系统信任库中。把它加入信任库就好了。但如果看到 error 10 at 0 depth lookup: certificate has expired,那就是有效期问题,得重新签发。
3.6 避坑指南:我踩过的那些坑
做 TLS 配置这么多年,有些坑我几乎每次培训都会提。这里列几个最常见的:
| 坑 | 表现 | 解决方案 |
|---|---|---|
| 私钥权限过大 | Nginx 启动报错 | chmod 600 server.key |
| CSR 没有 SAN | Chrome 报 NET::ERR_CERT_COMMON_NAME_INVALID | 重新生成带 SAN 的 CSR |
| 证书链不完整 | 部分客户端握手失败 | 合并中间证书到 server.crt |
| 密钥算法不匹配 | OpenSSL 加载失败 | 检查私钥和证书算法是否一致 |
| 有效期设置过长 | 安全审计不通过 | 生产环境不超过 398 天(Apple 政策) |
我曾经帮一个客户排查问题,他们的 Android App 连不上服务器。查了两天,最后发现是证书链里少了中间证书。服务器只发了叶子证书,没有发中间 CA。Android 的证书校验比桌面浏览器严格,直接就拒绝了。从那以后,我每次部署都会用 openssl s_client 模拟完整握手,确保证书链完整。
终极验证命令:
# 模拟 TLS 握手,查看服务器发送的证书链
openssl s_client -connect www.example.com:443 -showcerts
这个命令会显示服务器在握手过程中发送的所有证书。如果只显示一张,说明证书链没配全。