SSH 不是「加密版的 TELNET」,它把明文、无认证、无完整性三个缺陷一起修了。 最常用的登录方式是公钥认证:私钥一次都不出现在网络上。
这一节讲清 SSH 修了哪几件事,以及这些修补的代价。 代价就是「握手变重」——下面把每个阶段的字节数量出来。
一、它修了四个结构性缺陷
第一个是明文:全程加密,抓包看到的只是一串乱码。 第二个是主机身份:SSH 会验证服务器的公钥。 第一次连接时问你「这个指纹对不对」,确认之后就记在 known_hosts 里,以后指纹变了就报警。 这是防中间人的第一道防线,也是被所有人无脑点「yes」跳过的那一道。 第三个是用户认证:不再只靠密码,公钥认证、密码认证、多因子都能用。 第四个是数据完整性:每个数据包带一个 MAC,改一个比特就断。
二、公钥认证:私钥从不上网
公钥认证的流程值得单独说一遍,因为它回答了一个常见疑问:「私钥会不会被传过去?」 答案是不会。 过程是:服务器发一段随机数,客户端用私钥签个名,服务器用公钥验签。 私钥从头到尾只在自己的机器上参与运算,一个字节都没发出去。 所以「密码泄露」这件事在公钥认证下根本不会发生。 这也是为什么服务器运维早就全面转向公钥加跳板机—— 密码可以被猜到、被撞库、被钓鱼,私钥不能(它要你手里的文件)。 代价是「私钥文件本身成了唯一凭据」:所以私钥必须加口令保护, 否则「拿到你的笔记本」就等于「拿到你的所有服务器」。 这条链上最弱的一环,从「密码强度」变成了「终端安全」。
三、代价:握手变重了
TELNET 建连只要一个 TCP 三次握手,之后直接发字符。 SSH 在 TCP 之上还要谈三轮:版本串 → 算法协商 → 密钥交换。 这三轮的字节数不大,但轮数多,所以「建连时间」从 TELNET 的一个 RTT 变成 SSH 的两三个 RTT。 在局域网里感觉不出来,在跨洋链路上就是几百毫秒的差别。 所以 SSH 后来加了「连接复用」:第一次连上之后开一条主连接,后面的会话都搭在这条上, 省掉重复握手。 这个思路和 HTTP 的 keep-alive 完全一样。 日常成本还有一项:每个数据包多 16 字节认证标签,再加分组密码的对齐填充。 一次交互式登录只传几十字节,却要付几十字节的代价。 这就是「加密不免费」的具体含义。 但因为它是「每次连接付一次」而不是「每字节都付」,所以可以接受。
四、动手 Demo:SSH 补了 TELNET 的哪几个洞
先读本机 SSH 客户端的真实版本,再把握手的几个阶段量一遍。 重点是「每个阶段花多少字节」。
import subprocess
p = subprocess.run(['ssh', '-V'], capture_output=True)
print((p.stdout or p.stderr).decode().split('\n')[0])
vstr = 'SSH-2.0-OpenSSH_10.3'
kex = ['curve25519-sha256', 'ecdh-sha2-nistp256',
'diffie-hellman-group14-sha256']
enc = ['[email protected]', 'aes128-ctr',
'[email protected]']
mac = ['[email protected]', 'hmac-sha2-512']
print('版本串', len(vstr), '字节')
print('算法名字串', sum(len(x) + 1 for x in kex + enc + mac), '字节')
实跑输出:
--------------------------------------------------------------------
SSH 补了 TELNET 的哪几个洞
--------------------------------------------------------------------
**SSH 不是「加密版的 TELNET」**,**它把 TELNET 的三个结构性缺陷一起修了。**
**本机装的 SSH 客户端版本**:
OpenSSH_10.3p1, OpenSSL 3.5.7 9 Jun 2026
缺陷 TELNET SSH 修好之后
----------------------------------------------------------------------------------
**明文传输** **是** **否,全程加密** **抓包看不到密码**
**主机身份** **不验证** **验证服务器公钥** **防中间人**
**用户认证** **只传密码** **公钥、密码、多因子** **可以不传密码**
**数据完整性** **无** **每包带 MAC** **改一个比特就断**
**端口转发** **无** **有,三条路** **能穿隧道**
**第三行值得单独说**:
**SSH 最常用的登录方式是「公钥认证」**——**私钥留在你机器上,服务器上只放公钥。**
**整个过程中,私钥一次都没有出现在网络上。**
**所以「密码泄露」这件事在公钥认证下根本不会发生。**
**它的代价是握手变重了**。**TELNET 建连只要一个 TCP 三次握手,之后直接发字符。**
**SSH 在 TCP 之上还要谈一轮版本、谈一轮算法、做一次密钥交换。**
**下面把这几个阶段的字节数量一下**:
阶段 做什么 量级
--------------------------------------------------------------------
版本串 两端各发一次 20 字节
算法协商(KEXINIT) 两端的算法名字列表 名字串 175 字节
密钥交换 curve25519 一次 公钥 32 字节
之后每个数据包 加密加 MAC 额外 16 字节认证标签
**最后一行是 SSH 的日常成本**:**每个数据包多 16 字节认证标签,再加上分组密码的对齐填充。**
**一次交互式登录传几十个字节,却要付几十个字节的代价。**
**这就是「加密不免费」的具体含义。**
**但因为它是「每次连接只付一次」,不是「每字节都付」,所以可以接受。**
本机是 OpenSSH_10.3p1,底下的 OpenSSL 是 3.5.7。
版本串 20 字节,两端的算法名字串 175 字节。
所以「握手」的绝对字节数并不大——真正花时间的是「轮数」,不是字节数。
算法协商这一步是「各报一串自己支持的算法,取第一个两边都有的」。
所以列表的顺序是有意义的:客户端把自己最想用的排前面。
这也是为什么「服务端配置里删掉弱算法」能生效——删掉之后交集里就没有它了。
第三行那个 etm 是 Encrypt-then-MAC:先加密再对密文算 MAC。
它比「先算 MAC 再加密」安全,因为攻击者拿不到明文就没法伪造 MAC。
30 秒实验
- 量一下:
ssh -V的输出里,OpenSSL 版本说明了什么? - 想一下:为什么公钥认证不需要传私钥?
- 想一下:known_hosts 里记的是什么?删掉它会怎样?
常见误区
- 以为「SSH 就是加密过的 TELNET」。 它还加了主机认证和完整性校验,这是三件不同的事。
- 以为「公钥认证会把公钥传过去验」。 公钥本来就在服务器上, 客户端传的是「用私钥做的签名」。
- 以为「加密不花代价」。 每包 16 字节标签加填充,小包场景下占比很高。
自测三问(点开对答案)
1. SSH 修了 TELNET 哪几个缺陷? —— **明文、主机身份、用户认证、数据完整性**,四个一起修。 2. 公钥认证为什么不怕密码泄露? —— **私钥只在本地参与运算**,一个字节都不上网。 3. `etm` 是什么意思? —— **Encrypt-then-MAC**:先加密再对密文算 MAC。下一节换到文件传输,看 FTP 为什么非要两条连接。
- ☐ (本节完,下一节 8.3 文件传输)