☰
OpenSSL证书签发全流程:从CSR到CA签名避坑指南
2026/10/20 14:52:06 网站建设 项目流程

证书这东西,平时用不着,真到要用的时候,能卡掉人半条命。尤其是自己搭的测试环境、内部服务、或者做HTTPS双向认证,OpenSSL的命令来来回回就那几个,但每次都能遇到点幺蛾子。要么SAN没带,要么私钥权限问题,要么签名算法太老直接被拒。这篇就把我从CSR申请到CA签名的完整流程掰开揉碎讲一遍,重点放在踩过的坑上,希望对正在折腾证书的你有点帮助。

1. 项目概述与核心需求解析

1.1 证书体系是什么,能解决什么问题

先明确一个概念:数字证书本质上是“身份证明 + 加密钥匙包”。它解决两个问题,一是证明“我是谁”,二是让通信双方能安全地交换密钥。

以典型的HTTPS站点为例,浏览器访问一个网站时,网站会亮出自己的证书。浏览器检查证书是否由可信机构(CA)签发、是否过期、域名是否匹配,确认无误后再用证书里的公钥协商加密通道。这就是一次完整的信任链验证。

具体到内部系统,场景更丰富。比如公司内部搭建的Git服务器、Docker仓库、Kubernetes集群、Kafka或RabbitMQ之间的TLS加密通信,这些统统离不开证书。用公开CA签吧,费钱费事,内网域名还可能被拒绝;自己动手用OpenSSL搭一套内部CA然后签发证书,就成了最务实的方案。

适合读这篇的,我猜主要是这几类人:运维工程师要自建CA统一管理内部组件证书;后端开发在调试HTTPS接口或者配置Nginx反向代理时需要一张本地证书;安全测试的同学做中间人代理测试时需要伪造证书。无论哪类,掌握了OpenSSL这套东西,走到哪里都不会慌。

1.2 为什么选OpenSSL,以及你可能正在犯的认知错误

市面上有CFSSL、Easy-RSA、mkcert等工具,但OpenSSL到底还是绕不开的底层依赖。原因很简单:几乎所有框架、服务器、语言库底层用的就是OpenSSL,其他工具生成的证书最终也得以OpenSSL能解析的格式存在。

很多人以为用OpenSSL只是“敲几下命令”,其实不然。最容易踩坑的是对证书“格式”的三个误区。

第一,PEM和CRT不是两回事。PEM是Base64编码文本,用记事本打开能看到-----BEGIN CERTIFICATE-----的,CRT只是一种扩展名,内容完全可能是PEM。很多人把.crt文件当成二进制读,结果变乱码,急得直跺脚,其实用文本编辑器打开就对了。

第二,私钥绝对不要带密码生成(或至少封存时再加密)。内网证书尤其如此,Nginx、Tomcat、Java应用加载私钥时都需要密钥库密码,但很多程序不提供输入密码的参数。一旦设了密码,启动服务时就会卡在密码输入阶段,头大得很。生产环境如需加密保存私钥文件,用单独的工具做信封加密,别直接写在证书生成流程里。

第三,证书有效期尽量别太短。默认自签CA证书通常以365天为周期,具体取决于OpenSSL配置。测试没所谓,但如果是内部长期使用的根证书,建议签个10年以上,否则每年一续期,光通知各业务方更新信任链就能烦死你。

2. 环境准备与关键文件管理

2.1 检查OpenSSL版本,优先3.x

环境这块,第一个动作不是急着敲openssl req,而是先看版本。

openssl version

实测中,CentOS 7自带的通常是OpenSSL 1.0.2系列,Debian 10带的是1.1.1系列,而这两者的默认签名算法和加密强度都有区别。如果你用的是OpenSSL 3.x,MD5、SHA1这类弱算法默认直接禁止,配置不当会报错。

我个人的建议:统一用OpenSSL 3.x版本进行后续操作。这里有两点客观优势。一是支持现代加密套件,比如TLS 1.3;二是内部CA签发的证书若交给客户端验证,各语言库对老版本OpenSSL生成的证书校验策略更严格,容易出兼容问题,新版本反而更干净。

安装新版本没什么难度,各平台包管理器都有,注意编译安装时带上enable-fips与否看自己需求,别手贱加一堆额外编译参数,默认安装就够日常使用。

2.2 搭建好干净的证书目录结构

很多人把证书文件随便丢,会带来两个直接问题:权限不好控,搞不清哪个私钥对应哪张证书。

我的习惯,是单独建一个目录专门管理内部PKI。废话不多说,直接给一套我一直在用的目录骨架:

mkdir -p ~/pki/ca mkdir -p ~/pki/certs mkdir -p ~/pki/private mkdir -p ~/pki/crl touch ~/pki/index.txt echo 1000 > ~/pki/serial

这样设计的目的,是把CA相关的材料(根证书的私钥、自签证书、撤销列表)和业务证书(各服务使用的证书)分开存放。后面所有证书都从certs/目录输出,扫描的时候一目了然。

还有一个常被忽略的点:index.txt和serial是OpenSSL维护证书签发记录的数据库。没有这两个文件,后续执行证书撤销、查看已签发列表等功能时会直接报错,所以即便你现在不需要撤销功能,也先建好放着。

注意:私钥目录我习惯直接设700权限。之前见过有人把私钥放在家目录下,默认644权限,相当于所有本地用户都能读,这等于把大门钥匙放在蹭脚垫下面。chmod 700 ~/pki/private是底线操作。

3. 私钥生成与CSR申请的完整实操

3.1 私钥生成:算法、位数与格式选择

生成私钥,第一个决策是用RSA还是ECC。

RSA的兼容性是最好的,几乎所有服务端和客户端都支持,排障时不用怀疑是算法问题。推荐用RSA 2048位起步,4096更安全但也更慢。如果是测试环境,2048完全够用;生产环境追求效率与安全的平衡,一般也是2048。

ECC(椭圆曲线)证书的优势是密钥短、握手快,目前主流选择是prime256v1,也就是常说的P-256。它的性能在移动端和IoT设备上有明显优势。不过前提是,你的业务系统、所依赖的库必须都支持ECC,否则生成出来后没有服务端能加载,白忙一场。

实操命令如下:

# RSA 2048 openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out ~/pki/private/example.key # 或者用传统参数格式 openssl genrsa -out ~/pki/private/example.key 2048 # ECC P-256 openssl ecparam -genkey -name prime256v1 -out ~/pki/private/example.key

两条RSA命令的区别在于输出格式。genpkey是PKCS#8格式,头部是BEGIN PRIVATE KEY;genrsa生成的头部是BEGIN RSA PRIVATE KEY。两者都能被主流服务加载,但如果交给Java的KeyStore或者某些严格校验的库,PKCS#8更省事,推荐直接用genpkey。

命令执行后,建议立刻检查私钥权限:chmod 600 ~/pki/private/example.key。这一步别省,绝大多数后续问题都从权限太松开始。

3.2 生成CSR:字段填写与细节

私钥就绪后,就该做CSR申请了。加密理论绕得人头发麻,但你只需要记住:CSR就是一张“申请表”,里面填着你的身份信息、公钥和你期望的证书属性,由CA审核后签名。

生成CSR的命令:

openssl req -new \ -key ~/pki/private/example.key \ -out ~/pki/certs/example.csr \ -subj "/C=CN/ST=Beijing/L=Beijing/O=ExampleOrg/CN=example.internal"

这些字段的含义:C是国籍、ST是省份、L是城市、O是组织名,CN是最关键的字段——通用名。对服务器证书来说,CN通常填域名或主机名。

有几个细节容易被忽视。

第一,-subj参数如果不加,OpenSSL会进入交互模式,一个个问你国家、省份、城市、组织名称,不填完不干活。但交互模式下很容易填错一层又懒得撤销重来,既然都用脚本批处理了,直接以-subj方式填好,干净利落。

第二,CN字段值如果和最终访问域名不一致,浏览器会报域名不匹配。比如你申请证书时CN是example.internal,但实际访问地址是example.local,浏览器照样打红叉。规避方法是后面用SAN扩展确保写上全部相关域名。

第三,邮件地址、组织单位这类属性可填可不填,但千万别在CN里塞邮箱地址,很多人图省事把CN=admin@example.com这种写法当默认,这会造成“证书是身份证书还是服务器证书”的语义混乱,一部分客户端会直接拒绝。

3.3 CSR审核与关键信息检查

写完CSR别急着拿去签名,先自己审一遍。

openssl req -text -noout -verify -in ~/pki/certs/example.csr

这个命令能解析CSR内容,看看公钥信息、DN字段是否符合预期。里面有个很容易被忽略的细节:CSR里显示的公钥长度。如果你生成私钥时手滑用了1024位,这里就能直接看出来,免得签完才发现。

另外要检查CSR和私钥是否匹配:

# 检查私钥的公共部分和CSR的公共部分是否一致 openssl pkey -pubout -in ~/pki/private/example.key | sha256sum openssl req -pubkey -noout -in ~/pki/certs/example.csr | sha256sum

两个哈希一致,说明这把私钥对应的公钥确实在CSR里。不匹配的话,签名出来的证书即使在CA那端成功了,你部署时服务端加载也会报错,因为私钥属于另一家人。

4. CA证书创建与自签名

4.1 初始化CA私钥与根证书

现在进入最核心的一步:自己当一回CA,签出自己的根证书。这个根证书将被用来签发后续所有业务证书。

首先为CA生成私钥,操作逻辑跟生成业务私钥完全一样:

openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -out ~/pki/ca/ca.key chmod 600 ~/pki/ca/ca.key

CA私钥比普通业务私钥要更长,我固定用4096位,毕竟它的泄露意味着整个信任链崩塌,所有依赖它签发的证书都不可信。

然后生成自签根证书。自签的意思是“自己给自己签”,没有上级CA。命令里需要加的-x509参数表示直接输出证书而不是CSR,-days指定有效期:

openssl req -x509 -new \ -key ~/pki/ca/ca.key \ -out ~/pki/ca/ca.crt \ -days 3650 \ -subj "/C=CN/ST=Beijing/L=Beijing/O=ExampleOrg Root CA/CN=ExampleOrg Root CA"

这里有几处容易翻车的地方,都是血泪。

第一,根证书的CN千万不要和业务证书的CN重复。根证书是“签发机构”的身份,业务证书是“网站/服务”的身份,混在一起后,部分客户端在构造信任链时会把根证书当成业务证书去匹配域名,导致紊乱。

第二,CA证书的Basic Constraints必须包含CA:TRUE。自签时OpenSSL默认会写进去,但这要验证。

openssl x509 -text -noout -in ~/pki/ca/ca.crt | grep -A 1 "Basic Constraints"

输出里必须出现CA:TRUE。没有这行意味着这张证书不具备“签发下级证书”的资格,用它在后面签名会失败,或者说签出来的证书客户端不认。

第三,根证书的有效期建议10年起。内部CA一年一换信任链,成本极高——每个服务、每台机器、每个客户端都要重新信任新根证书。更合理的策略是:根证书设10年,用它签发2~3年有效期的业务证书,到期只换业务证书,根证书在原地坐着不动。

提示:root CA证书生成后,马上做一遍自检:openssl x509 -in ~/pki/ca/ca.crt -text -noout。重点看Signature Algorithm是不是sha256WithRSAEncryption(OpenSSL 3.x默认),Version是不是v3(也就是序列号是2),而不是v1。v1证书不支持扩展区,没法带SAN,属于老古董,必须重签。

4.2 用配置文件降低繁琐程度

直接在命令行里写一长串参数容易出错,还不好复现。我现在的做法,是提前备一份OpenSSL配置文件,把CA要用到的扩展项固化进去。

这里给出一份我常用的CA签发配置模板,路径假设为~/pki/ca/openssl.cnf:

[ ca ] default_ca = my_internal_ca [ my_internal_ca ] dir = /home/you/pki database = $dir/index.txt serial = $dir/serial new_certs_dir = $dir/certs certificate = $dir/ca/ca.crt private_key = $dir/ca/ca.key default_md = sha256 policy = policy_loose [ policy_loose ] countryName = optional stateOrProvinceName = optional organizationName = optional organizationalUnitName = optional commonName = supplied [ v3_ca ] basicConstraints = critical, CA:TRUE keyUsage = critical, keyCertSign, cRLSign subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always, issuer

这份配置里刻意做了几件事:policy_loose放宽了对CSR中字段的要求,这样签发的证书不需要每个字段都填得满满当当;basicConstraints明确了这是CA证书;keyUsage限制这把私钥只能用来签证书和CRL,不能拿去加密数据。注意:实际生产部署请根据你自己的路径调整dir那一行。

配置就位后,后续所有证书签发都可以用一条命令搞定,后面会提到。

5. 使用CA签名:从CSR到正式证书

5.1 签名前配置准备

业务CSR已经生成、CA根证书和私钥都在,接下来就是最解气的时刻——把CSR变成正式证书。但先别急,签名前还有个关键准备:SAN(Subject Alternative Name)扩展。

SAN的作用是告诉客户端“本证书同时适用于以下多个域名/IP”。这是现代浏览器和服务器的硬性要求,只靠CN字段已经远远不够。

生成业务证书时,我通常会为每个应用单独准备一个扩展文件,像这样:

cat > ~/pki/certs/example.ext <<EOF subjectAltName = DNS:example.internal, DNS:git.example.internal, IP:192.168.1.100 keyUsage = critical, digitalSignature, keyEncipherment extendedKeyUsage = serverAuth EOF

DNS:后面支持填多个域名,IP:后面填具体IP地址。两者可混搭,但注意写法不能错。如果只填了域名没填IP,那么用IP地址访问服务时,即使证书已安装,浏览器里照样报不安全,因为IP不在证书允许范围内。

5.2 执行签发操作

执行签发命令,格式如下:

openssl ca \ -config ~/pki/ca/openssl.cnf \ -in ~/pki/certs/example.csr \ -out ~/pki/certs/example.crt \ -extfile ~/pki/certs/example.ext \ -days 825 \ -batch

拆解一下每个参数:-config指定CA配置;-in输入CSR;-out输出正式证书;-extfile把SAN扩展文件带进去;-days设有效期;-batch跳过所有交互确认,免得脚本中途卡住。825天是我常用的业务证书有效期,比一年稍长,又不足三年,兼顾了轮换频率和运维压力。

签发成功后,立刻做两个验证动作。

第一,查看证书基本信息:

openssl x509 -in ~/pki/certs/example.crt -text -noout

检查Signature Algorithm、Subject、Issuer、Validity、SAN、Basic Constraints这些关键项。发现Issuer字段不是你的CA名字,说明命令串了配置,签错了源,排查方向要从配置文件开始。

第二,验证证书与私钥是否匹配:

openssl x509 -in ~/pki/certs/example.crt -pubkey -noout | sha256sum openssl pkey -pubout -in ~/pki/private/example.key | sha256sum

这种做法在实操中比OpenSSL自带的-modulus更通用,尤其针对ECC密钥对场景更可靠,输出不一致就直接重来,不要带病部署。

5.3 证书链拼装与部署格式

签发后的证书是“业务证书”,它要跟CA根证书一起提供给别人验证。这个过程叫“证书链拼接”,也有人叫“全链证书”。

大部分服务器的做法是在TLS握手时,把服务端证书和CA证书依次下发,客户端自下而上校验,直到信任的根为止。拼接命令非常简单:

cat ~/pki/certs/example.crt ~/pki/ca/ca.crt > ~/pki/certs/example-fullchain.crt

顺序千万别反。先自己的证书,再CA证书。如果你把顺序搞反,部分客户端会直接报错,另一些会尝试自动重排,但总会有一堆兼容性问题,没必要赌这个。

Nginx里的常见配置是:

server { listen 443 ssl; server_name example.internal; ssl_certificate /etc/pki/certs/example-fullchain.crt; ssl_certificate_key /etc/pki/private/example.key; }

Tomcat或Java系组件用的则是PKCS12格式,需要把证书和私钥放进一个带密码的容器:

openssl pkcs12 -export \ -in ~/pki/certs/example.crt \ -inkey ~/pki/private/example.key \ -certfile ~/pki/ca/ca.crt \ -out ~/pki/certs/example.p12 \ -passout pass:changeit

转换前注意-certfile参数:它会附带CA证书,Java应用才能构建完整信任链。这个也常被漏掉,导致报PKIX path building failed。

6. 常见问题与排查技巧实录

6.1 我踩过且大概率你也会踩的坑

把实操中碰到的坑集中列一遍。

  • 签名时提示数据库锁定或找不到文件。通常是因为没有先把index.txt和serial准备好。前面提起过这两个文件,没有它们,openssl ca会直接罢工。临时补的话千万别用echo > index.txt,文件里已经有历史记录的会被冲掉。正确做法是用有内容校验的文本编辑方式增量添加记录。

  • 私钥带密码导致Nginx启动失败。Nginx在启动时需要读取私钥,如果私钥有密码保护,要么报Permission denied,要么直接拒绝启动,还不给明确原因。全局解决方案就是:OpenSSL生成私钥时根本别设-aes256这类加密参数。

  • KeyUsage和ExtendedKeyUsage打架。证书既做服务端认证又做客户端认证时,extendedKeyUsage需要同时声明:serverAuth和clientAuth。如果你写的扩展文件里只有一个serverAuth,那么用它做客户端双向认证时,服务端一验就挂。更隐蔽的是:某些场景下keyUsage里没有digitalSignature,但在ECDSA证书中这是必需的,不然TLS握手时签名验证直接失败。

  • SAN缺失导致的浏览器不信任。这是所有坑里最坑的一个。Chrome从某个版本起不再关心CN是否匹配,只看SAN。CSR里不带SAN没关系,关键是签名时-extfile必须指定。漏了这一步,证书装上后浏览器依然警告"NET::ERR_CERT_COMMON_NAME_INVALID"。

  • 系统时间不一致导致证书立即失效。证书的notBefore字段取自生成时刻的系统时间,如果CA服务器系统时间慢了10分钟,签出来的证书生效时间可能晚于客户端当前时间,客户端会觉得证书“还没生效”,直接拒绝。排查方式是在生成机器上先同步时间。

6.2 一张表搞定大部分疑难杂症

现象大概率原因解决动作
浏览器报NET::ERR_CERT_COMMON_NAME_INVALIDSAN缺失或不含当前域名签名时携带-extfile,SAN里加对应DNS/IP
部署后服务启动报权限错误私钥或证书目录权限太宽私钥600,证书目录700,属主改成运行用户
证书链验证报"unable to get issuer"业务证书没有附上CA证书拼接时业务证书在前、CA证书在后
Java报"PKIX path building failed"PKCS12缺失CA证书转换时加-certfile ca.crt
客户端报证书未生效签发系统时间偏移同步NTP时间后重新签发
签名时报"CA certificate is invalid"根证书Basic Constraints缺CA:TRUE重建根证书,确认v3且含critical CA:TRUE
OpenSSL 3.x报弱签名算法MD5/SHA1被禁显式指定-sha256,并检查配置文件默认算法
生成CSR后忘记保存私钥使用了临时目录或系统环境变量指定的路径固定工作目录,每次先用pwd确认位置
Nginx报SSL_ERROR_SYSCALL客户端不支持服务端所选加密套件查看OpenSSL版本和服务端支持的CipherSuite列表
证书能用但浏览器显示“不是安全连接”客户端不信任内部CA需要将CA根证书导入系统/浏览器信任库

6.3 两个实用的小工具操作

如果涉及DNS和HTTP的验证动作,日常排障我会用两个顺手的小命令。

# 查看远程服务的证书链(用openssl自带s_client即可) openssl s_client -connect example.internal:443 -servername example.internal -showcerts </dev/null # 验证本地证书链是否完整 openssl verify -CAfile ~/pki/ca/ca.crt ~/pki/certs/example-fullchain.crt

verify输出OK才叫干净。如果报unable to get local issuer certificate,那明显是CA没被信任或链没拼全。

我个人在实际操作中的体会是:证书报错大部分不是加密算法有多难,而是管理不善和细节遗漏。把事情分解成“生成私钥→提交CSR→CA签名→部署证书”四步,每一步校验一次,基本能把出错的概率压到最低。这篇列出的每一个坑,都是我用一次次加班换来的教训,你照着走一遍流程,再结合自己的场景做调整,能少走不少弯路。以后不管是自签测试证书,还是搭内部CA统一管理组件证书,主动权都能握在自己手里。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询