不管你是不是刚入行,只要管过几台Linux服务器,一定经历过这种场景:有同事临时需要上一台生产机器看看日志,或者外部合作方要做一次紧急排障。传统做法是让他把公钥发过来,你手动塞到目标机器的authorized_keys里,等活儿干完再手动删掉。机器少还行,机器一多就完全失控——发出去的公钥谁还记得删?哪个IP在访问?证书什么时候该回收?全是坑。
后来我把这套流程改成“用签名的SSH公钥做临时免密登录”,简单说就是搭建一个内部CA,用这个CA给用户的公钥“盖章”,盖上有效期、允许登录的身份、甚至只允许执行哪条命令。服务端只信任这一个CA,用户的证书到期自动失效,全程不需要登录任何服务器删除任何东西。这篇文章就把这套方案的完整玩法拆开讲清楚,从原理、搭建、签发到排障,照着做就能落地。
1. 先说清楚:这题考的可不是把公钥塞进authorized_keys
1.1 传统免密授权的痛点
先说一个我自己的经历。有一年某业务系统连续告警,负责运维的同学需要临时到十几台机器上去排查。当时我用传统方式处理:挨台机器登录,把这位同学的公钥追加到每台机器的/home/某用户/.ssh/authorized_keys,同时建一个临时账号给他。看起来没什么问题,但后续麻烦接踵而至。
最大的麻烦是“回收不可控”。排查结束后,我需要再挨台机器登录,把公钥从authorized_keys里删掉,把临时账号禁用掉。问题在于:这件事靠人记,很容易漏。有一次某台机器漏删了,结果过了三个月,那位已经离职的同事的公钥还挂在生产机器的授权文件里。虽然公司网络有管控,但这件事把我吓得不轻——凭一条公钥就能免密登录生产环境,哪天出事儿都不知道根源在哪。
其次的问题是“权限不可控”。authorized_keys机制本身只管“谁能登录”,不管“登录后能干什么”。只要公钥被接受,用户拿到的是一个完整登录会话,除非你在.bashrc或者命令通道层面做拦截。但那些做法要么侵入系统太深,要么容易破。签发证书之后,这些都可以在证书里直接声明。
最后是“批量管理失控”。几十台机器,每台都要维护一份授权列表,机器一多必然出错。谁加过这台?谁没删干净?检测成本极高。
1.2 SSH证书签名的思路:把信任交给一个CA
OpenSSH从5.4版本开始支持基于证书的认证,这在Linux圈子里其实不算新技术了。很多人不熟悉,是因为日常运维中真的用到证书认证的场景不多,大家更喜欢复制粘贴公钥。
核心思路是:不再信任“某把公钥”,而是信任“某把CA公钥”。具体流程是:
- 搭建一个CA密钥对(私钥 + 公钥),私钥留在签名管理机上,公钥部署到所有服务器上。
- 用户用普通的
ssh-keygen生成自己的一对密钥。 - 管理员用CA私钥对用户公钥进行签名,生成一张证书,证书里写明有效期和授权身份。
- 用户连接服务器时,客户端会自动把“公钥+证书”一起发给服务端。服务端校验证书的签名来自可信CA、有效期合法、授权身份匹配本地配置,就放行。
这个过程可以类比为“门禁卡”。过去你是给每个人发一把钥匙(公钥),复制一把就能进门,丢了就要换锁。现在你给每个人发一张带有效期的门禁卡(证书),卡片由发卡中心(CA)统一签发,过期自动失效。门禁系统只认发卡机构的印鉴,不在自己手里维护一堆钥匙副本。
对比下来,证书模式的最大价值就是:
- 批量部署一次性完成:所有服务器只需要配置一次信任CA,后续授权不用再登录服务器改文件。
- 授权回收变成“等它过期”:证书到期自动失效,省去逐台清理。
- 权限边界丰富:可以声明允许登录的身份、强制执行的命令、来源IP范围,证书本身就是一道微防火墙。
1.3 “临时”和“免密”是怎么同时做到的
免密大家都懂的:公钥或证书认证通过后,SSH不会要求输入密码,直接放行。而“临时”的本质就是证书里的有效期字段。
ssh-keygen签名证书时有一个-V参数,用来指定证书的有效期。你可以写相对时长,比如+1d(1天)、+8h(8小时)、+30m(30分钟),也可以写绝对时间区间,比如-V 20240520120000:20240521120000。证书一旦超过这个时间窗口,客户端和服务端都会判定为失效。
这个机制相比“大到时间就手动删公钥”的最大好处是防呆:不需要人记着回收,时间一到自然失效。就算签发人离职、忘了、被车撞了(开玩笑),权限也不会多存在一分钟。
2. 环境准备与CA体系搭建
2.1 OpenSSH版本与系统要求
想用证书认证,首先要确认两端版本支持。原则很简单:尽量新。OpenSSH 5.4开始支持用户证书,5.6开始支持相对较完整的功能,但生产环境中我建议至少OpenSSH 7.0以上,能用8.x、9.x更好。很多老版本虽然也能跑证书,但在某些参数格式和-O扩展选项上支持不完整,容易踩坑。
系统方面,主流的Linux发行版(CentOS/RHEL 7/8/9、Ubuntu 16.04以上、Debian 9以上)自带的OpenSSH都满足条件。macOS自带的也行,Windows 10/11自带的OpenSSH也能做客户端,但服务端配置路径不一样,这里主要以Linux服务端来展开。
可以使用一个表格把环境要求列清楚:
| 组件 | 要求 | 说明 |
|---|---|---|
| OpenSSH客户端 | 5.4+,建议7.0+ | 用户侧连接时使用证书文件 |
| OpenSSH服务端 | 5.6+,建议7.6+ | 需要支持TrustedUserCAKeys和AuthorizedPrincipalsFile |
| 签名机 | 任意Linux/macOS | 存放CA私钥,建议仅内网可达 |
| 操作系统 | Linux为主 | 服务端路径以Linux发行版为准 |
2.2 生成CA密钥对
搭建CA的第一步是生成一个独立的密钥对。这里我推荐使用RSA 4096位,原因有两点:一是兼容性最好,老客户端和服务端都能识别;二是做CA的密钥一年也就签发几百次,性能完全不是瓶颈。虽然ed25519密钥更短更快更现代,但某些老版本OpenSSH在解析ed25519签名的证书时会有问题。
在签名机上执行:
mkdir -p /opt/ssh-ca cd /opt/ssh-ca ssh-keygen -t rsa -b 4096 -f user_ca -C "internal-ssh-ca" -N ""这条命令会生成user_ca(CA私钥)和user_ca.pub(CA公钥)。私钥是签发证书用的,必须严格保管,最好放在只有少数人有权限的路径里,比如管理机上的单独目录,权限设为600。公钥要复制到所有需要被访问的服务器上,路径一般放/etc/ssh/下,比如/etc/ssh/user_ca.pub。
如果你后续还想校验服务端主机身份,可以再生成一对主机CA密钥。主机证书是另一个维度,这里先聚焦用户证书,后边会提到它的扩展用法。
2.3 服务端配置信任CA
服务端的配置路径是/etc/ssh/sshd_config。需要开启两个关键配置:
# 信任这个CA签发的用户证书 TrustedUserCAKeys /etc/ssh/user_ca.pub # 指定每个用户允许哪些principals身份 AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u第一行的意思很直接:凡是这个CA签出来的证书,服务端都认为可信。第二行的%u会被替换成登录用户名。也就是说,配置服务端之后,你还要为每个允许登录的用户建一个文件,例如用户tempadmin,对应的文件是/etc/ssh/auth_principals/tempadmin,文件内容就是允许的principal列表。
这个文件本质上是“身份白名单”,和authorized_keys有几分类似,但它匹配的不是具体公钥,而是证书里声明的principal名称。举个例子:证书签发时指定-n tempadmin,而auth_principals/tempadmin文件里正好包含tempadmin这一行,服务端才会放行。
配置完成后重启服务:
systemctl restart sshd # 或者 service sshd restart注意:修改
sshd_config不会影响已建立的连接,重启服务时正在进行的下载、传输等会话不会断,但推荐在低峰期操作,并且先执行sshd -t检查语法。
sshd -t2.4 服务端配置要点速查表
把服务端涉及到的几个文件和路径整理成速查表,避免以后忘:
| 路径/参数 | 作用 |
|---|---|
/etc/ssh/user_ca.pub | 部署CA公钥,供sshd验证证书签名 |
TrustedUserCAKeys | 指定信任的CA公钥文件路径 |
/etc/ssh/auth_principals/%u | 每个登录用户对应的principal白名单 |
AuthorizedPrincipalsFile | 指定principal白名单文件模板 |
PubkeyAuthentication | 必须保持为yes(默认就是yes) |
AuthorizedKeysFile | 证书模式下可以不用,建议注释或清空 |
这里有一个关键点需要特别注意:证书模式完全不需要往authorized_keys里写任何内容。很多人第一次接触证书时,习惯性地把用户公钥或证书公钥又追加到authorized_keys里,结果发现有没有都无所谓,然后产生混乱。事实上,只要TrustedUserCAKeys配置正确、principal匹配成功,服务端就会接受证书认证,跟authorized_keys一点关系都没有。
3. 签发一张带生命周期的临时用户证书
3.1 生成用户自己的密钥对
证书是给“用户的公钥”盖章,所以用户侧先要有自己的密钥对。这一步和平时生成密钥没有任何区别:
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -N ""生成后,~/.ssh/id_ed25519是私钥,~/.ssh/id_ed25519.pub是公钥。用户把公钥交给管理员,管理员在CA签名机上完成签发。这里必须强调:用户的私钥绝不能离开用户自己。管理员只需要公钥,拿到私钥反而是一种安全风险。
3.2 用CA签名,签发正式证书
拿到用户的公钥后,在签名机上执行签发命令:
ssh-keygen -s user_ca \ -I "temp-admin-20240520" \ -n tempadmin \ -V +12h \ -z 2024052001 \ -O source-address=192.168.1.0/24 \ ~/.ssh/id_ed25519.pub这条命令会在公钥同目录下生成对应的证书文件:id_ed25519-cert.pub。注意证书文件名的规律:它必须是“私钥文件名 +-cert.pub”,而且必须放在和私钥相同的目录下。ssh-keygen默认就是这么做的:因为待签名的文件是~/ .ssh/id_ed25519.pub,生成物就是~/ .ssh/id_ed25519-cert.pub,路径不需要额外指定。
整个签发动作的核心就是把用户公钥“变成一张带说明的证书”。签发完成后,管理员把id_ed25519-cert.pub和id_ed25519(私钥)一起安全地送回给用户,或者通过内部加密通道传输。
3.3 关键参数逐个拆解
很多人看到这串参数容易懵,其实每个参数都不复杂。逐个拆开讲清楚:
| 参数 | 含义 | 我的建议 |
|---|---|---|
-s user_ca | 指定CA私钥文件 | 签名机上的路径,签完即收回 |
-I 身份标识 | 指定证书ID,用于审计 | 建议写成“用途-日期-序号”,方便追踪 |
-n principals | 证书允许登录的服务端身份 | 必须和服务端auth_principals文件内容匹配 |
-V 有效期 | 证书有效时间窗口 | 临时授权建议用相对短时间 |
-z 序列号 | 证书序列号 | 用于追踪和审计,可用脚本自动递增 |
-O 扩展选项 | 附加限制条件 | 常用source-address、force-command、clear |
逐项说明。
-V参数是“临时”的重心,它的语法比较灵活。+12h表示从现在开始12小时内有效,+30m表示30分钟,+1d表示1天。也可以写成绝对时间,比如-V 20240520120000:20240521120000,表示从2024年5月20日12点到2024年5月21日12点。这个时间格式是YYYYMMDDHHMMSS,没有分隔符,写起来容易出错,所以我个人更推荐相对时间写法。运维场景中,临时授权的典型时长是30分钟到8小时,最长一天。超过一天的授权就要走电子审批流程,签发前把用途写清楚。
-n参数对应证书中的principals字段。它本质上就是“你这张证书可以冒充哪个用户登录”。比如-n tempadmin,表示允许以tempadmin用户登录到服务器。服务端会校验证书里的principals字段和auth_principals/tempadmin文件里的内容是否匹配。如果证书里写的是-n ops,但服务端文件只放行了tempadmin,认证就被拒绝。
-O force-command="命令"是一个非常好用的限制选项。一旦指定,用户通过这张证书登录后,只能执行这条命令,无法进入交互式shell,也无法执行其他命令。这在做临时数据抽取、日志查看等受限场景时特别好用。
-O source-address=IP段可以限制允许的来源IP。比如证书只允许从内网网段接入,就能防止证书文件外泄后被外网滥用。
clear选项可以清空环境变量,减少注入风险。具体写法是-O clear。
最好还配合-z序列号,做到一张证书一个编号。后续在服务端日志里看到I temp-admin-20240520,就知道是谁、什么用途、哪一批签发的。
3.4 客户端使用与验证
用户拿到私钥和证书文件后,直接连接即可:
ssh -i ~/.ssh/id_ed25519 tempadmin@目标服务器IP如果私钥路径是默认的~/.ssh/id_ed25519,甚至可以不加-i,SSH会自动找到。连接的瞬间,客户端会在私钥同目录下查找id_ed25519-cert.pub,如果存在,就自动使用证书认证。不需要在命令行中显式指定证书文件。
验证证书是否生效,可以看两个信息:
一是SSH连接时是否提示输入密码:如果直接进入登录状态,说明证书认证成功。
二是查看证书内容:
ssh-keygen -L -f ~/.ssh/id_ed25519-cert.pub输出会显示证书的签发CA指纹、有效期、principals、强制命令等信息。这个命令在任何机器上都能跑,适合快速检查一张证书的“保质期”和权限边界。
提醒:用户拿到证书文件后,如果私钥在传输过程中泄露,别人拿到私钥+证书就能在证书有效期内冒充用户。所以传输通道必须是安全的,比如通过内部加密IM、加密邮件或u盘面交。
3.5 撤销与到期行为
证书一旦到期,客户端虽然还是会尝试出示证书,但服务端会判定证书过期,认证直接失败。不需要管理员登录服务器删除任何东西。这一点在“临时授权”场景里价值非常大。
但这里必须说清楚一个潜在坑:如果你把用户的公钥同时也放在了authorized_keys里,那么证书过期后用户依然可以免密登录,因为走的是authorized_keys认证路径。所以用证书模式的同时,建议把服务端上遗留的历史公钥全部清空,或者用配置管理工具周期性检查,确保authorized_keys里没有任何残留。这是很多团队切换到证书模式后仍然“半夜接到安全告警”的原因——旧公钥没清理干净,证书成了摆设。
如果真的需要紧急收回某张证书(比如密钥泄露),OpenSSH目前没有一个标准的在线证书吊销机制。能做到的:
- 如果证书还没扩散,可以直接删除目标服务器上对应的
auth_principals用户文件,但这会影响所有使用该用户身份的证书。 - 证书序列号作用有限,不支持在线吊销列表。
- 实践中最稳妥的做法就是:证书有效期本身设短一些,默认2小时,就算泄露,影响窗口也有限。
4. 批量运维场景下的落地经验
4.1 批量分发证书的常规套路
如果你管理的是几十上百台服务器,用户可能是多个不同角色,你需要一套批量分发和配置的手段。
第一步:在所有服务器上统一配置sshd_config的两个关键项。这个一般通过配置管理工具(Ansible、Puppet、Chef)下发,或者先打好一个基线镜像。如果服务器数量不多,写个for循环也够用。
第二步:为每个允许登录的用户(或角色)创建auth_principals文件。比如有一个ops通用运维账号,那么auth_principals/ops内容写ops即可,所有以ops身份登录的证书,只要principals声明为ops,都能通过。
第三步:用户申请证书时,管理员在签名机签发后,把私钥和证书通过内部安全通道发送。下面是一个简单的批量分发示例(假设签名机已能通过现有SSH通道连接目标服务器):
for h in $(cat hosts.txt); do scp -o StrictHostKeyChecking=no ~/.ssh/id_ed25519 \ ~/.ssh/id_ed25519-cert.pub tempadmin@$h:~/.ssh/ ssh -o StrictHostKeyChecking=no tempadmin@$h \ "chmod 600 ~/.ssh/id_ed25519 && chmod 644 ~/.ssh/id_ed25519-cert.pub" done这个脚本适合一次性给同一个人分发到多台机器,实际使用中注意把hosts.txt换成真正的机器清单即可。
4.2 强制命令:证书只能干该干的事
临时授权最怕的是“权限给了之后不知道该给多少”。很多运维同学给权限时纠结:不给不行,给了又怕对方乱来。证书的-O force-command可以解决这个纠结。
举个例子。你需要请一个外部同学到服务器上拉取一份日志文件,不想让他执行任何其他命令。可以这样签发:
ssh-keygen -s user_ca \ -I "log-extract-external-20240520" \ -n tempadmin \ -V +3h \ -O force-command="/usr/local/bin/extract_log.sh" \ /home/external/.ssh/id_ed25519.pub这样用户连接后,SSH会强制执行/usr/local/bin/extract_log.sh,命令执行完就退出,无法运行其他命令。强制命令可以结合clear一起使用,把环境变量清空,进一步缩小可操作面。
配合强制命令时,还需要注意一点:如果用户使用交互式SSH客户端,force-command会覆盖客户端请求的任何shell或命令。所以它并不只是一道“命令包装”,而是从服务端层面锁死了动作边界。这个特性在堡垒机、自动化管理、外部协作场景非常有用。
还有一个常见组合:source-address+force-command,一个限定从哪里来,一个限定干什么,证书的权限边界就很立体了。
4.3 来源IP限制与审计追踪
临时授权不光是给个“能登录”的许可,还要考虑“从哪里登录”。如果一张证书允许从任何IP登录,那就算只签发半小时,泄露后的风险窗口也会被滥用。所以建议在证书里设置source-address:
ssh-keygen -s user_ca \ -I "temp-dba-20240520-001" \ -n dbadmin \ -V +1h \ -O source-address=192.168.20.0/24,10.10.10.0/24 \ /home/user/.ssh/id_rsa.pub多个网段用逗号分隔。source-address的作用是:服务端在认证时会校验客户端来源IP,不在允许列表内直接拒绝。这样就算私钥和证书一起被偷了,离开内网后也完全不可用。
审计方面,证书的-I标识是核心。服务端在日志里能看到类似:
Accepted certificate for user dbadmin from 192.168.20.3 port 52314 ssh2: CERT证书的fingerprint与CA公钥、用户公钥都不同,但日志里会附带证书的key ID(也就是-I指定的值)。所以建议-I的命名规范定为:角色-日期-序号。比如temp-dba-20240520-001,一看就知道是哪批签发的,是谁的临时权限。
再配合z序列号,理论上可以做到每张证书独立追踪。如果你的跳板机或审计系统可以采集SSH日志,那么“谁、何时、从哪里、获得了哪个用户的临时权限、登录到哪台机器”这些信息就非常清晰了。
4.4 主机证书:一揽子信任主机指纹
和用户证书对应的还有主机证书。它可以解决未知主机指纹的问题:当你的客户端需要连接非常多的服务器时,known_hosts文件会越来越乱,每次新环境还要手动确认指纹。用CA给服务器主机公钥签名,然后在客户端的known_hosts中添加一行标记:
@cert-authority *.example.internal ssh-rsa AAAA...CA公钥内容...客户端连接*.example.internal网段时,如果服务器端出示的主机证书是由这个CA签发的,就直接信任,不再提示指纹确认。这在批量自动化、脚本分发时特别有用,可以避免StrictHostKeyChecking=no这种危险写法。
主机证书的签发和用户证书类似,需要-h参数标记为主机证书:
ssh-keygen -s host_ca -h -I "server-001-host-cert" -n 192.168.1.101 \ -V +365d /etc/ssh/ssh_host_rsa_key.pub服务端生成的是/etc/ssh/ssh_host_rsa_key.pub,签发后在服务端/etc/ssh/sshd_config里增加:
HostCertificate /etc/ssh/ssh_host_rsa_key-cert.pub然后把主机CA公钥加到所有客户端的known_hosts中。这样主机指纹和用户访问经过同一个CA体系,整体信任链条就打通了。
5. 我踩过的坑与排查实录
5.1 常见问题速查表
写到这里,把自己实际遇到过的各种问题整理成速查表,方便日后排查:
| 问题现象 | 原因 | 处理办法 |
|---|---|---|
| 连接时一直要密码,证书似乎没生效 | 证书文件名不是私钥名-cert.pub,或没和私钥放同目录 | 检查文件名,确保形如id_ed25519-cert.pub且放在~/.ssh/下 |
日志提示no matching principal found | 证书里的principals和服务端auth_principals文件内容不匹配 | 检查-n参数与服务端auth_principals/用户名的文件内容 |
| 证书提示已过期 | -V参数写错或时间太短 | 查看证书内容确认有效期 |
认证日志提示certificate invalid | 签名CA公钥和TrustedUserCAKeys配置不一致 | 检查服务器上的user_ca.pub和签名机CA公钥是否一致 |
| 私钥权限报错 | 私钥文件权限太开放 | chmod 600 ~/.ssh/id_ed25519 |
| 登录后无法进入shell | 强制命令生效,属于正常现象 | 重新签发不包含force-command的证书,或在命令中指定执行路径 |
日志里显示Authentication refused | 可能PubkeyAuthentication被关闭,或服务端未重启 | 确认sshd_config并sshd -t后重启 |
5.2 现场排查的具体思路
遇到证书认证失败不要慌,按照下面这套顺序来排查基本都能定位:
第一,确认客户端有没有找到证书。执行:
ssh -vvv -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes tempadmin@目标IP观察输出中是否出现Offering public key: /home/user/.ssh/id_ed25519 ED25519 SHA256:...和Certificate key: /home/user/.ssh/id_ed25519-cert.pub。如果没有出现证书相关行,说明客户端压根没有识别到证书文件。
第二,确认服务端是否认可CA。登录服务器,执行sshd -t检查配置,然后查看服务端日志:
sudo tail -f /var/log/auth.log # 其他发行版可能是 # sudo tail -f /var/log/secure日志中如果出现Accepted certificate for user ...,说明证书认证流程已经走通。如果出现Failed publickey for ... from ...或user ... not allowed because a listed principal is not in certificate,问题就在principal匹配上。
第三,用ssh-keygen -L -f查看证书内容,逐项对有效期、principals、source-address。
这里有个特别经典的坑:有人签发证书时-n ops,但服务端auth_principals/ops文件里写的是op,少一个s,结果来回查了一天。这种“看着像又对不上”的问题,排查时必须把证书里的principals原样打印出来,再和服务端文件逐字符对比。
5.3 一些安全上的体会和底线
签发证书这事,用顺手之后会特别香,但底层安全底线不能松。
第一个体会:CA私钥必须和服务器绝对隔离。CA私钥如果放在某台生产服务器上,服务器被攻破,等于所有服务器都被攻破。我的做法是把CA私钥放在专门的签名机上,平时不开外部访问,只有管理员登录。
第二个体会:临时授权不要签-forever。就算用户要求长期权限,也不要签永久证书,否则就失去了证书巡逻最核心的价值。常见的做法是标准临时2小时,特殊场景8小时,一天以上必须走审批。
第三个体会:证书有效期越短越安全。许多人觉得“签一天也没事儿”,但实际排查过几次后就会明白,最有价值的告警窗口往往就在权限过期前那几分钟。如果权限是长期存在的,你连攻击面都摸不清。
第四个体会:可以写个小脚本把签发流程固化。比如我自己的签名机上有一个脚本,要求必填三个参数:用户名、有效时长、用途。脚本内部强制校验时长、自动生成序列号、自动校验公钥格式。就算哪天心情不好手抖,也不会签出一张没有期限的证书。
6. 写在最后的一点经验
这套方案我用了很长时间,管了几十台服务器的临时授权,整体感受非常稳。只要服务端配置到位、CA私钥保管好,后续每一次临时授权都只是一个“签发动作”,和服务器数量完全无关。用户侧拿到证书就能登录,到期自动失效,真正实现了“发权限不操心,收权限不用人”。
如果再让我补充一个小技巧:如果你们有跳板机或者统一登录入口,可以把证书认证和跳板机结合起来。用户先用证书认证登录跳板机,再从跳板机跳转到其他机器。这样既能把审计集中在一点,又能保证后端服务器不直接暴露给用户,安全性会更好。证书模式的签名、验证逻辑不需要改,只需要把跳板机当作CA信任链上的一个环节即可。
临时授权这件事,说到底就是一个“谁能进、能进多久、进去能干什么”的问题。传统的authorized_keys回答了“谁能进”,但把后面两个问题完全交给了人肉管理。证书认证把三者全部写进证书里,让权限管理变得可声明、可审计、可预期。如果你也在为“公钥撒得到处都是、过期没人收”这件事头疼,强烈建议花半天时间把这套方案搭起来。不会后悔。