《协议是怎样炼成的》· SSH(上篇)你以为在登录服务器,其实服务器也在验证你
1995 年,赫尔辛基理工大学的校园网遭了一次口令嗅探攻击。一个叫 Tatu Ylönen 的研究员发现,自己和同事每天用来登录远程主机的 telnet、rlogin,是把密码原原本本塞进网线里的——任何能看到这段流量的人,抄走账号密码毫不费力。他花了几个月写了个东西来替掉它们,取名 Secure Shell,简称 SSH。同年 7 月放出来,年底就有五十个国家的两万名用户在用。
三十年过去,SSH 几乎成了远程运维的唯一入口。但绝大多数人对它的理解,停在”telnet 的加密版”——一个把密码藏起来的工具。这个理解不能算错,可它只看到了地基。SSH 真正解决的,是一个比”加密”更难的问题:在一条谁都能监听、谁都能插话的网络上,两台素未谋面的机器,怎么互相确认”你是不是我要找的那一个”。
这篇文章想讲清楚的,就是这个被低估的内核。而你会一路看到一条暗线:认证从来是双向的——你在登录服务器的同时,服务器也在被你验证。
理解 SSH 最好的方式,是顺着它当年被逼出来的那条路走一遍:它的每一层防御,都是被一个比上一个更强的攻击者逼出来的。 我们就按攻击者能力从弱到强,一关一关往下走——你会看到,每加一层,都是因为上一层挡不住一种新的攻击。
第一关:有人在偷看(被动窃听)—— 加密
最弱的攻击者,只能”看”——趴在链路上抄流量,不改、不冒充。可就这一手,已经要了 telnet 的命。
telnet 的毛病,归结成一个字:明文。你敲的每个字符、服务器回的每一行、还有那句要命的登录密码,全都以原样在网络上传输。在那个共享式以太网、抓包易如反掌的年代,这等于把钥匙挂在门把手上——本文开头那次 1995 年的嗅探,抄走的就是这样的明文口令。
要防偷看,办法很直接:加密。把内容用密钥搅成乱码,没有密钥的人看到的只是噪声。
可这里藏着一个先有鸡还是先有蛋的难题。最适合给大流量加密的是对称加密——加密和解密用同一把钥匙,快。但既然双方用的是同一把钥匙,这把钥匙总得先让两边都拿到。怎么拿到?你总不能把它明文发过去——那监听者连钥匙带内容一锅端。
DH 密钥交换
SSH 的解法,是 Diffie-Hellman 密钥交换(中文一般译作”迪菲-赫尔曼”,得名于 1976 年提出它的两位密码学家 Whitfield Diffie 和 Martin Hellman,下文简称 DH)。它要做成一件听起来不可能的事:双方在完全公开的信道上交换信息,却能各自算出同一个只有他们俩才知道的共享秘密;而旁观者哪怕把来回的报文一字不漏全抄走,也算不出这个秘密。
它靠的是一套公钥、私钥的配合。整个过程拆开,就四步:
① 公开参数:双方共用的”场地”。 先有两个公开的数——一个大素数 p 和一个生成元 g(椭圆曲线版 ECDH 里则是约定好的曲线和基点)。它们写在协议里、明文可发,不是任何人的密钥,只是双方做运算时共用的同一块场地。
② 各自生成一对临时公私钥。 你随机选一个数当私钥 a,自己留着、绝不示人;再用它在公开参数上算出公钥 A = g^a mod p。对方同样随机选私钥 b,算出公钥 B = g^b mod p。注意:a 和 b 是各自独立随机选的,彼此没有任何配对关系——你选你的、他选他的,事先不商量。
这里有个反直觉、却很关键的点:DH 的私钥并没有什么”生成算法”,它本身就是一个随机数——从一个密码学安全的随机数发生器里抽出来而已(公钥才是拿这个随机数算一下得来的)。这跟 RSA、Ed25519 那种”密钥要走一套生成流程”的印象不同。它带来一个直接后果:DH 的安全,死死系在随机数的质量上——随机数一旦可预测或有偏(历史上不少事故正是栽在设备刚开机熵不足、随机数发生器有缺陷上),私钥就可能被猜出来,整套防线随之崩塌。所以”随机选”这三个字不是随口一说,它是 DH 安全的命根子。
③ 只交换公钥。 你把 A 发给对方,对方把 B 发给你,明文传输。私钥 a、b 从头到尾待在各自机器里,绝不上网络。公钥之所以能大方公开,是因为从 A = g^a 倒推出私钥 a,是公认的数学难题(离散对数),算不动。
④ 各自用”自己的私钥 + 对方的公钥”算出共享秘密。 你拿对方的公钥 B、用你的私钥 a 算一次:S = B^a mod p;对方拿你的公钥 A、用他的私钥 b 算:S = A^b mod p。

妙就妙在这第 ④ 步——两个算式的结果必然相等:
B^a = (g^b)^a = g^(ab) A^b = (g^a)^b = g^(ab)
两人最后都把指数凑成了 a 和 b 的乘积,只是相乘的先后不同;而 a×b = b×a,先乘谁结果都一样。于是双方殊途同归,落到同一个 g^(ab) 上——这就是只有你俩知道的共享秘密。
而你并不需要知道对方的私钥 b:因为 b 早已”藏”在它的公钥 B = g^b 里,你把 B 再做 a 次方,b 就自动进到最终指数中;对方那边对称同理。两人都只动用了自己的私钥,却各自把对方的私钥”借”进了结果。
那外人为什么到不了同一个数?他手上只有公开的 g、p 和飞过网络的公钥 A、B;想算出 g^(ab),就得先从 A 或 B 倒推出某一方的私钥——又卡在离散对数上,算不动。所以 g^(ab) 是你俩能轻松抵达、外人却够不着的终点。
说到底,DH 就是这么回事:双方各自捏一个保密的私钥、只交换用它算出的公钥,靠 a×b = b×a 的对称性,在一条公开信道上,殊途同归到同一把只有彼此知道的密钥。
想亲手验一遍?(不感兴趣可跳过) 取 g=5、p=23,你选私钥 a=6、对方选私钥 b=15:
你的公钥 A = 5^6 mod 23 = 8,对方公钥 B = 5^15 mod 23 = 19(交换 A、B,旁人都看得到)。
你算 S = B^a = 19^6 mod 23 = 2,对方算 S = A^b = 8^15 mod 23 = 2。
一个走 19^6、一个走 8^15,表面南辕北辙,最后都落在 2——这个 2 就是共享秘密。
讲到这儿,要拎清一个最容易混淆的点:DH 解决的是”密钥交换”,不是”内容加密”。 它的产物是上面那个共享秘密 S,双方再从 S 派生出一把对称会话密钥;你真正要传的内容——命令、密码、文件——在 DH 这一步里一个字都没参与。真正给内容上锁的,是紧随其后的对称加密(如 AES、ChaCha20),用的就是这把派生出来的会话密钥。
分工是清楚的:DH 负责把同一把钥匙安全送到两边手里,对称加密负责用这把钥匙锁住内容。偷看的问题,要这两步合起来,才算解决。
真实案例 · Logjam(CVE-2015-4000,2015)。 DH 不是”用上就安全”,参数弱了照样被破。2015 年披露的 Logjam 攻击就是活例:当时大量服务器为兼容历史上的”出口级”弱加密,仍接受 512 位的 DH 参数;研究者发现,中间人能把握手降级到这种弱参数,而 512 位 DH 的离散对数,靠预计算几天、之后单次求解约一分钟就能算出来——共享秘密一旦被算出,”加密”就形同虚设。更微妙的是:很多服务器共用同一个素数 p,攻击者只要对这一个 p 做一次昂贵的预计算,此后就能批量解密所有用它的连接;研究者据此估计,国家级算力足以攻破当时广泛使用的 1024 位 DH。教训很直接:DH 要安全,参数(尤其 p 的位数)必须够大——今天的底线是 2048 位以上。
一个必须澄清的现实:上面我用经典的”大素数 p”DH 讲原理,因为它最直观;但现代 SSH 默认用的,其实是它的椭圆曲线版 ECDH。 别被”椭圆曲线”吓住——ECDH 仍然是 Diffie-Hellman,思想一模一样(各持私钥、只交换公钥、殊途同归到共享密钥),只是把”对大素数 p 取模的幂运算”换成了”椭圆曲线上的点运算”。自 OpenSSH 6.4(2013)起,默认的密钥交换就是 curve25519(即 X25519,一种 ECDH):用一条 256 位的曲线,达到约等于 3072 位大-p DH、也就是 128 位对称的安全,却快得多、短得多。那经典的大-p DH 去哪了?退成了兼容回退项——ssh -Q kex 里还能看到 diffie-hellman-group14/16/18(p 分别约 2048 / 4096 / 8192 位),只在对端不支持椭圆曲线时才轮到它们。所以上面 Logjam 说的”p 至少 2048”,针对的正是这些回退项;走默认的椭圆曲线路径,根本没有一个大 p 摆在那儿。更新的 OpenSSH 还更进一步,把默认换成了”X25519 + 后量子算法”的混合方案,为将来的量子计算机预先兜底——但那半截内核,依然是椭圆曲线版的 DH。
三种摆在一起,区别一目了然:
| 种类 | 底层数学 | 典型尺寸 | 安全强度 | 在 SSH 中的角色 |
|---|---|---|---|---|
| 经典 DH(group14/16/18) | 大素数取模幂运算 | p 2048 / 4096 / 8192 位 | ≥112 位对称(p 越大越高) | 兼容回退,对端不支持椭圆曲线时才用 |
| 椭圆曲线 DH(curve25519 / X25519) | 椭圆曲线点运算 | 256 位曲线 | 约 128 位对称 | 默认(OpenSSH 6.4 起) |
| 后量子混合(mlkem768x25519 等) | 格密码 + 椭圆曲线 | 256 位曲线打底 | 抗量子,且不弱于 curve25519 | 最新 OpenSSH 默认 |
所以 SSH 从没离开过 DH,只是把”大素数 p”换成了”椭圆曲线”(curve25519)。大-p DH 退居兼容回退,而”p 要多少位”,也只是回退项的事了。
第二关:有人在改包(主动篡改)—— 完整性
把攻击者升一级:他不再只是看,还能动手改你线上的报文。这时会暴露一个几乎所有人都忽略的盲区——**加密只防”读”,不防”改”**。
这点极其反直觉,却是密码学的硬道理:加密和完整性是两个独立的属性,加密绝不自动带来完整性。 你把内容加密了,挡住的只是”看懂”;一个能改报文的攻击者,照样能在看不懂内容的情况下篡改密文。对流式密码尤其致命——SSH 常用的 ChaCha20、CTR 模式都是流式:攻击者把密文里的某一位翻转,解密出来的明文对应位就跟着翻转。他不需要读懂,就能做出受控的、且接收方毫不知情的修改。
所以一条”只加密、不校验完整性”的信道是可锻造的。补这个洞的,是完整性校验:MAC(消息认证码),或 AES-GCM、ChaCha20-Poly1305 这类自带校验的算法。给每个报文盖一个校验,改一个字节,接收方一验就发现、当场丢弃。它和加密是天生一对,该紧挨着加密一起上。
真实案例 · Terrapin(CVE-2023-48795,2023)。 这不是纸上谈兵。2023 年底披露的 Terrapin 攻击,针对的正是 SSH 握手阶段的完整性。处在中间的攻击者,能在握手刚开始、序列号还没对齐的窗口里悄悄删掉若干报文,而双方都察觉不到——因为当时的实现对这段序列号的处理有缺陷,删包不触发任何完整性告警,后果是能降级掉一部分安全特性。它的修复,就是 OpenSSH 9.6(2023 年 12 月)引入的”严格密钥交换”(strict KEX)。Terrapin 微妙就微妙在:信道是加密的、握手看着也正常完成了,可完整性在某个窗口上有道缝——而正是这道缝,让”改包、删包却不被发现”成了可能。
最后再把这一关的位置坐实一句:完整性排在认证之前,不是叙事上的编排,而是协议本身的顺序。SSH 握手里,密钥交换一结束、双方互发 NEWKEYS,对称加密和完整性校验(MAC)是在同一刻一起开启的;要到这之后,才轮到用户认证登场。换句话说,等你开始向服务器证明身份,每一个认证报文本身就已经跑在完整性的保护之下了。所以这不是”能不能把完整性放认证前”的问题——在协议里,它本来就先于用户认证生效。
第三关:有人在假扮(冒充端点)—— 认证
再把攻击者升到最强一级:他不满足于改你的包,而是干脆假扮成你对面那台机器。
请在这里停一下,看清楚前两关有个共同的、没人明说的前提:加密也好、完整性也好,都建立在”你是在跟对的那一方协商密钥”之上。那么——万一跟你做 DH 的,本来就是攻击者呢?
设想一下:你以为在跟 server 握手,实际上中间有人截下了你的流量。他冒充 server 跟你做了一遍 DH,又转过身冒充你跟真正的 server 做了一遍 DH。两条加密信道都建得漂漂亮亮、完整性校验也都正常,可你加密发出去的每个字,都先经过他的手解密、再转发。前两关的防御非但没救你,反而给了你虚假的安全感——你以为没人看得见、没人改得了,其实坐在中间的人看得一清二楚。

这就是中间人攻击。它暴露的,是加密和完整性都无力回答的问题:对面到底是谁? SSH 对它的回答,叫认证。
3.1 两套密钥,干两件事
往下讲之前,先分清一件事,免得后面看到陌生名词犯晕:SSH 握手里其实动用了两套不同的非对称密钥,干两件不同的事。
- 一套是密钥交换用的(DH,现代实现多用它的椭圆曲线版 ECDH)。它临时生成、每次连接重新协商、用完即弃,只为算出这次的会话密钥——解决的是”内容怎么加密”。
- 另一套是主机密钥(host key):服务器长期固定的一对密钥,代表这台机器的”身份”,平时就躺在
/etc/ssh/下——解决的是”对面是谁”。主机密钥的算法,今天默认是 Ed25519(一种基于椭圆曲线的签名算法,又快又短,是当前首选),也可能是老一些的 RSA 或 ECDSA。
提前点破,免得待会儿指纹提示里冒出来的 ED25519 把你绕晕:它说的正是这把主机密钥的类型,管的是签名认证,跟前面做密钥交换的 DH 是两码事,别当成同一把。

3.1 签名,与它签的那份”会议记录” H
主机认证靠的是”签名”。在用它之前,得先把”签名”这个词本身说清楚——它是后面一切的基石。
什么是签名? 拿私钥对一段数据做一次密码学运算,得到一个签名值;任何持有对应公钥的人都能验证它。验证通过,意味着两件事:① 这签名确实出自持有该私钥的人;② 那段被签的数据一字未改。它有两个硬性质:没有私钥,伪造不出能通过验证的签名;签名又死死绑定在它所签的那段数据上,挪到别的数据上立刻失效。(注意,签名靠的是非对称密钥——私钥签、公钥验;这跟前面加密内容用的对称密钥是两码事。)
那服务器签的,又是哪段数据? 不是”我是 example.com”这种空话,而是本次握手的摘要——交换哈希 H。DH 一完成,双方就各自把整场握手的关键材料(双方的 DH 公开值、刚算出的共享密钥、以及服务器的主机公钥等)哈希成这一个值 H;它是这次握手独一无二的”会议记录”。记住 H,它后面还要再出场一次。

3.2 主机认证
分清了两套密钥、讲清了签名,主机认证本身就简单了:服务器用主机私钥对 H 签名发给你,你用它的主机公钥验签。验证通过,就说明:跟我做这次 DH、生成这个 H 的,确实握着这台服务器的私钥。
这里有个反直觉、却关键的点,值得单独说清楚。
中间人攻击里,攻击者并没有、也不需要拿到服务器的私钥。他干的是另一件事:分别和你、和真服务器各做一次完整且”正确”的握手——对你,他扮服务器;对真服务器,他扮你。两条信道都建得规规矩矩,他在中间解密再转发,单看加密和完整性,一切正常。这正是中间人最阴险的地方。
那主机认证凭什么还能拦住他?凭的是签名这一步——而签名背后那个前提:私钥能”证明持有”,却不必”交出来”。服务器用主机私钥对这一次握手(也就是 H)签名,你用它的公钥验证即可;私钥自始至终待在服务器上,从不上网络。攻击者手里没有这把私钥,于是卡死在两条路上:
- 想伪造签名? 没有私钥,造不出能通过验证的签名。
- 想把真服务器的签名原样转发给你? 也不行。这里有个关键性质:签名是一次一换的——主机私钥固定不变,但服务器每次连接都拿它对当次握手现签一个新签名,因为被签的 H 每次都不同(它含了本次临时 DH 的公开值)。攻击者跟真服务器做的是另一次 DH、生成的是另一个 H,那个签名只对那次有效;搬到你这条信道上、拿你这次的 H 一验,立刻对不上。
所以结论是:加密和完整性这两层,中间人能完美伪装;认证这层,他伪装不了。 他能冒充”一条通道”,却冒充不了”一个身份”——因为身份是用一把他拿不到、又永不外露的私钥签出来的。
而”私钥不外露也能证明持有”这个前提,不只是主机认证的根基;等下你会看到,用户认证用的是一模一样的招。

3.4 第一次连接:TOFU、指纹与 known_hosts
问题又往前递了一格:你得先手里有这台服务器正确的主机公钥,才谈得上验证。可第一次连一台陌生服务器时,你并没有。
这就是你第一次 ssh 一台新机器时,那句几乎没人细看的提示的由来:
1 | The authenticity of host 'example.com (203.0.113.10)' can't be established. |
SSH 在这里用的是一种叫 TOFU(Trust On First Use,首次使用即信任) 的策略:第一次见面,它没法替你判断真假,只能把这把公钥的指纹摊给你看,由你来拍板。理论上你该通过另一条可信渠道(机房交接单、云控制台、内部文档)核对这串指纹——现实中大多数人直接敲了 yes,这正是 TOFU 最脆弱的一环,后面坑点篇我们会专门算这笔账。
这里要分清两个一直在打交道、却常被混为一谈的东西——指纹和签名:
- 指纹(fingerprint)是固定的,给人看的。 提示框里那串
SHA256:6jK9...e3Qf,是服务器主机公钥本身的哈希,相当于这台机器的”身份证号”;只要主机密钥不换,它永远是这一串。它存在的意义,就是在第一次连接、你还没有这台服务器公钥时,让你这个人肉眼核对、拍板信不信。 - 签名是一次性的,给机器看的。 前面讲的那个”对本次握手 H 的签名”,每次连接都不一样,由你的 SSH 客户端拿主机公钥自动验证,你根本看不到、也不用看。

你眼睛要确认的,是固定的指纹;机器每次自动比对的,是一次性的签名。提示框给你看指纹而不是签名,正因为指纹固定、能靠另一条渠道核对;签名一次一变,没法拿给人”对一眼”。
而你一旦回答 yes,SSH 就把这把主机公钥记进 ~/.ssh/known_hosts。从此这台服务器再来,它会拿记录在案的公钥默默核对:对得上才继续,对不上就当场报警——那条所有人都见过、又最容易被无脑跳过的 HOST KEY CHANGED 警告,就是从这儿来的。
真实案例 · GitHub 主机密钥泄露(2023 年 3 月)。 整套信任,全压在”主机私钥不泄露”这一条上——因为它是服务器身份的唯一锚点。2023 年 3 月就出过这么一回:GitHub 不慎把自己的 RSA 主机私钥短暂提交到了一个公开仓库里。尽管没有证据表明它被人利用,GitHub 仍在发现后火速轮换了这把密钥,理由用它官方的话说,是”防止有人借此冒充 GitHub、或窃听用户的 Git over SSH 操作”——这正是主机认证要挡的攻击。轮换的直接后果,全世界开发者都撞上了:第二天 git pull 纷纷弹出 REMOTE HOST IDENTIFICATION HAS CHANGED、提示”可能有人在搞中间人”,得手动更新 known_hosts 里 GitHub 的指纹才能继续。这件事把整套机制照得透亮:服务器的身份全系于那把主机私钥的保密;一旦它可能泄露——哪怕只是’可能’——原指纹就必须作废、所有客户端都得重新确认。
到这里,请记住一件大多数教程不会强调的事:刚才这一整套——主机密钥、指纹、known_hosts——验证的是服务器,是服务器在向你证明”我就是我”。 你还没输密码、还没动用你自己的任何凭据,认证就已经先发生了一轮,方向是服务器 → 你。
这是”认证是双向的”这条暗线的上半场。那下半场自然就是:服务器又凭什么相信,坐在键盘前敲命令的,是有权登录的那个你?
3.5 用户认证(暗线的下半场)
反过来,服务器也得回答一个对称的问题:凭什么相信,坐在键盘前敲命令的,是有权登录的那个你?这就轮到用户认证。
先记住这个顺序——它正是大多数人忽略的那半:你以为 ssh 登录就是”我向服务器证明自己”,可在你递出任何凭据之前,服务器已经先向你证明过它自己了(上半场那一整套)。现在,才轮到反方向。
SSH 定义了几种用户认证方式,日常最常用的是两种:密码、公钥。(另有 hostbased、以及常用来接 OTP / 双因素的 keyboard-interactive,留到后面再说。)
密码认证:能用,但把秘密交了出去。 最直白的是密码:你把密码发给服务器,服务器核对。注意,这跟 telnet 的明文密码不是一回事——此刻信道已被前面那把会话密钥加密,密码不会在网线上裸奔。但它有个绕不过的软肋:密码这个秘密,终究要离开你、送到服务器那端去核对。 这意味着服务器进程会看到它的明文(解密之后),意味着它可能被弱口令爆破、被撞库、被你在十台机器上重复使用。密码的安全,依赖”它在两端都不泄露”——而这条链上环节越多,越容易出事。
公钥认证:把”主机认证”那招,反过来用。 公钥认证的妙处,是它根本不传任何秘密。而且如果你读懂了上半场,会有种似曾相识的感觉:它就是把服务器证明自己的那套办法,方向调转,用在你身上。
回忆服务器怎么证明自己的:用私钥对本次握手(那个交换哈希 H)签名,你用它的公钥验证,私钥从不出门。现在轮到你:
- 你先生成一对密钥:私钥留在自己机器(
~/.ssh/下),公钥事先放进服务器上你账户的~/.ssh/authorized_keys。 - 登录时,你的客户端用你的私钥,对一段数据签个名发给服务器——而这段数据打头的,正是本次会话的标识,也就是那个交换哈希 H(眼熟吧?就是服务器刚才签过的同一个 H)。
- 服务器拿
authorized_keys里你的公钥验这个签名。验证通过,就证明:请求登录的,确实握着这把私钥——也就是你。
整个过程里,你的私钥一步都没离开本机。服务器手上只有你的公钥;就算服务器被攻破,攻击者拿到的也只是一堆公钥,冒充不了你(公钥只能验签、不能伪造签名)。这正是公钥认证强过密码的根本:密码要把秘密送出去核对,私钥只把”签名”送出去证明,秘密本身永不离身。 和主机认证一样,你签的数据里含了本次会话的 H,所以一个签名只对这一次连接有效——别人录下来,换个会话重放也没用。
3.6 暗线闭合:认证从来是双向的
把上下半场连起来看,那条暗线就完整了,而且对称得近乎优雅——两次认证,签的是同一个 H,方向正好相反:
- 服务器 → 你(主机认证):服务器用主机私钥签 H,你用
known_hosts里的公钥验。 - 你 → 服务器(用户认证):你用自己的私钥签 H,服务器用
authorized_keys里的公钥验。

同一份”会议记录” H,被两边各自用自己的私钥签了一遍、又各自用对方的公钥验了一遍。你以为登录只是”你在向服务器证明自己”,其实这是一次彻头彻尾的相互验证:服务器先证明了它没被冒充,你再证明你有权进来。一次握手,两个方向的认证——这就是 SSH 被低估的内核。
把整条流程画成一张骨架图,双向认证就一目了然了:

看这张图有两件事值得品:一是 ② 和 ③ 方向相反、签的却是同一个 H——这就是”双向认证”四个字的全部含义;二是中间那道 NEWKEYS 分水岭。NEWKEYS 是握手里一条单字节的信令,作用就是当”切换开关”:双方互发它,宣告”从此刻起,启用刚协商好的新密钥与新算法”——对称加密与完整性校验,从这一刻同时生效。它把握手劈成两半:主机认证在它之前(还在明文协商阶段,靠签名自证),用户认证在它之后(已在加密信道里做)。这也顺带解释了:为什么哪怕你用密码登录,密码也不会明文裸奔——因为提交密码时,信道早加密了。
上面是逻辑骨架。如果你想看到报文级别的完整时序——从 TCP 落地、版本协商,到打开 channel 的每一步——见下面这张时序图;而支撑这一切的,是 SSH 协议自下而上的三层结构。


第四关:有人在等(先存下、赌将来)—— 前向保密
最后一个攻击者最有耐心:他现在什么都解不开,于是把你的密文全程录下来存着,赌的是”将来”——将来某天拿到服务器的主机私钥,再回头把这些旧流量一次性解密。
挡这一手的,叫前向保密(forward secrecy)。它其实早已埋在前面那个”两套密钥”的设计里了,现在到了揭晓的时候。
你可能一直有个疑问:既然服务器有一把固定的主机密钥,会话密钥为什么不干脆用它加密传过来,还要绕一道临时 DH?答案正是为了前向保密。会话密钥来自每次新生成、用完即弃的临时 DH,主机密钥全程只管签名认证、从不碰密钥本身。这一拆带来一个硬性质:哪怕主机私钥将来被偷,也解不开任何一次过去的会话——因为那些会话的密钥,早随临时 DH 的随机数一起灰飞烟灭了。
反过来想就知道这有多要紧:假如会话密钥是用固定主机密钥加密传输的,那么上面那个有耐心的攻击者,只要默默录下你所有历史密文,哪天偷到主机私钥,就能把过往全部会话一次性解密。SSH 这套”临时产钥匙、固定钥匙只盖章”的分工,正是为了堵死这条路。(顺带一提,Ed25519、ECDSA 本就是纯签名算法,压根没法拿来加密传输密钥——这条捷径在工程上也走不通。)
真实案例 · Heartbleed(CVE-2014-0160,2014)。 这个”将来”绝非杞人忧天。2014 年的 Heartbleed 就是一次大规模的私钥泄露:OpenSSL 的一个内存越界缺陷,让攻击者能从服务器内存里直接读出私钥,且不留痕迹,受影响的服务器以百万计。事后,US-CERT 给出的关键缓解建议正是——启用前向保密,以减轻”未来私钥泄露”造成的损害。道理就是上面这套:只要会话密钥来自用完即弃的临时 DH,那么哪怕私钥被 Heartbleed 掏走,过去那些被录下来的流量也照样解不开。它微妙在哪?在 Heartbleed 之前,很多人觉得”私钥放在服务器上很安全、前向保密是多余的讲究”;Heartbleed 用一个谁都没料到的内存 bug 证明了——私钥会以你想象不到的方式泄露,而前向保密,正是为这种”想象不到”兜底的。
最后:几个最容易搞混的点
四关走完,SSH 的内核就齐了。它绕来绕去就是”密钥”和”签名”,有几处概念特别容易混,临收尾集中澄清一下:
1. DH 是”密钥交换”,不是”内容加密”。 DH 只负责让双方协商出同一把密钥,它本身不加密你的任何数据。真正加密命令、文件的是对称加密(AES、ChaCha20)——DH 送钥匙,对称加密锁内容。
2. 非对称管”开头”,对称管”之后”——分水岭是 NEWKEYS。 整场握手是两类密码学在接力:开头用非对称(DH 密钥交换 + 主机认证签名),把”钥匙”和”身份”都办妥;NEWKEYS 之后切对称,此后所有内容都用会话密钥对称加密。一个常被忽略的点:用户认证发生在 NEWKEYS 之后——所以你提交公钥签名(或密码)的那一刻,信道已经是加密的了。
3. 签名 ≠ 指纹。 签名一次一换、给机器自动验;指纹固定不变、给人肉眼核对(是主机公钥的哈希)。详见前面主机认证一节。
4. 做认证的”签名”,一律是非对称的。 主机认证也好、用户认证也好,签名 / 验签靠的都是非对称密钥(私钥签、公钥验)。对称密钥只管加密内容,从不拿来证明身份。
5. SSH 的内核是三件事,不是两件。 这一路我们其实办成了三件事,对应前三关:保密(防偷看,第一关)+ 完整性(防篡改,第二关)+ 认证(防冒充,第三关);第四关的前向保密,则是给”认证所依赖的长期私钥将来失守”再上的一道时间维度的保险。别只记住”加密”——完整性和认证同样是内核。
看清楚这条线了吗——从只能偷看的,到能改包的,再到能假扮的,最后到肯等到将来的:攻击者每强一分,SSH 就补一层。这四层叠起来,才是它三十年没被换掉的真正原因。而贯穿始终、又最容易被忽略的那条暗线是——你从不只是单方面登录一台服务器;你和它,自始至终在互相验明正身。
最后提一个名词,免得日后混淆:和”签名”经常被相提并论、甚至被搞混的,还有”证书(certificate)“。先记一句:证书是”由一个受信任的第三方,对某把公钥再签一道名”,用来解决机器一多、
known_hosts/authorized_keys管不过来的问题。它和这里讲的签名是同一套密码学、只是不同用法——具体留到后面章节专门讲。