- 引言 — 一个伪装成 bugfix 版本的转折点
- 密钥交换这一侧的故事 — 从 sntrup761 到 mlkem768 默认值
- 10.1 的警告 — WarnWeakCrypto 造就的运维现实
- 10.4 — 签名这条战线开了
- 草案 -00 的现状 — 数字对不上
- 咬住运维者的变更 — 从 10.0 到 10.4
- 不该做的事
- 实务检查清单
- 结语
- 参考资料
引言 — 一个伪装成 bugfix 版本的转折点
OpenSSH 10.4 于 2026 年 7 月 6 日发布。发布说明的自我介绍很谦逊 — "a number of security fixes as well as general bugfixes and a couple of new features"。大多数运维者读到这里,就把包扔进更新队列然后翻篇了。
可是那 "a couple of new features" 之一,预告了 SSH 协议未来五年的走向。把 ML-DSA-44 与 Ed25519 捆在一起的后量子复合签名以实验性方式进来了。在密钥交换的后量子迁移基本结束的时点上,签名的轮次现在开始了。
昨天发的PQ 证书为什么还没到一篇,讲的是 TLS/WebPKI 这一侧的故事 — 那边因为 ML-DSA 签名太大,正在造一条叫 Merkle Tree Certificates 的迂回路。SSH 这一侧情况不同,所以走的是另一条路。本文以原文比对 OpenSSH 10.0 到 10.4 的发布说明,沿着那条路走下去,并顺带梳理途中咬住运维者的不兼容变更。
先把时间线钉下来。全部以官方发布说明为准。
9.0 2022-04 sntrup761x25519-sha512 成为默认 KEX (后量子默认值的开端)
9.9 2024-09-19 加入 mlkem768x25519-sha256
10.0 2025-04-09 mlkem768x25519-sha256 成为默认 KEX。移除 DSA。从服务端默认值移除 modp DH
10.1 2025-10-06 引入非 PQ 密钥交换的警告(WarnWeakCrypto)。移除 XMSS。代理套接字逃离 /tmp
10.2 2025-10-10 紧急 bugfix (ControlPersist 令会话无法使用的问题,bz3872)
10.3 2026-04-02 证书空 principals 的含义反转。移除与不支持 rekey 的实现的兼容代码
10.4 2026-07-06 实验性复合签名 ssh-mldsa44-ed25519@openssh.com。seccomp 失败变为致命
密钥交换这一侧的故事 — 从 sntrup761 到 mlkem768 默认值
据 OpenSSH 的后量子页面,后量子密钥交换成为默认值,要追溯到 2022 年 4 月的 9.0。当时 NIST 标准化尚未结束,于是项目选了 NTRU Prime 系的 sntrup761x25519-sha512。9.9 里加入了基于 NIST 标准(FIPS 203)的 mlkem768x25519-sha256,并在 10.0 成为默认值。
10.0 发布说明为默认值替换列出的依据有四条 — 被认为对量子计算机攻击是安全的,被保证不弱于既有的 curve25519-sha256,NIST 已将其标准化,以及比先前的默认值(sntrup761)相当快("considerably faster than the previous default" — 这是项目自身的说法,发布说明里并未另行给出基准数值)。
这里的「不弱于」正是混合结构的核心。mlkem768x25519-sha256 两者都做 ML-KEM-768 与经典 x25519 ECDH,再把两个共享密钥拼接后哈希。IETF 文档(draft-ietf-sshm-mlkem-hybrid-kex,本文写作时点 -10 修订版已进入 RFC 编辑队列)的定义是这样的。
K = HASH(K_PQ || K_CL)
K_PQ: 由 ML-KEM-768 封装得到的共享密钥 (FIPS 203)
K_CL: 由 x25519 ECDH 得到的共享密钥 (RFC 8731)
即使 ML-KEM 在未来的密码分析下崩塌,x25519 仍在;若量子计算机击破 x25519,ML-KEM 顶住。为什么要买这么多保险、现在就急着上?因为 "store now, decrypt later" — 把此刻来往的流量存起来、日后用量子计算机破解的攻击。密钥交换一旦被攻破,整个会话都会被解密,所以逻辑是:即便在密码学意义上有意义的量子计算机(CRQC)今天还不存在,单单密钥交换也必须提前更换。OpenSSH 的 PQ 页面把 CRQC 到来的前景写成 5 到 20 年的区间,并写道多数观察者的预期是 2030 年代中期。
协议层面的背景 — KEX 嵌在哪里、又签什么 — 请参考 SSH 协议深潜一篇。
10.1 的警告 — WarnWeakCrypto 造就的运维现实
更改默认值与撬动整个生态,是两个不同的问题。10.1(2025-10-06)拿出了更直接的手段。当客户端以非后量子的密钥交换谈妥后,会打出这样一条警告。
** WARNING: connection is not using a post-quantum key exchange algorithm.
** This session may be vulnerable to "store now, decrypt later" attacks.
** The server may need to be upgraded. See https://openssh.com/pq.html
该警告默认开启,由新的 ssh_config 选项 WarnWeakCrypto 控制。从运维视角看,这条警告的含义很简单 — 你把客户端升到 10.1 或更高的那一刻,组织内所有陈旧的 SSH 服务端就全都摊在用户眼前了。老旧的网络设备、被厂商弃置的一体机、把 KexAlgorithms 钉死在旧值上的黄金镜像,都会一齐报警。
正确的解法是把服务端升上去。sntrup761 从 9.0 起支持,mlkem768 从 9.9 起支持,所以如果服务端本就在那个版本段,先检查它的配置是不是通过 KexAlgorithms 把 PQ 算法关掉了即可。对于实在升不了的服务端,PQ 页面推荐的方式是按主机做选择性的静默。
Match host unsafe.example.com
WarnWeakCrypto no-pq-kex
你会有把 WarnWeakCrypto no 全局钉上去的冲动,但发布说明预告了这个选项今后也会管辖其他弱密码警告。全局关掉,就等于把未来的警告也提前关掉了。想到把用户训练成无视警告才是最糟的结果,就该把静默的范围收窄。
10.4 — 签名这条战线开了
与密钥交换不同,签名不是 "store now, decrypt later" 的目标。伪造签名是必须在连接活着的那一瞬间完成的事,所以无法在十年后回溯着去攻破今天存下流量的认证。OpenSSH 的 PQ 页面也明示了这一点,并写道签名这一侧唯一的紧迫性,只是在 CRQC 成真之前把经典签名密钥退役掉。所以签名迁移是一场可以比密钥交换晚几年出发的竞赛,实际也正是这样推进的。
前兆在 10.1 里。在移除实验性的基于哈希的签名 XMSS 时,发布说明这样写道 — "We expect to implement a new post-quantum signature scheme in the near future."。那个 near future 就是 10.4。
10.4 实验性支持了 draft-miller-sshm-mldsa44-ed25519-composite-sigs 里所规定的复合签名 ssh-mldsa44-ed25519@openssh.com。草案作者是 OpenSSH 的 Damien Miller 本人。其结构与密钥交换的混合是同一套哲学 — 用后量子的 ML-DSA-44 与经典的 Ed25519 分别对同一条消息签名,验证要两者都通过才有效。正如草案的安全考量所言,要伪造就得把两种算法都击破。
M' = Prefix || Label || len(ctx) || ctx || SHA512(M)
Prefix = "CompositeAlgorithmSignatures2025"
Label = "COMPSIG-MLDSA44-Ed25519-SHA512"
mldsa_sig = ML-DSA-44.Sign(mldsa_sk, M', ctx=Label)
ed25519_sig = Ed25519.Sign(ed25519_sk, M')
composite_signature = mldsa_sig || ed25519_sig
它不是默认值,所以要用就得在两端手动把它加进 HostKeyAlgorithms、PubkeyAcceptedAlgorithms 等,密钥生成这样做。
ssh-keygen -t mldsa44-ed25519
抓一下大小的感觉 — Ed25519 公钥 32 字节,签名 64 字节。按 FIPS 204,ML-DSA-44 公钥 1,312 字节,签名 2,420 字节。复合公钥是两个公钥拼起来的 1,344 字节,复合签名则是 2,484 字节。昨天讲的 TLS 这一侧,一次握手要搬运好几个签名和好几个公钥,所以这个大小扛不住,正在挖一条叫 Merkle Tree Certificates 的迂回路;而 SSH 握手只搬运一个主机密钥和一个签名(RFC 4253 的 KEX 结构)。每个连接多出 4KB 出头的额外开销,所以 SSH 无需迂回路,可以走直接把算法换掉的正面打法。
草案 -00 的现状 — 数字对不上
这里给你看一个应当认真对待「实验性」这个标签的理由。草案 -00(2026 年 6 月 2 日版)正文把复合签名的大小写成这样。
byte[3309] mldsa_sig
byte[64] ed25519_sig
This 3373-byte combination is henceforth referred to as
"composite_signature".
可是 FIPS 204 定下的 ML-DSA-44 签名是 2,420 字节。3,309 字节是上一档 ML-DSA-65 的签名大小。实际的 OpenSSH 10.4 源码(标签 V_10_4_P1 的 crypto_api.h)以 MLDSA44_SIGBYTES 2420 实现,与 FIPS 204 一致,所以看起来是草案正文那边写错了。同一份文档的签名上下文一节里,还原封不动地留着 "TODO TODO TODO"。
这不是要嘲笑草案 — 一份 -00 的个人提交(individual submission)文档本来就是这个状态,而正因如此,它连工作组都还没采纳这一事实本身就是信息。这意味着线格式与密钥格式还会随修订版而变的阶段,文档的状态在讲的,正是 OpenSSH 用 "experimental" 一词钉死的同一件事。
咬住运维者的变更 — 从 10.0 到 10.4
被后量子的故事遮住而不太看得见,但这趟发布列车也是一连串的废弃(deprecation)。按失败暴露的方式来分组。
在连接时点失败的。 10.0 彻底移除了 DSA 签名算法(这是 2015 年以默认禁用起步的废弃流程的收尾)。同一版本里服务端默认值的有限域 DH — diffie-hellman-group* 与 diffie-hellman-group-exchange-* — 被拿掉了。既做不了 ECDH 也做不了 PQ 的旧客户端,无法连上 10.0 或更高版本服务端的默认配置(客户端一侧的默认值保留了,所以新客户端连旧服务端的方向仍然可行)。另外,当 moduli 文件存在但没有请求范围内的值时,静默回退到编译进来的分组的行为也没了,于是手工编辑过的 moduli 文件现在可能导致连接失败。
在会话中途死掉的。 这一类调试起来要棘手得多。10.3(2026-04-02)移除了与不支持 rekey 的实现的 bug 兼容代码 — 连接正常建立,直到传输量累积到需要重新密钥时才失败。10.4 收紧了协议,对在认证后重新密钥期间发送非 KEX 消息的对端立即断开(对象是不遵守 RFC 4253 第 7.1 节的实现)。两者咬住的都不是最新的 OpenSSH,而是对面那些遗留的嵌入式设备。当出现一张写着「连是连得上,可一发大文件就断」的工单时,怀疑这个。
让自动化和工具链坏掉的。 10.1 把代理套接字从 /tmp 移到了 ~/.ssh/agent 之下。这是一项安全改进,意在阻止 /tmp 访问受限的进程去抓代理密钥,但所有以 glob 匹配 /tmp/ssh- 路径的监控与清理脚本都失效了。也有副作用 — 操作系统的 /tmp 清扫不再替你清走陈旧的套接字,于是 ssh-agent 新获得了自带的清理功能与标志(-U、-u、-uu,以及退回 /tmp 的 -T),并且为 NFS 家目录在套接字路径里放入了主机名哈希。10.4 里 sshd -G 的配置转储从清一色小写变为大小写混合("PubkeyAuthentication") — 以小写为前提做 grep 的合规脚本会静默地开始返回空结果。10.1 的 IPQoS 改造使得 lowdelay、throughput、reliability 这类 ToS 关键字现在被忽略,落回系统默认的 QoS。
认证行为发生变化的。 10.3 的变更中最值得玩味的一处 — 以前 principals 段为空的证书,在 authorized_keys 的 principals= 选项路径上被当作通配符,也就是被视为匹配任何 principal。这是有意为之的行为,却是个陷阱:若 CA 误发了一张 principals 为空的证书,它非但不是废证书,反而成了能登入所有信任该 CA 的账户的万能钥匙。从 10.3 起,空的 principals 什么都不匹配(TrustedUserCAKeys 路径本来就没有这个问题)。反过来,如果有环境依赖这种通配符行为,升级后认证会立刻坏掉 — 这意味着那份依赖本就危险,但坏就是坏。10.1 的 ssh-add 在把证书加入代理时,现在会自动把过期设为证书过期时刻加 5 分钟宽限(用 -N 禁用),而那些把短命证书塞进长命代理、指望「应该一直都在吧」的流水线,现在过期后就失败。
构建与部署环境。 从 10.4 起,在 Linux 上启用 seccomp 沙箱或 NO_NEW_PRIVS 失败会成为致命错误。以前只是记日志然后继续跑,如今这类系统必须在 configure 时点显式关掉沙箱。对象是把 sshd 跑在非常老的内核或古怪容器运行时之上的情形。
已预告的下一位打者。 10.1 与 10.2 的发布说明预告了 SHA1 SSHFP DNS 记录的废弃 — 在未来某个版本里 SHA1 SSHFP 将被忽略,ssh-keygen -r 将只生成 SHA256 记录。如果你的组织在 DNS 里种了 SSHFP,现在重新生成更划算(SHA256 SSHFP 从 2012 年的 6.1 起就受支持)。
此外 10.4 还有若干本身就构成升级动机的安全修复 — 恶意服务端可让 sftp host:/path . 下载写到错误位置的问题,远程到远程的 scp 复制可把文件写进目标目录的父目录的问题,internal-sftp 的命令行在第 9 个参数之后被静默截断以致安全选项可能被丢弃的问题,启用 GSSAPI 认证时的预认证 DoS,以及重新密钥期间服务端更换主机密钥时客户端的 use-after-free 这类。
不该做的事
不要在生产环境打开复合签名。 如上所见,规范是一份 -00 的个人提交草案,正文里还留着 TODO 和大小的笔误,OpenSSH 自己称它为 experimental。一旦线格式或密钥格式改变,你今天造出的密钥和部署配置就成了债务。在实验室里造个密钥、观察互操作 — 到此为止,才是契合当前阶段的行为。
不要为了签名现在就把主机密钥推倒重来。 签名不带回溯威胁,这是 OpenSSH 自己的说法。现在需要的不是 PQ 主机密钥的铺开,而是轮换的肌肉 — 一旦 CRQC 露头,你能多快把全组织的主机密钥和 CA 密钥换掉。没有清单就没有轮换。
不要把警告全局关掉。 WarnWeakCrypto 是一个今后还会管辖其他弱密码警告的选项。只用 Match 块按主机静默,并把被静默的主机清单本身当作一份技术债清单来管理。
不要把 KexAlgorithms 钉进黄金镜像然后忘掉。 为兼容老设备而一次性钉死的算法清单,会让 PQ 默认值、也让未来的移除,全都从你身边绕过去。如果钉死确实必要,最好连同一个过期日期一起写上。
实务检查清单
# 1) 确认客户端实际会谈判的 KEX — 首项是不是 mlkem768x25519-sha256
ssh -G host.example.com | grep -i kexalgorithms
# 2) 找出配置里任何钉死算法的地方
grep -riE 'kexalgorithms|hostkeyalgorithms|pubkeyacceptedalgorithms' /etc/ssh/
# 3) 找出假定代理套接字在 /tmp 之下的脚本 (10.1 里移到了 ~/.ssh/agent)
grep -rn 'tmp/ssh-' /opt/scripts/
# 4) 找出以小写 grep sshd -G 输出的地方 (10.4 里改成了大小写混合)
grep -rn 'sshd -G' /opt/scripts/ | xargs -r grep -l 'grep [a-z]'
# 5) 遗留设备的 rekey 存活测试 — 调低阈值强制触发重新密钥
ssh -o RekeyLimit=1M legacy-appliance 'dd if=/dev/zero bs=1M count=16' > /dev/null
除此之外,如果你的组织使用 SSH 证书,请趁现在把以下两件事列出清单:CA 发行流水线是否可能造出 principals 为空的证书(若还残留 10.3 之前的服务端,那就成了万能钥匙),以及 authorized_keys 里用到 principals= 选项的地方在哪。后量子迁移的全貌 — SSH 之外的 TLS、VPN 乃至代码签名 — 与后量子密码迁移:对抗 Q-Day 的 70 亿美元竞赛一篇叠着看会更好。
结语
OpenSSH 的后量子迁移正以教科书式的顺序推进。先是带回溯威胁的密钥交换 — 标准化之前用 sntrup761 出发(9.0),标准一出就换成 mlkem768(10.0),用警告去推动生态(10.1) — 然后是不带回溯威胁的签名,才刚刚以标准文档加实验实现出发(10.4)。急的先用力,不急的则谨慎。这份对顺序的分寸,与昨天看的 TLS 那一侧的故事按完全相同的原理运转,同时因 SSH 轻巧的握手而走上一条简单得多的路径。
与此同时,这趟发布列车一直在稳稳地清走旧东西。DSA、服务端默认值里的 modp DH、/tmp 里的代理套接字、对不能 rekey 的实现的耐心、空 principals 的通配符。每一项都是合理的移除,但咬住的时点各不相同 — 连接时、重新密钥时、升级之后、脚本运行时 — 所以没有清单就挨上一下会很疼。逐个版本地精读发布说明的 "Potentially-incompatible changes" 一节 — 归根结底,那就是本文全篇的摘要。
参考资料
- OpenSSH 10.4 发布说明 (2026-07-06)
- OpenSSH 10.3 发布说明 (2026-04-02)
- OpenSSH 10.1 发布说明 (2025-10-06)
- OpenSSH 10.0 发布说明 (2025-04-09)
- OpenSSH: Post-Quantum Cryptography — 警告文与 FAQ
- draft-miller-sshm-mldsa44-ed25519-composite-sigs — ML-DSA 44/Ed25519 复合签名规范
- draft-ietf-sshm-mlkem-hybrid-kex — mlkem768x25519-sha256 规范 (RFC 编辑队列)
- NIST FIPS 204 — ML-DSA 标准 (密钥与签名大小表)
- openssh-portable V_10_4_P1 — crypto_api.h 里的 MLDSA44 常量与 ssh-mldsa-eddsa.c
- PQ 证书为什么还没到 — TLS/WebPKI 侧的同一个故事 (相关文章)
현재 단락 (1/77)
[OpenSSH 10.4](https://www.openssh.com/txt/release-10.4) 于 2026 年 7 月 6 日发布。发布说明的自我介绍很谦逊 — "a number...