如果你在连服务器、连交换机、连家里路由器时,看到过这样一行报错:
Unable to negotiate with 192.168.1.10 port 22: no matching cipher found. Their offer: aes256-cbc,aes128-cbc,3des-cbc,des-cbc第一反应是不是“SSH 密钥没配对好”?我接过不少这样的工单,至少一半人把这个报错当成密钥问题处理——重新生成密钥、反复改 authorized_keys、折腾公钥权限,结果毫无变化。其实这个报错跟密钥一毛钱关系都没有,它发生在 SSH 连接流程里的“算法协商”阶段,简单说就是:客户端和服务器各自亮出支持的加密算法清单,结果两边没找到交集,握手直接失败。
这篇文章就把这个报错彻底讲透,从报错原理、为什么新版客户端不认老算法,到临时绕过和服务器端根治,再附上我实际踩过的一堆坑。适合运维、嵌入式开发、经常连网络设备和老系统的朋友,也覆盖 Windows 下用 SecureCRT、Bitvise、VS Code Remote SSH 连服务器的场景。
1. 一分钟看懂“no matching cipher found”到底在说什么
1.1 SSH 连接的“谈条件”阶段:算法协商是怎么发生的
SSH 连接并不是建立 TCP 连接后马上输入密码,它的完整流程大致是:
- TCP 三次握手,建立网络层连接。
- 版本协商:双方确认都使用 SSH-2.0 协议。
- 算法协商:确定密钥交换算法、主机密钥算法、对称加密算法(cipher)、MAC 算法。
- 密钥交换:生成会话密钥。
- 客户端认证:密码、公钥或键盘交互。
- 进入加密会话,开始传输数据。
我们遇到的这个报错,卡在第 3 步和第 4 步之间。SSH 是典型的“先谈条件再干活”协议,客户端和服务器各自维护一张支持列表,然后找出双方都认可的交集。让这个交集里的算法真正生效后,后续的身份认证才有机会发生。
如果最终交集为空,协议就中断。服务器说“我能提供:aes256-cbc、aes128-cbc、3des-cbc、des-cbc”,客户端说“我默认只提供:aes128-ctr、aes256-ctr、aes128-gcm、aes256-gcm、chacha20-poly1305@openssh.com”,两边反复核对后一个共同项都没有,于是报出 no matching cipher found。
打个比方:两个人约饭,一个说只吃川菜,另一个说只吃粤菜,交集为零,这顿饭约不成。你要做的,就是让双方菜单里出现至少一个重合项,并且能被对方接受。
1.2 服务器提供的 aes256-cbc、3des-cbc、des-cbc 到底是什么
先拆解算法名称。以 aes256-cbc 为例:
- AES:加密算法本身,全称 Advanced Encryption Standard。
- 256:密钥长度 256 位。
- CBC:分组密码的工作模式,全称 Cipher Block Chaining。
CTR、GCM 也是工作模式。CBC 曾经非常流行,但今天的新版 SSH 客户端默认几乎都不再启用。下面是一张常见算法对照表:
| 算法 | 加密强度 | 工作模式 | 现状 |
|---|---|---|---|
| aes256-cbc | 256 位 | CBC | 老系统常用,新客户端默认禁用 |
| aes128-cbc | 128 位 | CBC | 老系统常用,新客户端默认禁用 |
| 3des-cbc | 112 位有效强度 | CBC | OpenSSH 已逐步默认移除 |
| des-cbc | 56 位 | CBC | 极弱,不建议使用 |
| aes128-ctr / aes256-ctr | 128/256 位 | CTR | 现代默认算法 |
| aes128-gcm / aes256-gcm | 128/256 位 | GCM | 现代默认算法 |
| chacha20-poly1305@openssh.com | 256 位 | AEAD | OpenSSH 默认算法之一 |
看到服务器报出 des-cbc 这种算法,基本可以判断对方 SSH 服务配置非常老,或者是某个嵌入式设备用了很精简的 SSH 实现。后面我会讲怎么处理。
这里还要强调一点:报错里的 Their offer 是服务器在协商消息里主动发来的、它自己支持的 cipher 列表。看到这个列表,你就能大致判断服务器端 SSH 是老配置还是新配置。它不是乱码,而是解决问题的关键线索。
1.3 这个报错跟“SSH 密钥”其实没有直接关系
标题里写“SSH 密钥未发现”,我猜是搜索时的直觉联想,但我必须把概念掰清楚:这个报错发生在身份认证之前。
SSH 的登录顺序是“先加密握手、再身份认证”。公钥、私钥、口令都属于身份认证环节,位置在算法协商之后。no matching cipher found 在握手阶段就直接中断了,后面的密钥认证根本轮不到执行。
想看证据,用ssh -vvv user@host跑一次,输出里会有debug1: kex: algorithm ...以及后续的debug1: Authentications that can continue: ...。如果日志还没走到 Authentications 就断了,说明问题在协商层,不在密钥。
如果目的是“每次 SSH 连接不用输密码”,那确实需要配置密钥免密登录,那是另一个问题,我在最后一章会专门写。
2. 为什么新版客户端默认不认这些老算法
2.1 CBC 模式哪里不安全:从 DES 到 Padding Oracle
先别急着骂新版 OpenSSH 矫情。CBC 模式确实有它的历史问题。
CBC 加密时,每个明文分组会先和上一个密文分组做异或,然后再加密。第一个分组使用初始化向量 IV。这种链式结构导致密文分组之间存在关联性,攻击者可以基于这种关联性做一些操作。
2008 年前后有一系列针对 SSH CBC 模式的明文恢复攻击研究,业内常提的 CVE-2008-5161 就是一个典型。攻击者可以逐字节恢复加密会话中的明文。虽然在真实网络环境里利用条件比较苛刻,但安全审计工具只要扫到 CBC 算法,通常都会标红。
所以 OpenSSH 的决定很明确:默认列表逐渐排除 CBC 系列,全面转向 CTR 和 GCM。CTR 模式将计数器加密后与明文异或,没有 CBC 那种密文分组链式依赖;GCM 更是同时提供了加密和完整性校验,一步到位。包括 ChaCha20-Poly1305,也是流密码加认证的优秀组合。
2.2 OpenSSH 默认算法列表的变化血泪史
我列一下大致的版本变化,你对比一下就明白为什么“同一个老服务器,老系统能连,新客户端连不上”。
- OpenSSH 5.x、6.x 时代:默认 ciphers 里包含 aes128-cbc、aes256-cbc、3des-cbc 等。所以那个年代连老设备基本没有这个报错。
- OpenSSH 7.x:开始动手清理弱算法,先后移除了 blowfish-cbc、cast128-cbc 等小众 CBC 算法。我记得 7.6 的公告里 3des-cbc 被标记为弃用,后续版本逐步从默认列表里拿掉。
- OpenSSH 8.x、9.x:默认 ciphers 已经是 aes128-ctr、aes256-ctr、aes128-gcm@openssh.com、aes256-gcm@openssh.com、chacha20-poly1305@openssh.com 这些,CBC 一个都不在默认列表里。
想看当前客户端支持哪些 cipher,执行:
ssh -Q cipher输出里如果有 aes256-cbc,说明你的客户端编译时保留了它,只是不在默认列表里。有的发行版编译 OpenSSH 时干脆把 des-cbc 这类算法裁掉了,那就算你在命令行指定也没用。
Windows 上如果你用的是系统自带 OpenSSH,同样可以用ssh -Q cipher查。SecureCRT、Xshell、Bitvise SSH Client 这类图形工具一般自带算法集,默认勾选范围比 OpenSSH 宽,所以反而没那么容易报这个错——这在后面场景里会提到。
2.3 最容易踩中这个报错的三类场景
场景一:老 Linux 服务器或长期不更新的内网机器。
CentOS 5、CentOS 6、Ubuntu 10.04、Debian 6 这类老发行版,自带 OpenSSH 版本低,sshd_config 里默认就带着 CBC 系列。很多内网服务器几年不升级,一换新笔记本去连,立刻暴雷。升级系统内的 OpenSSH 是最佳方案,但不少老系统升不动,只能靠客户端兼容。
场景二:嵌入式设备、网络设备、路由器。
这个更常见。很多交换机、路由器、工业设备内置的是精简版 Dropbear 或者某个商业 SSH 实现,支持的 cipher 就那三五个,而且基本都是 CBC。我见过某品牌的接入交换机,offer 列表只有 aes256-cbc 和 aes128-cbc,连 3des 都没有。热词里的小米 AX3600 开启 SSH 后,某些固件也会遇到类似的协商问题,处理思路完全一样。
场景三:Windows 工具链和虚拟化环境。
Windows 10/11 自带的 OpenSSH 版本通常比较新,默认不启用 CBC。用 VS Code Remote SSH、Vagrant、Git 里的 ssh 命令连接老虚拟机镜像时,非常容易碰到这个报错。Bitvise SSH Server 作为 Windows 上的 SSH 服务端,默认算法列表相对宽,但如果客户端太新而服务端配置被改成老算法,同样会出现。SecureCRT 相对温和,因为它默认勾选了大量算法,反而几乎没有这个烦恼。
一句话总结:报错不是你的密钥出了问题,而是“服务器太老、客户端太新”,两边菜单没有交集。
3. 最快解决:客户端侧指定兼容算法
如果你的目标只是“能连上”,最快的方法是在客户端侧加上老算法。这个方案不动服务器,风险最小,适合设备不在你手里、或者设备太老改不动的情况。
3.1 一次性临时指定:ssh -c 参数
最直接的命令是:
ssh -c aes256-cbc user@192.168.1.10-c全称是 cipher,后面跟算法名。只要服务器 offer 列表里有 aes256-cbc,这条命令就能连上。
如果服务器头部 offer 里同时存在多个算法,你还可以一次指定多个:
ssh -c aes256-cbc,aes128-cbc,3des-cbc user@192.168.1.10注意:这种写法是“完全覆盖”客户端的 cipher 列表,表示“我只看这几个算法”。连接老设备没问题,但连接新服务器时反而可能失败,因为新服务器可能不提供这些 CBC 算法。所以临时排查可以用,长期使用不建议。
更稳妥的写法是使用+号追加算法:
ssh -c +aes256-cbc user@192.168.1.10+号的含义是“在客户端默认算法列表的基础上,额外追加 aes256-cbc”。这样连接老设备时能协商成功,连接新设备时依然优先使用 CTR/GCM 等强算法。这个用法在 OpenSSH 7.0 以后都支持。
如果服务器太老,可能同时存在 KEX 算法、主机密钥算法也匹配不上的问题。这时候一个命令全带上:
ssh -c +aes256-cbc \ -oKexAlgorithms=+diffie-hellman-group1-sha1 \ -oHostKeyAlgorithms=+ssh-rsa \ -oMACs=+hmac-sha1 \ user@192.168.1.10-o参数可以直接覆盖 ssh_config 里的配置项。为什么老设备经常要带 diffie-hellman-group1-sha1?因为这个是早期的密钥交换算法,新版 OpenSSH 默认把它移除了。为什么带 ssh-rsa?OpenSSH 8.8 之后默认禁用了基于 SHA-1 的 ssh-rsa 主机密钥签名,而很多老设备的主机密钥只有 ssh-rsa 这一种。
3.2 长期方案:~/.ssh/config 分主机配置
临时命令适合救急,但如果这台设备你要经常连,建议写到~/.ssh/config里。以 Linux/macOS 为例,配置文件不存在就创建一个:
vim ~/.ssh/config写入:
Host old-switch HostName 192.168.1.10 User admin Port 22 Ciphers +aes256-cbc KexAlgorithms +diffie-hellman-group1-sha1 HostKeyAlgorithms +ssh-rsa MACs +hmac-sha1保存后直接:
ssh old-switch从此连这台老设备会自动使用追加后的算法列表,而连接其他正常服务器时完全不受影响。这种“按 Host 分块管理”的方式,解决的不只是 cipher 问题,也是管理多个不同环境服务器时的统一思路——不同主机、不同用户、不同端口、不同密钥文件,全写在 config 里,一劳永逸。
在 Windows 上也一样,文件路径通常是C:\Users\你的用户名\.ssh\config。如果你用的是 Git Bash 或 Cygwin,要把这个文件放到对应环境解析的 home 目录下;VS Code Remote SSH 默认读的也是~/.ssh/config,改完后重载窗口即可生效。
scp、rsync、git、sftp这些工具底层都走 ssh 命令,所以配置了 config 文件后,它们也会自动沿用这套算法配置。
3.3 客户端指定算法时的三个注意点
注意点一:+号别乱加也别不加。
Ciphers +aes256-cbc表示追加,Ciphers aes256-cbc表示只使用这一个。我见过有人把配置文件写成Ciphers aes256-cbc,结果连其他正常服务器时发现 HTTPS 风格的新算法全没了,差点以为服务器坏了。记住,日常通用做法是追加,不是覆盖。
注意点二:命令行的优先级高于配置文件。
如果你在命令行写了-c aes256-cbc,那这一步会覆盖 config 里已有的 Ciphers 配置。反过来,命令行里没写任何 cipher 参数时,才会读 config。这容易排查,但也容易踩:你明明在 config 里写了配置,结果命令行带了某个-o参数,覆盖掉了一部分,导致以为配置没生效。
注意点三:验证配置是否生效用 ssh -G。
ssh -G是 OpenSSH 7.2 以后提供的一个非常实用的参数,它不做实际连接,只解析最终生效配置。比如:
ssh -G old-switch | grep cipher能看到最终 Ciphers 是什么,非常方便。Windows 系统 OpenSSH 也支持这个参数。
4. 根治方案:从服务器端启用所需 Cipher
如果服务器在你手里,最干净的做法是在服务器端把客户端需要的算法加进 sshd_config。这样任何客户端都能正常连接,不用每台电脑都去改 config。
4.1 修改 sshd_config 的操作步骤
登录服务器,编辑 SSH 服务端配置文件:
sudo vim /etc/ssh/sshd_config在文件末尾追加:
Ciphers +aes256-cbc,aes128-cbc,3des-cbc注意,OpenSSH 7.0 以后的服务端也支持+语法,意思是“在默认算法的基础上追加”。如果你希望严格限制为“只允许这些算法”,可以去掉+号:
Ciphers aes256-cbc,aes128-cbc,3des-cbc但我会劝你别这么干。去掉+号意味着所有现代算法全被禁用,新客户端连过来只能被迫用老算法,反而降低了整台服务器的安全基线。保留默认算法、追加必要的 CBC 算法,是最小改动。
改完后先检查配置语法:
sudo sshd -t看到没输出错误后再重启:
sudo systemctl restart sshd重启 SSH 服务之前,建议你先开一个额外的连接窗口,免得 restart 失败后把自己锁在外面。这在远程操作服务器时是基本素养,真出过太多惨案了。
如果你用的是 Bitvise SSH Server 这类 Windows 上的 SSH 服务端,操作路径通常在它的控制面板里:Server settings -> Encryption,勾选需要的 cipher 即可。Bitvise 默认算法列表比较宽,一般不需要改。
4.2 CentOS/RHEL 系系统里的 Crypto Policy 大坑
如果你在 RHEL 8、CentOS 8/9、RockyLinux、AlmaLinux 这些系统上,按上面的方法改完 sshd_config,重启后发现根本没生效——别怀疑自己写错,很可能是系统级 Crypto Policy 把你的配置覆盖了。
RHEL 系从 8 开始引入了crypto-policies机制,它会自动生成 OpenSSH 的算法约束文件,通常在/etc/crypto-policies/back-ends/openssh.config。sshd_config 里写的 Ciphers 会被这个文件限制,即使你写了,最终生效列表也会被策略过滤。
先看当前策略:
update-crypto-policies --show默认一般是DEFAULT。如果要兼容老算法,最简单粗暴的办法是把策略改为 LEGACY:
sudo update-crypto-policies --set LEGACY改完后重启 sshd,再验证。注意:LEGACY 策略会放宽整个系统的加密算法限制,影响面不止 SSH,还包括 TLS、IPSec 等。如果你不想全局放宽,可以只修改/etc/crypto-policies/back-ends/openssh.config,手动在对应行追加算法,再执行update-crypto-policies刷新。
这个坑在国产化 Linux 上也常见。热词里提到的“银河麒麟 ssh 升级”后连不上老设备,很多时候就是升级后算法策略收紧,加上 Crypto Policy 覆盖导致 sshd_config 里的配置“不生效”。
4.3 安全权衡:只开必要算法,别照抄全列表
服务器端启用 CBC 算法要克制。
如果你是安全负责人,或者这台服务器会被安全审计扫描,开启 CBC 算法大概率会被报“SSH CBC Mode Ciphers Enabled”之类的风险项。所以我的建议是:
- 不要开 des-cbc。56 位密钥强度,真不是闹着玩的,现代机器暴力破解它并不难。
- 3des-cbc 能不开就不开。虽然比 des 强,但 112 位有效强度已经落后,OpenSSH 官方都弃用了。
- aes256-cbc 是这四个里相对最能接受的,如果只是连一台老设备,只加这一个通常足够。
核心原则:能用客户端解决的问题,尽量不动服务器全局配置;必须在服务器端开,就开最小集。少数老设备需要兼容,就在客户端 config 里为它们单独追加,不要污染全局默认。
5. 常见问题与排查技巧实录
5.1 指定了 cipher 还是报错:三个“+”号一个都不能少
很多人改了 cipher 后,发现报错换了个样子,比如:
no matching key exchange method found. Their offer: diffie-hellman-group1-sha1或者:
no matching mac found. Their offer: hmac-sha1这说明 cipher 已经协商成功了,但算法协商是多阶段的。KEX 算法、主机密钥算法、MAC 算法各自都要找到交集。老设备往往所有环节都停留在“老年代”,你需要一次性把对应的算法都追加进去。
我在配置老设备时习惯一条龙写全:
ssh -c +aes256-cbc \ -oKexAlgorithms=+diffie-hellman-group1-sha1 \ -oHostKeyAlgorithms=+ssh-rsa \ -oMACs=+hmac-sha1 \ user@192.168.1.10建议直接用ssh -vvv看详细日志,它会逐步告诉你卡在哪个协商环节,再对症下药。想更省事,就把这些写进 config 的对应 Host 块里,一次配置长期有效。
这里插一句,热词里有人问“ssh 命令执行过程中退出,命令还会继续么”。这是一个很经典的终端问题:普通 ssh 会话断开后,远端进程会收到 SIGHUP 信号被杀掉。解决办法是用 tmux 或 screen,或者 nohup 包一层。这个和今天的 cipher 问题同属于 SSH 连接管理里的高频坑,但不是同一个问题,别搞混。
5.2 配置没生效的几个原因
我按出现频率排一下:
~/.ssh/config 权限过宽。OpenSSH 对配置文件权限很敏感,如果文件权限是 644 可能被忽略。修复:
chmod 600 ~/.ssh/config。Host 匹配没对上。你访问的是
ssh old-switch,但 config 里写的是Host 192.168.1.10,如果别名不一致,配置块就不会生效。建议写Host old-switch,并通过HostName指定真实 IP。命令行参数覆盖了配置文件。前面说过,命令行的优先级更高。如果命令行里显式写了其它 cipher 或
-o参数,config 里对应项就失效了。sshd_config 改了没重启。这个很常见,改完一定要
sshd -t检查,然后systemctl restart sshd。Crypto Policy 覆盖。RHEL 系系统记得检查
/etc/crypto-policies/back-ends/openssh.config,具体见 4.2。误改了客户端的 ssh_config 而不是 sshd_config。两个文件路径容易混:客户端是
/etc/ssh/ssh_config(没有 d),服务端是/etc/ssh/sshd_config(有 d)。改错文件等于白改。
5.3 高频报错与问题速查表
| 问题 / 报错 | 可能原因 | 解决方向 |
|---|---|---|
| no matching cipher found | cipher 协商无交集 | 客户端或服务端追加对应 cipher |
| no matching key exchange method | KEX 算法无交集 | KexAlgorithms=+diffie-hellman-group1-sha1 |
| no matching mac found | MAC 算法无交集 | MACs=+hmac-sha1 |
| no matching host key type | 主机密钥算法无交集 | HostKeyAlgorithms=+ssh-rsa |
| Permission denied (publickey) | 公钥未配置或文件权限错误 | 见 5.4 密钥配置 |
| 此扩展在此工作区中被禁用 | VS Code Remote-SSH 工作区扩展标记问题 | 检查 .vscode/extensions.json,重新加载窗口 |
| ssh 断开后远端命令终止 | 终端会话收到 SIGHUP | 用 tmux / screen / nohup |
| vagrant ssh 卡住或算法报错 | 虚拟机镜像较老 | 在 ~/.ssh/config 中为对应 Host 追加算法,并用 IdentityFile 指定 vagrant 私钥 |
关于热词里那个 VS Code 提示“此扩展在此工作区中被禁用,因为其被定义为在远程扩展主机中运行”,它其实和 SSH 算法无关,更多是扩展配置问题。出现时先别怀疑连接坏了,检查.vscode/extensions.json里是否对该扩展做了限制,或者在扩展管理中把它安装到远程主机侧,再重载窗口。VS Code Remote-SSH 这个工具本身很好用,但对扩展环境的理解门槛不低。
5.4 顺手解决:真正配置 SSH 密钥免密登录的方法
因为太多人把 cipher 报错误当成“SSH 密钥”问题,我干脆把密钥免密登录的正规配置写出来。这个配置完成后,otty、FinalShell、Xshell、VS Code 等工具都能直接免密登录。
第一步,生成密钥对。推荐 Ed25519:
ssh-keygen -t ed25519 -C "your_comment"如果你的目标设备比较老,不支持 Ed25519,那就用 RSA:
ssh-keygen -t rsa -b 4096 -C "your_comment"第二步,把公钥放到服务器的 authorized_keys 里。最简单的方式:
ssh-copy-id user@serverWindows 没有 ssh-copy-id 时,可以手动把~/.ssh/id_ed25519.pub的内容追加到服务器的~/.ssh/authorized_keys:
cat id_ed25519.pub | ssh user@server "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"第三步,检查权限。这一步最容易出问题:
chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys chmod go-w ~如果服务器开了 SELinux,可能还需要恢复 ssh 相关上下文:
restorecon -R -v ~/.sshgit 配置 gitee 密钥、gitlab 配置 ssh 密钥,也都是这个流程。多个平台需要不同密钥时,用~/.ssh/config分块指定IdentityFile即可。有多个服务器时,给每个 Host 指定对应私钥,避免密钥串场。
最后分享一点小体会
我自己处理过好几次这类“no matching cipher found”的工单,最深的感觉是:这个报错本身不难,难的是判断“到底哪里还能动”。老设备不支持升级,只能客户端让一让;服务器在自己手里,就尽量在服务端做最小改动。最怕的是有人在服务器上放开全部弱算法,连 des-cbc 都开,最后被安全审计一查一个准。
我个人现在遇到老设备,首选就是在~/.ssh/config里给对应 Host 单独加算法块,追加aes256-cbc和老的 KEX、MAC,别的不动。这样新系统继续用强算法,老设备也能连,两边都不耽误。如果你要连的设备很多,建议把 config 文件用脚本统一管理,分发给各个办公电脑,省得每台机器都手工改一遍。
这个思路不仅适用 SSH 的 cipher 问题,也适用于整个运维里的老系统兼容问题:能升级就升级,不能升级就最小范围兼容,别为了图省事把安全底线一起降了。