SSH 算法协商完全解析:密钥交换、主机密钥与加密算法的角色与协作
一、为什么需要算法协商
SSH 客户端和服务器建立连接时,双方可能是不同厂商、不同版本、不同安全策略的实现。它们必须先就”用什么算法来保护这条连接”达成一致。
这个过程就像两个人打电话前先确认:”你听得懂中文吗?听不懂?那英文呢?……” 只有找到双方都支持的语言,对话才能开始。
SSH 协议规定了几个独立的参数维度,每个维度都必须至少有一个双方都支持的选项,否则连接直接失败。核心维度是:
- 密钥交换算法(KexAlgorithms):双方如何安全地生成共享会话密钥
- 主机密钥类型(HostKeyAlgorithms):服务器用什么算法证明自己的身份
- 加密算法(Ciphers):用什么算法加密传输的数据
- 消息认证码(MACs):用什么算法保证数据完整性
二、如何查看客户端支持的算法
2.1 ssh -Q 系列命令
ssh -Q 用于查询客户端编译时支持的算法列表:
ssh -Q kex # 密钥交换算法
ssh -Q key # 主机密钥/用户密钥类型
ssh -Q cipher # 加密算法
ssh -Q mac # 消息认证码算法
ssh -Q help # 列出所有可用的 -Q 参数
关键理解:ssh -Q 回答的是”这个 SSH 二进制文件里编译进去了什么”,不等于“默认启用了什么”。不安全的算法可能被编译进去以保持兼容性,但默认是关闭的。
2.2 查看默认实际启用的算法
ssh -Q 没有 -default 后缀参数(ssh -Q kex-default 会报错)。要看客户端实际会优先提出哪些算法,用 ssh -G:
ssh -G example.com | grep -i kexalgorithms
ssh -G example.com | grep -i hostkeyalgorithms
ssh -G example.com | grep -i ciphers
ssh -G example.com | grep -i macs
ssh -G 会应用所有配置文件后,打印出针对目标主机最终生效的配置。输出中排在第一位的算法就是客户端连接时会优先提出的选项。
2.3 查看真实连接协商结果
ssh -vv user@host 2>&1 | grep -E "kex: algorithm|kex: host key algorithm"
这会显示这条连接最终协商出的 KEX 算法和主机密钥算法,是最直接的答案。
三种查询方式对比:
| 命令 | 回答的问题 |
|---|---|
ssh -Q kex | 客户端支持哪些算法 |
ssh -G host | 客户端默认优先提出哪些算法 |
ssh -vv host | 这条连接实际协商出什么算法 |
三、密钥交换算法(Key Exchange Algorithms)
3.1 它解决什么问题
在 SSH 连接建立的最初阶段,双方需要在一条不安全的网络上,安全地协商出一个只有双方知道的共享会话密钥。这个会话密钥将用于后续所有数据的加密。
密钥交换算法基于 Diffie-Hellman 密钥交换或其变体。DH 的核心思想是:
- 双方各自生成一个临时私密值。
- 双方交换由私密值计算出的公开参数。
- 双方各自用”自己的私密值 + 对方的公开参数”计算出相同的共享密钥。
- 窃听者只能看到公开参数,无法推导出共享密钥。
这个共享密钥就是后续加密用的会话密钥。
3.2 Diffie-Hellman 的变体
纯 DH 有几个问题:容易受中间人攻击、计算开销大、群参数可能不安全。因此演化出多种变体:
| 变体 | 原理 | 优点 |
|---|---|---|
| ECDH | 用椭圆曲线代替有限域 | 密钥更短,性能更好 |
| Curve25519 | 特定椭圆曲线,专为 ECDH 设计 | 高速、抗侧信道攻击 |
| DH Group Exchange | 动态协商 DH 群大小 | 避免固定群被预计算攻击 |
| 混合抗量子 | 传统 ECDH + 后量子算法 | 抵御未来量子计算机威胁 |
3.3 客户端支持列表解读
以下是你客户端 ssh -Q kex 的输出:
默认启用(现代、安全)
| 算法 | 原理与说明 |
|---|---|
sntrup761x25519-sha512@openssh.com | 混合抗量子算法:sntrup761(后量子)+ x25519(经典 ECDH)。即使未来量子计算机破解了 x25519,sntrup761 仍能保护。OpenSSH 新版本首选 |
curve25519-sha256 | 基于 Curve25519 的 ECDH,性能与安全俱佳,无已知弱点 |
curve25519-sha256@libssh.org | 同上的早期变体,仍广泛支持 |
ecdh-sha2-nistp256/384/521 | 基于 NIST 素域曲线的 ECDH。P-256/384/521 分别对应 128/192/256 位安全强度 |
diffie-hellman-group-exchange-sha256 | 可动态协商 DH 群大小,避免固定群被预计算 |
diffie-hellman-group16-sha512 | 4096 位 DH 群,SHA-512 哈希 |
diffie-hellman-group18-sha512 | 8192 位 DH 群,SHA-512 哈希 |
diffie-hellman-group14-sha256 | 2048 位 DH 群,SHA-256 哈希,IETF 标记为 MUST 支持 |
默认禁用(编译进去但不启用)
| 算法 | 问题 |
|---|---|
diffie-hellman-group1-sha1 | 1024 位 DH 群,易受 Logjam 攻击(预计算后快速破解) |
diffie-hellman-group14-sha1 | SHA-1 哈希,已不推荐 |
diffie-hellman-group-exchange-sha1 | SHA-1 哈希,已不推荐 |
关键点:ssh -Q kex 列出 diffie-hellman-group1-sha1,不代表客户端会用它。只有显式配置 KexAlgorithms +diffie-hellman-group1-sha1 时才会启用。
四、主机密钥类型(Host Key Types / HostKeyAlgorithms)
4.1 它解决什么问题
当客户端决定信任一个服务器时,需要验证”这个服务器确实是它声称的那台机器,而不是中间人伪装的”。
服务器通过出示自己的主机密钥来证明身份。主机密钥类型参数决定了客户端接受服务器用哪种算法生成这个身份证明。
4.2 主机密钥的工作原理
- 服务器上有一对主机密钥(私钥 + 公钥),私钥只有服务器知道。
- 连接时,服务器把主机公钥发给客户端。
- 服务器用主机私钥对密钥交换过程中的数据签名。
- 客户端用主机公钥验证签名,确认服务器持有对应的私钥。
- 客户端检查这个公钥是否与
~/.ssh/known_hosts中记录的一致。
4.3 各算法类型详解
| 算法 | 数学基础 | 密钥长度 | 安全性 | 推荐状态 |
|---|---|---|---|---|
| Ed25519 | Edwards 曲线(Curve25519 的变体) | 固定 256 位 | 约等于 RSA 3072 | 首选,性能与安全最佳 |
| ECDSA | NIST 素域曲线 | 256/384/521 位 | 分别约等于 RSA 3072/7680/15360 | 可用,NIST 曲线有争议 |
| RSA | 大整数分解 | 2048-16384 位 | 2048 位约等于 112 位安全强度 | 兼容性最强,新环境不首选 |
| DSA | 离散对数 | 固定 1024 位 | 仅约 80 位安全强度 | 已弃用 |
4.4 客户端支持列表解读
以下是你客户端 ssh -Q key 的输出:
普通密钥类型
| 算法 | 状态 |
|---|---|
ssh-ed25519 | 默认启用,首选 |
ecdsa-sha2-nistp256/384/521 | 默认启用 |
ssh-rsa | 默认禁用(OpenSSH 8.8+),仅兼容旧服务器时手动启用。原因是 SHA-1 签名已被认为不安全 |
ssh-dss | 默认禁用(OpenSSH 7.0+),DSA 已废弃 |
证书类型(-cert-v01@openssh.com 后缀)
这些是 SSH 证书用的密钥类型。SSH 证书是一种比 authorized_keys 更中心化的信任管理方式:由 CA(证书颁发机构)签发证书,服务器信任 CA 即可,不用逐个添加公钥。
| 算法 | 说明 |
|---|---|
ssh-ed25519-cert-v01@openssh.com | Ed25519 证书 |
ecdsa-sha2-nistp256-cert-v01@openssh.com | ECDSA 证书 |
ssh-rsa-cert-v01@openssh.com | RSA 证书 |
ssh-dss-cert-v01@openssh.com | DSA 证书(已废弃) |
FIDO/U2F 安全密钥类型(sk- 前缀)
sk- = security key,指硬件安全密钥(如 YubiKey)。
| 算法 | 说明 |
|---|---|
sk-ssh-ed25519@openssh.com | Ed25519 硬件密钥 |
sk-ecdsa-sha2-nistp256@openssh.com | ECDSA 硬件密钥 |
这些用于物理硬件密钥,登录时需要触碰硬件才能完成认证,安全性更高。
4.5 主机密钥从哪来
服务器上的主机密钥位于 /etc/ssh/,命名格式为 ssh_host_<类型>_key(私钥)和 .pub(公钥)。安装 openssh-server 时会自动生成一组。
客户端首次连接服务器时,会收到服务器的主机公钥。确认指纹后,该公钥保存在 ~/.ssh/known_hosts。后续连接时,客户端会检查服务器出示的主机密钥是否与 known_hosts 中记录的一致,不一致则发出严重警告。
五、加密算法(Ciphers)
5.1 它解决什么问题
会话密钥协商完成后,SSH 连接上传输的所有数据(密码、命令、文件内容)都需要被加密。加密算法参数决定了用哪种对称加密算法来保护数据流。
5.2 对称加密的两种模式
| 模式 | 全称 | 特点 |
|---|---|---|
| CTR | Counter Mode | 把块密码变成流密码,每个块用递增计数器加密。需要额外的 MAC 保证完整性 |
| GCM | Galois/Counter Mode | CTR + GMAC 认证,同时提供加密和完整性。属于 AEAD |
| CBC | Cipher Block Chaining | 每个块与前一个密文块异或。易受填充 oracle 攻击,已不推荐 |
5.3 AEAD 是什么
AEAD(Authenticated Encryption with Associated Data,带关联数据的认证加密) 是一种同时提供机密性和完整性的加密方案。
传统模式(如 CBC、CTR)只负责加密,完整性要另加 MAC。AEAD 把两者合一,更安全也更高效。
chacha20-poly1305 和 aes*-gcm 都是 AEAD 算法。
5.4 客户端支持列表解读
以下是你客户端 ssh -Q cipher 的输出:
默认启用(AEAD 类算法)
| 算法 | 原理与说明 |
|---|---|
chacha20-poly1305@openssh.com | ChaCha20 流加密 + Poly1305 认证。在无 AES 硬件加速的平台上性能极佳。注意:Terrapin 攻击论文指出其存在序列号操纵风险,缓解措施是实现 strict-kex |
aes256-gcm@openssh.com | AES-256 + GCM 认证模式。Intel 平台有 AES-NI 硬件加速 |
aes128-gcm@openssh.com | AES-128 + GCM,同上但密钥较短 |
aes256-ctr | AES-256 计数器模式,需配合 MAC 算法保证完整性 |
aes192-ctr | AES-192 计数器模式 |
aes128-ctr | AES-128 计数器模式 |
默认禁用(CBC 模式,不安全)
| 算法 | 问题 |
|---|---|
aes256-cbc / aes192-cbc / aes128-cbc | CBC 模式易受填充 oracle 攻击(如 Lucky13) |
3des-cbc | 3DES 效率低,且已被认为强度不足 |
注意:你的输出中没有 des-cbc,说明客户端已彻底移除最弱的 DES。
六、消息认证码(MACs)
6.1 它解决什么问题
MAC 用于保证数据完整性,防止传输过程中被篡改。
在非 AEAD 加密算法(如 aes*-ctr)下,MAC 是独立的一层保护:发送方对数据算一个认证标签,接收方验证标签。如果数据被篡改,标签对不上。
6.2 ETM 模式
-etm@openssh.com 后缀表示 Encrypt-then-MAC(先加密再认证)。
| 模式 | 流程 | 安全性 |
|---|---|---|
| MAC-then-Encrypt | 先算 MAC,再加密(MAC 和数据一起加密) | 较弱,易受填充攻击 |
| Encrypt-then-MAC | 先加密,再对密文算 MAC | 推荐,更安全 |
| Encrypt-and-MAC | 同时加密和算 MAC,分别发送 | 较弱 |
ETM 模式更安全,因为接收方可以先验证 MAC 再解密,避免对恶意构造的密文进行解密操作。
6.3 客户端支持列表解读
以下是你客户端 ssh -Q mac 的输出:
ETM 模式(Encrypt-then-MAC,推荐)
| 算法 | 说明 |
|---|---|
hmac-sha2-256-etm@openssh.com | SHA-256 + ETM,推荐 |
hmac-sha2-512-etm@openssh.com | SHA-512 + ETM,推荐 |
umac-128-etm@openssh.com | UMAC-128 + ETM,基于通用哈希,速度快 |
umac-64-etm@openssh.com | UMAC-64 + ETM |
hmac-sha1-etm@openssh.com | SHA-1 + ETM,较弱 |
hmac-sha1-96-etm@openssh.com | SHA-1 截断到 96 位 + ETM,弱 |
hmac-md5-etm@openssh.com | MD5 + ETM,已不安全 |
hmac-md5-96-etm@openssh.com | MD5 截断 + ETM,已不安全 |
非 ETM 模式(传统,较弱)
| 算法 | 说明 |
|---|---|
hmac-sha2-256 | SHA-256,推荐 |
hmac-sha2-512 | SHA-512,推荐 |
umac-128@openssh.com | UMAC-128 |
umac-64@openssh.com | UMAC-64 |
hmac-sha1 | SHA-1,较弱 |
hmac-sha1-96 | SHA-1 截断,弱 |
hmac-md5 | MD5,已不安全 |
hmac-md5-96 | MD5 截断,已不安全 |
关键点:hmac-md5、hmac-sha1 虽然编译进去了,但默认不启用。现代 OpenSSH 默认只用 hmac-sha2-* 和 umac-* 的 ETM 版本。
七、known_hosts 与主机密钥变更
7.1 known_hosts 是什么
~/.ssh/known_hosts 是客户端保存已信任服务器主机公钥的文件。首次连接某台服务器时,客户端会问:
The authenticity of host 'server (10.0.0.1)' can't be established.
ED25519 key fingerprint is SHA256:vuKnvrpcVAk2nu0W5zM2Kqdi48TTY/2TDNiMjSRbN8Q.
Are you sure you want to continue connecting (yes/no/[fingerprint])?
你输入 yes 后,服务器的主机公钥就被写入 known_hosts。下次再连,客户端自动比对,不再询问。
7.2 每行记录的结构
server,10.0.0.1 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI...
└──────┬──────┘ └────┬────┘ └──────────┬──────────┘
主机名/IP 密钥类型 公钥数据
- 主机名和 IP 用逗号分隔,表示同一个主机
- 可以有多行,对应不同算法类型(ed25519、ecdsa、rsa)
- 如果主机名被哈希处理(
HashKnownHosts yes),第一段会变成乱码
7.3 主机密钥变更时会怎样
当服务器重装、升级、或更换主机密钥后,你再次连接会看到:
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
...
Offending ED25519 key in /home/user/.ssh/known_hosts:5
这是 SSH 在保护你:新指纹和旧记录不一致,无法确认服务器身份。
7.4 什么时候该删除旧记录
两种情况:
情况一:服务器确实重装了(合法变更)
你确认服务器被重装/换密钥了,就该删除旧记录,重新信任新公钥:
ssh-keygen -R 10.0.0.1
ssh-keygen -R server.example.com
然后重新连接,会再次提示确认新指纹。
情况二:服务器没动过(可能是中间人攻击)
如果你没有重装服务器、没有换密钥,却收到这个警告——千万别直接删记录。这可能是中间人攻击。先用别的方式联系管理员,确认服务器主机密钥到底有没有变。
7.5 实用命令
# 删除指定主机的旧记录
ssh-keygen -R 10.0.0.1
# 查看 known_hosts 中某主机的记录
ssh-keygen -F 10.0.0.1
# 手动查看 known_hosts
cat ~/.ssh/known_hosts
# 查看某主机的指纹(重新连接时对比)
ssh-keyscan -t ed25519 10.0.0.1 | ssh-keygen -lf -
ssh-keyscan 可以从服务器抓取当前主机公钥,配合 ssh-keygen -lf 算出指纹,用于对比。
八、ssh_config 与 sshd_config 常用指令
8.1 客户端配置文件
客户端配置分两层:
| 文件 | 作用范围 |
|---|---|
/etc/ssh/ssh_config | 全局,影响所有用户 |
~/.ssh/config | 用户级,只影响当前用户,优先级更高 |
8.2 客户端常用指令
| 指令 | 作用 | 示例 |
|---|---|---|
Host | 匹配目标主机,后面跟的配置只对此主机生效 | Host old-server |
HostName | 实际连接的主机名/IP | HostName 10.0.0.1 |
User | 登录用户名 | User richard |
Port | 端口 | Port 2222 |
IdentityFile | 指定私钥文件 | IdentityFile ~/.ssh/id_ed25519 |
KexAlgorithms | 密钥交换算法 | KexAlgorithms +diffie-hellman-group1-sha1 |
HostKeyAlgorithms | 主机密钥算法 | HostKeyAlgorithms +ssh-rsa |
Ciphers | 加密算法 | Ciphers +aes128-cbc |
MACs | 消息认证码 | MACs +hmac-sha1 |
PubkeyAcceptedAlgorithms | 用户公钥认证算法 | PubkeyAcceptedAlgorithms +ssh-rsa |
StrictHostKeyChecking | 主机密钥检查策略 | StrictHostKeyChecking ask |
ServerAliveInterval | 心跳间隔(秒) | ServerAliveInterval 60 |
8.3 算法配置的前缀语法
配置算法列表时,前缀决定是追加还是替换:
| 前缀 | 含义 | 示例 |
|---|---|---|
| 无前缀 | 替换整个默认列表 | Ciphers aes128-ctr |
+ | 追加到默认列表末尾 | Ciphers +aes128-cbc |
- | 从默认列表中移除 | Ciphers -aes128-ctr |
^ | 把指定算法移到列表最前 | Ciphers ^aes128-ctr |
推荐用 +,因为这样不会破坏默认的安全列表,只是把旧算法补充进去。
8.4 服务端配置文件
| 文件 | 作用 |
|---|---|
/etc/ssh/sshd_config | 服务端主配置 |
/etc/ssh/sshd_config.d/*.conf | 额外配置片段(较新版本支持) |
8.5 服务端常用指令
| 指令 | 作用 |
|---|---|
Port | 监听端口 |
ListenAddress | 监听地址 |
HostKey | 指定主机私钥文件 |
KexAlgorithms | 允许的密钥交换算法 |
HostKeyAlgorithms | 允许的主机密钥算法 |
Ciphers | 允许的加密算法 |
MACs | 允许的 MAC 算法 |
PubkeyAuthentication | 是否启用公钥认证 |
PasswordAuthentication | 是否启用密码认证 |
PermitRootLogin | 是否允许 root 登录 |
AllowUsers / DenyUsers | 用户访问控制 |
8.6 查看服务端实际生效的配置
# 打印最终生效的完整配置
sudo sshd -T
# 只看算法相关
sudo sshd -T | grep -iE "kexalgorithms|hostkeyalgorithms|ciphers|macs"
# 检查配置文件语法
sudo sshd -t
sshd -T 会把所有 include、默认值合并后,输出真正生效的配置,比直接看 sshd_config 更准确。
九、ssh -vv 实际输出解读
9.1 为什么要看 -vv
ssh -Q 只告诉你”支持什么”,ssh -G 只告诉你”默认提案什么”。这条连接到底协商出了什么,只有 -vv 能看到。
9.2 一段真实的协商日志
ssh -vv user@10.0.0.1
输出中与算法协商相关的关键行(已简化):
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug2: local client KEXINIT proposal
debug2: KEX algorithms: sntrup761x25519-sha512@openssh.com,curve25519-sha256,...
debug2: host key algorithms: ssh-ed25519,ecdsa-sha2-nistp256,...
debug2: ciphers ctos: chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,...
debug2: ciphers stoc: chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,...
debug2: MACs ctos: umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,...
debug2: MACs stoc: umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,...
debug2: peer server KEXINIT proposal
debug2: KEX algorithms: curve25519-sha256,ecdh-sha2-nistp256,...
debug2: host key algorithms: rsa-sha2-512,rsa-sha2-256,ssh-ed25519,...
debug2: ciphers ctos: chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,...
debug2: MACs ctos: umac-128-etm@openssh.com,hmac-sha2-256-etm@openssh.com,...
debug1: kex: algorithm: curve25519-sha256
debug1: kex: host key algorithm: ssh-ed25519
debug1: kex: server->client cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none
debug1: kex: client->server cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none
9.3 逐段解读
第一段:local client KEXINIT proposal
这是你的客户端发出的提案列表。每一行对应一个维度:
KEX algorithms:客户端支持的密钥交换算法,按优先级排列host key algorithms:客户端接受的主机密钥算法ciphers ctos/stoc:客户端到服务器(ctos)、服务器到客户端(stoc)两个方向的加密算法MACs ctos/stoc:两个方向的消息认证码
第二段:peer server KEXINIT proposal
这是服务器发回的提案列表。格式同上。
第三段:kex: algorithm 等行
这是最终协商结果,最关键:
| 行 | 含义 |
|---|---|
kex: algorithm: curve25519-sha256 | 密钥交换算法最终选了 curve25519-sha256 |
kex: host key algorithm: ssh-ed25519 | 主机密钥算法最终选了 ssh-ed25519 |
kex: server->client cipher: chacha20-poly1305@openssh.com | 服务器到客户端方向用 chacha20-poly1305 |
kex: client->server cipher: chacha20-poly1305@openssh.com | 客户端到服务器方向用 chacha20-poly1305 |
MAC: <implicit> | 因为 chacha20-poly1305 是 AEAD,MAC 已内置,所以显示 <implicit> |
9.4 协商结果怎么定的
双方各自发出提案列表后,从客户端列表的第一项开始,逐个检查服务器是否也支持。第一个双方都支持的算法,就是最终结果。
所以:
- 客户端提案顺序决定优先级
- 服务器只需要”支持”即可,不需要排在前面
- 如果客户端首选
sntrup761x25519-sha512,但服务器不支持,就试下一个,直到找到共同支持的
9.5 常用过滤命令
# 只看最终协商结果
ssh -vv user@host 2>&1 | grep "kex:"
# 看双方提案
ssh -vv user@host 2>&1 | grep -E "KEXINIT proposal|KEX algorithms|host key algorithms"
# 看完整协商日志
ssh -vv user@host 2>&1 | less
十、SSH 证书(Certificate)
10.1 为什么需要证书
传统 authorized_keys 方式的问题是:每台服务器、每个用户,都要手动添加公钥。当服务器有几百台、用户有几千人时,管理成本极高。
SSH 证书用 CA(证书颁发机构) 解决这个问题:
- CA 生成一对密钥(CA 私钥 + CA 公钥)
- 用户把自己的公钥交给 CA,CA 用私钥签一张证书(公钥 + 身份信息 + 有效期 + 签名)
- 服务器配置信任 CA 公钥(
TrustedUserCAKeys) - 用户登录时出示证书,服务器验证证书由可信 CA 签发即可,不需要逐个添加用户公钥
10.2 证书类型
SSH 证书分两种:
| 类型 | 用途 | 签发对象 |
|---|---|---|
| 用户证书 | 用户登录服务器 | 用户的公钥 |
| 主机证书 | 服务器向客户端证明身份 | 服务器的主机公钥 |
10.3 用户证书签发流程
第一步:CA 生成密钥对(只做一次)
ssh-keygen -t ed25519 -f ~/.ssh/ca_user_key -C "User CA"
生成 ca_user_key(CA 私钥,必须严格保管)和 ca_user_key.pub(CA 公钥)。
第二步:用户生成自己的密钥对
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519
第三步:CA 用私钥给用户公钥签证书
ssh-keygen -s ~/.ssh/ca_user_key \
-I "richard@example.com" \
-n richard,ubuntu \
-V +52w \
~/.ssh/id_ed25519.pub
| 参数 | 含义 |
|---|---|
-s | CA 私钥 |
-I | 证书标识(Key ID),写谁申请的都行 |
-n | 允许登录的用户名列表(principal) |
-V | 有效期,+52w 表示 52 周 |
生成 ~/.ssh/id_ed25519-cert.pub,这就是证书。
第四步:服务器信任 CA
在服务器的 /etc/ssh/sshd_config 中:
TrustedUserCAKeys /etc/ssh/ca_user_key.pub
把 CA 公钥放到服务器上,重启 sshd。
第五步:用户登录
ssh richard@server
客户端自动使用 id_ed25519 + id_ed25519-cert.pub 完成认证,不需要在服务器上添加 authorized_keys。
10.4 主机证书签发流程
第一步:CA 生成主机 CA 密钥(只做一次)
ssh-keygen -t ed25519 -f ~/.ssh/ca_host_key -C "Host CA"
第二步:给服务器的主机公钥签证书
在服务器上:
sudo ssh-keygen -s ~/.ssh/ca_host_key \
-I "server.example.com" \
-h \
-n server.example.com,10.0.0.1 \
-V +52w \
/etc/ssh/ssh_host_ed25519_key.pub
| 参数 | 含义 |
|---|---|
-h | 表示签的是主机证书(不是用户证书) |
-n | 主机名/IP 列表 |
生成 /etc/ssh/ssh_host_ed25519_key-cert.pub。
第三步:客户端信任主机 CA
在客户端的 ~/.ssh/known_hosts 中添加:
@cert-authority *.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5...
@cert-authority 表示”这个 CA 签的主机证书我都信”。
第四步:连接
客户端连服务器时,服务器出示主机证书,客户端验证证书由可信 CA 签发,不再需要每次确认指纹。
10.5 证书 vs authorized_keys
| authorized_keys | SSH 证书 | |
|---|---|---|
| 信任方式 | 逐个添加公钥 | 信任 CA 即可 |
| 管理成本 | 用户多、服务器多时极高 | 低,只需管理 CA |
| 有效期 | 无,永久有效 | 可设有效期,自动过期 |
| 吊销 | 手动删除对应行 | 维护 KRL(Key Revocation List) |
| 适用规模 | 小规模 | 中大规模 |
10.6 查看证书内容
ssh-keygen -Lf ~/.ssh/id_ed25519-cert.pub
输出会显示证书的 Key ID、有效期、principal、签名等信息。
十一、Terrapin 攻击与 strict-kex
11.1 Terrapin 是什么
Terrapin(CVE-2023-48795)是 2023 年底公开的一种 SSH 协议层攻击。它不破解加密,而是通过操纵握手阶段的序列号,在特定条件下让攻击者能注入或删除加密流量中的部分消息。
11.2 攻击原理
SSH 的二进制数据包协议(Binary Packet Protocol)在握手阶段使用的某些模式(如 chacha20-poly1305 和 *-etm MAC)下,序列号参与完整性校验的方式存在弱点。
攻击者作为中间人,可以:
- 在握手阶段篡改扩展协商消息(如
SSH2_MSG_EXT_INFO)。 - 通过删除或注入特定消息,使双方对序列号的计数产生偏差。
- 在偏差被”吸收”后,攻击者可以注入或删除后续的加密消息。
关键限制:攻击者不能解密流量,只能在特定条件下注入或删除消息。
11.3 哪些算法受影响
| 类别 | 受影响 |
|---|---|
chacha20-poly1305@openssh.com | 是 |
*-etm@openssh.com MAC | 是 |
aes*-gcm@openssh.com | 是 |
非 ETM 的 hmac-* | 否 |
aes*-ctr + 非 ETM MAC | 否 |
11.4 strict-kex 缓解机制
OpenSSH 9.6 引入了 strict-kex(严格密钥交换)作为缓解措施。
原理:双方在握手阶段协商一个信号名 kex-strict-c-v00@openssh.com(客户端)和 kex-strict-s-v00@openssh.com(服务器)。如果双方都支持,就启用严格模式:
- 序列号在握手阶段不允许出现任何空隙
- 任何序列号不连续的情况都视为攻击,立即断开连接
- 攻击者无法在不被发现的情况下操纵序列号
11.5 怎么确认自己是否受保护
ssh -vv user@host 2>&1 | grep -i strict
如果输出中有:
debug1: kex: strict KEX enabled
或协商列表中包含 kex-strict-*,说明已启用 strict-kex。
11.6 建议
- 升级 OpenSSH 到 9.6 或更高版本,自动启用 strict-kex
- 如果暂时无法升级,在
~/.ssh/config中优先使用非 ETM 的 MAC(如hmac-sha2-256)配合aes*-ctr,可避开 Terrapin 攻击面 - 但这只是权宜之计,升级才是根本方案
十二、协商过程:一次典型的 SSH 握手
当你执行 ssh user@host 时,以下步骤依次发生:
第一步:协议版本交换
客户端和服务器互报支持的 SSH 协议版本。
第二步:算法协商
双方各自发送一个”提案”列表,包含自己支持的 KexAlgorithms、HostKeyAlgorithms、Ciphers、MACs。双方从对方的列表中各自选择最优的、自己也能接受的算法组合。
第三步:密钥交换
使用协商出的密钥交换算法,双方安全地生成一个共享的会话密钥。
第四步:主机密钥验证
服务器用主机私钥对交换过程中的某些数据签名,发送给客户端。客户端用协商出的主机密钥算法对应的公钥验证签名,确认服务器身份。
第五步:用户认证
在已加密且已验证服务器身份的信道上,进行用户身份认证(密码或用户密钥)。
第六步:会话建立
认证通过后,SSH 会话正式建立,后续所有通信都用协商出的加密算法保护。
如果任何一步的算法协商找不到共同支持的选项,连接就会失败,并显示 no matching ... found 错误。
十三、当协商失败时:配置与兼容
13.1 连接老旧服务器
新版 OpenSSH 默认禁用了一些不安全的旧算法。当你需要连接一台老服务器时,可能会遇到:
Unable to negotiate with 10.0.0.1: no matching key exchange method found.
Their offer: diffie-hellman-group1-sha1
解决方案是在 ~/.ssh/config 中为特定服务器启用旧算法:
Host old-server
HostName 10.0.0.1
KexAlgorithms +diffie-hellman-group1-sha1
HostKeyAlgorithms +ssh-rsa
Ciphers +aes128-cbc
MACs +hmac-sha1
+ 前缀表示追加到默认列表,而不是替换。这样当服务器升级支持新算法后,会自动使用更优的选项。
13.2 安全提醒
启用旧算法会降低连接安全性。这只是权宜之计,根本方案是升级服务器端的 SSH 服务。
十四、总结
14.1 四个维度速查
| 维度 | 查询命令 | 默认启用首选 |
|---|---|---|
| 密钥交换 | ssh -Q kex | sntrup761x25519-sha512、curve25519-sha256 |
| 主机/用户密钥 | ssh -Q key | ssh-ed25519、ecdsa-sha2-nistp256 |
| 加密 | ssh -Q cipher | chacha20-poly1305、aes256-gcm |
| 消息认证 | ssh -Q mac | hmac-sha2-256-etm、umac-128-etm |
14.2 核心结论
ssh -Q 列出的是”这个二进制里有什么”,不是”默认用什么”。默认启用列表是它的子集。旧算法编译进去是为了兼容性,默认关闭是为了安全性。
三种查询方式各司其职:
| 命令 | 回答的问题 |
|---|---|
ssh -Q kex | 客户端支持哪些算法 |
ssh -G host | 客户端默认优先提出哪些算法 |
ssh -vv host | 这条连接实际协商出什么算法 |
这三者结合,才能完整回答”我的 SSH 连接到底用了什么算法保护”这个问题。
14.3 安全性排序总览
| 维度 | 首选 | 可用 | 已弃用 |
|---|---|---|---|
| 密钥交换 | sntrup761x25519-sha512、curve25519-sha256 | ecdh-sha2-*、dh-group16/18-sha512 | dh-group1-sha1、dh-group14-sha1 |
| 主机密钥 | ssh-ed25519 | ecdsa-sha2-*、rsa-sha2-512/256 | ssh-rsa、ssh-dss |
| 加密 | chacha20-poly1305、aes256-gcm | aes*-ctr | aes*-cbc、3des-cbc |
| 消息认证 | hmac-sha2-*-etm、umac-*-etm | hmac-sha2-*、umac-* | hmac-md5*、hmac-sha1* |
14.4 相关工具速查
| 命令 | 用途 |
|---|---|
ssh -Q kex/key/cipher/mac | 查看客户端支持的算法 |
ssh -G host | 查看客户端默认优先提出的算法 |
ssh -vv host | 查看实际协商结果 |
sshd -T | 查看服务端生效配置 |
ssh-keygen -R host | 删除 known_hosts 中某主机的旧记录 |
ssh-keygen -F host | 查询 known_hosts 中某主机的记录 |
ssh-keyscan host | 从服务器抓取主机公钥 |
ssh-keygen -lf key.pub | 查看密钥指纹 |
ssh-keygen -Lf cert.pub | 查看证书内容 |