私钥、CSR 与证书

OpenSSL 生成 CSR:多域名 SAN、通配符、续期

作者: · 最后核实 实测 macOS · OpenSSL 3.6.1

CSR(证书签名请求)是一个带签名的小文件,里面装着你的公钥和想写进证书的域名,用 openssl req 生成后粘贴到 CA 或云厂商的申请页面。下列 OpenSSL 3.x 命令覆盖常见的 CSR 任务,从一行生成带 SAN 的请求,到用旧证书做续期 CSR。输出示例来自 OpenSSL 3.6.1。

私钥永远不交给 CA,.key 文件留在自己服务器上。用 certbot、acme.sh 这类 ACME 客户端申请免费证书时,CSR 由客户端自动生成,不需要手工做。

交互式生成 CSR

用已有私钥执行 openssl req -new,按提示逐项输入;回车跳过,输入 . 表示留空。

openssl req -new -key example.com.key -out example.com.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
...
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]:Example Ltd
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:example.com
Email Address []:
A challenge password []:
An optional company name []:

部门、邮箱、challenge password、optional company name 都留空,公网 CA 用不到,有的还会因此拒单。交互模式加不了 SAN,除了临时测试,建议直接用下面的一行命令。

一行命令同时生成私钥和 CSR

-newkey 生成私钥,-noenc 不加密,-subj 填主题,-addext 加 SAN。

openssl req -new -newkey rsa:2048 -noenc \
  -keyout example.com.key -out example.com.csr \
  -subj "/C=CN/ST=Beijing/L=Beijing/O=Example Ltd/CN=example.com" \
  -addext "subjectAltName=DNS:example.com,DNS:www.example.com"
-----

成功时只打印一行 -----。-noenc 是 OpenSSL 3.0 新增的写法,取代意为“no DES”的 -nodes,后者还能用但已弃用。OpenSSL 1.1.0 及更早版本没有 -addext,只能用配置文件。

用 -addext 写多个域名

所有要覆盖的主机名都写进同一个 subjectAltName,用逗号分隔。

openssl req -new -key example.com.key -out example.com.csr \
  -subj "/CN=example.com" \
  -addext "subjectAltName=DNS:example.com,DNS:www.example.com,DNS:api.example.com"
openssl req -in example.com.csr -noout -text | grep -A1 "Alternative Name"
                X509v3 Subject Alternative Name: 
                    DNS:example.com, DNS:www.example.com, DNS:api.example.com

Chrome 从 58 版(2017 年)起就不再看 CN,RFC 9525 也把 CN 匹配从 TLS 身份规则里删掉了。域名只写在 CN 里的证书会报 NET::ERR_CERT_COMMON_NAME_INVALID,国内的 360、QQ 浏览器等 Chromium 内核浏览器同样如此。IP 地址写成 IP:203.0.113.10,但只有部分公网 CA 签 IP 证书。

用配置文件写大量 SAN

域名多、要反复使用,或者服务器上还是 OpenSSL 1.1.0 时,配置文件更清楚。

# san.cnf
[ req ]
prompt             = no
default_md         = sha256
distinguished_name = dn
req_extensions     = v3_req

[ dn ]
C  = CN
O  = Example Ltd
CN = example.com

[ v3_req ]
subjectAltName = @alt_names

[ alt_names ]
DNS.1 = example.com
DNS.2 = www.example.com
DNS.3 = api.example.com
IP.1  = 203.0.113.10
openssl req -new -newkey rsa:2048 -noenc -keyout example.com.key \
  -out example.com.csr -config san.cnf
openssl req -in example.com.csr -noout -text | grep -A1 "Alternative Name"
                X509v3 Subject Alternative Name: 
                    DNS:example.com, DNS:www.example.com, DNS:api.example.com, IP Address:203.0.113.10

prompt = no 让 OpenSSL 不再提问,直接取 [ dn ] 里的值。req_extensions 指定写进 CSR 的扩展段;常见错误是写成 x509_extensions,那个只在 req -x509 直接生成自签证书时生效,CSR 里就会没有 SAN。

生成通配符证书的 CSR

SAN 里写 *.example.com,同时把根域名也写上,因为通配符不包含根域名。

openssl req -new -key example.com.key -out wildcard.csr \
  -subj "/CN=*.example.com" \
  -addext "subjectAltName=DNS:*.example.com,DNS:example.com"
openssl req -in wildcard.csr -noout -subject
subject=CN=*.example.com

通配符只匹配一级:*.example.com 能覆盖 www.example.com,覆盖不了 example.com 和 a.b.example.com。公网 CA 只能通过 DNS 验证通配符,ACME 里就是 dns-01,域名托管在 DNSPod、阿里云解析时可以用对应的 API 自动添加 TXT 记录。命令里的主题要加引号,否则 shell 会展开 *。

用已有私钥生成 CSR

想保留现有私钥(比如 CA 要求重新提交 CSR)时,把 -newkey 换成 -key。

openssl req -new -key example.com.key -out example.com.csr \
  -subj "/CN=example.com" -addext "subjectAltName=DNS:example.com,DNS:www.example.com"
(成功时没有输出)

私钥加了密码会提示输入,脚本里用 -passin file:secret.txt。复用私钥是允许的,但只要怀疑泄露过(比如被打包进了网站源码压缩包),就必须换新私钥。

生成 ECDSA 的 CSR

CSR 的算法由私钥决定:ECDSA 私钥生成 ECDSA CSR,签出来的也是 ECDSA 证书。

openssl req -new -newkey ec -pkeyopt ec_paramgen_curve:P-256 -noenc \
  -keyout example.com.key -out example.com.csr \
  -subj "/CN=example.com" -addext "subjectAltName=DNS:example.com,DNS:*.example.com"
openssl req -in example.com.csr -noout -text | grep -E "Public Key Algorithm|Signature Algorithm"
            Public Key Algorithm: id-ecPublicKey
    Signature Algorithm: ecdsa-with-SHA256

老教程常写 -newkey ec:params.pem,需要先生成参数文件;-newkey ec -pkeyopt ec_paramgen_curve:P-256 一步到位。想同时部署 RSA 和 ECDSA 双证书,就生成两把私钥、两个 CSR,买两张证书。

查看 CSR 内容

-text -noout 以可读形式打印主题、公钥和申请的扩展。

openssl req -in example.com.csr -noout -text
Certificate Request:
    Data:
        Version: 1 (0x0)
        Subject: C=CN, ST=Beijing, L=Beijing, O=Example Ltd, CN=example.com
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                ...
        Attributes:
            Requested Extensions:
                X509v3 Subject Alternative Name: 
                    DNS:example.com, DNS:www.example.com
    Signature Algorithm: sha256WithRSAEncryption

Version: 1 (0x0) 是正常的,PKCS#10 只有这一个版本。只看主题用 -subject;提交前也可以把 CSR 贴进 CSR 解析工具确认域名没写错。

校验 CSR 并确认对应的私钥

-verify 校验 CSR 的签名,比对公钥哈希可以确认它属于哪把私钥。

openssl req -in example.com.csr -noout -verify
openssl req -in example.com.csr -noout -pubkey | openssl sha256
openssl pkey -in example.com.key -pubout | openssl sha256
Certificate request self-signature verify OK
SHA2-256(stdin)= 8c6044a61e57fc591b9842bbdb3dbde4c074e8257ba907889023a000ed166588
SHA2-256(stdin)= 8c6044a61e57fc591b9842bbdb3dbde4c074e8257ba907889023a000ed166588

复制粘贴时少了行、混进全角字符或多余空格,会报 unable to load X509 request 或 ASN.1 wrong tag。确认 BEGIN CERTIFICATE REQUEST 和 END CERTIFICATE REQUEST 两行都在,从微信、QQ 里转发过的 CSR 尤其要检查。

从旧证书生成续期 CSR

openssl x509 -x509toreq 能从现有证书复制主题,必须加 -copy_extensions copy,否则 SAN 会丢。

openssl x509 -x509toreq -in example.com.crt -key example.com.key \
  -copy_extensions copy -out renew.csr
openssl req -in renew.csr -noout -text | grep -A1 "Alternative Name"
                X509v3 Subject Alternative Name: 
                    DNS:example.com, DNS:www.example.com, IP Address:203.0.113.10

-copy_extensions 从 OpenSSL 3.0 才有。不加它,新 CSR 只有主题没有 SAN,签出来的证书可能悄悄少了 www。-key 是 3.x 对老参数 -signkey 的新名字。1.1.1 上请改用配置文件重新生成。

使用中文组织名

加 -utf8 让 OpenSSL 按 UTF-8 读取主题,否则中文公司名会变成乱码。

openssl req -new -key example.com.key -utf8 \
  -subj "/C=CN/O=示例科技有限公司/CN=example.com" -out zh.csr
openssl req -in zh.csr -noout -subject
subject=C=CN, O=示例科技有限公司, CN=example.com

不加 -utf8 时字节会被当成 Latin-1 存进去,签发后显示成乱码。只有 OV、EV 证书会显示公司名,CA 最终会用它核验过的工商注册名称(通常与营业执照一致)。CN 和 SAN 保持英文;中文域名要先转成 xn-- 开头的 Punycode 再写入。

选择签名哈希

OpenSSL 3.x 默认用 SHA-256 给 CSR 签名,所有 CA 都接受,一般不用改。

openssl req -new -key example.com.key -sha384 -subj "/CN=example.com" -out example.com.csr
openssl req -in example.com.csr -noout -text | grep "Signature Algorithm"
    Signature Algorithm: sha384WithRSAEncryption

CSR 自身的哈希只用来证明你持有私钥,证书用什么签名算法由 CA 决定。没有理由改默认值,SHA-1 签名的 CSR 会被公网 CA 拒绝。也可以用 CSR 生成工具在浏览器里生成;拿到证书后,用证书查看命令核对域名和有效期。

常见问题

国家、省份、公司名这些字段必须填吗?

DV 证书不需要,CA 只验证域名,其余字段会被忽略或删掉。OV、EV 证书的公司信息由 CA 单独核验,最终以核验结果为准。部门(OU)字段从 2022 年 9 月起已禁止出现在公网 TLS 证书里,留空即可。

在阿里云、腾讯云买证书时,CSR 自己生成还是让平台生成?

国内云厂商的证书控制台一般提供“系统自动生成”和“手动上传 CSR”两种方式。自动生成更省事,但私钥由平台生成后再给你下载;对私钥管控有要求的,用上面的命令在自己服务器上生成,再把 CSR 粘贴上去。

每次续期都要重新生成 CSR 吗?

技术上可以复用旧 CSR,因为里面只有公钥和域名。更好的做法是续期时连私钥一起换新,certbot、acme.sh 等 ACME 客户端默认就是这样做的。

资料来源

  1. OpenSSL 手册:openssl-req
  2. OpenSSL 3.0 手册:x509v3_config
  3. OpenSSL 3.0 手册:openssl-x509
  4. RFC 2986:PKCS #10 证书请求语法
  5. RFC 9525:TLS 中的服务身份