什么是 SSH?工作原理、端口与密钥登录
SSH 是连接远程 Linux 服务器的加密通道。本文解释客户端与服务端、22 端口、主机密钥指纹,以及密钥登录与密码登录的区别。
什么是 SSH?
SSH(Secure Shell,安全外壳协议)是一种协议,用于通过加密连接登录其他位置的计算机并在其上运行命令。您输入的内容会发送到远程计算机,远程计算机的输出会返回。网络中途的监听者无法读取其中任何内容。租用的 Linux 服务器没有连接显示器和键盘,因此 SSH 是使用这台服务器的基本方式。
这个名称包含两层含义。SSH 是协议,由 RFC 4251 至 RFC 4254 进行定义。OpenSSH 是实现该协议的程序,几乎所有 Linux 服务器和笔记本电脑实际运行的都是它。当有人说“通过 SSH 登录服务器”时,指的是其计算机上的客户端程序 ssh 与另一端服务器上的服务端程序 sshd 通信。
SSH 要取代的问题
远程登录早于 SSH。Telnet 会向 23 端口建立明文 TCP 连接,并按输入原样发送每个字节。任何内容都未加密,密码也不例外。能够看到网络流量的任何人都可以读取这些内容,例如同一办公网络中的人员,或链路上任意路由器的运营者。rlogin 系列也存在同样的弱点,而且它按名称信任客户端计算机。这意味着它信任网络所声称的名称。
1995 年,赫尔辛基工业大学的网络遭到密码嗅探攻击后,Tatu Ylönen 编写了第一个 SSH。该设计保留了 Telnet 的实用部分,即终端与远程 shell 之间的字节流,同时增加了 Telnet 无法解决的两个问题:加密字节流,以及证明连接另一端的服务器确实是您要访问的服务器。
第二部分很容易被忽略,但它是 SSH 的核心组成部分之一。仅靠加密无法保护您。中间的计算机可以接受您的连接,完美地加密通信,读取您发送的所有内容,再将其转发给真实服务器。SSH 为每台服务器提供永久身份,称为主机密钥,并在每次连接时检查该密钥,从而阻止这种攻击。
客户端与服务器模型的工作方式
这里有两个程序。服务器上,sshd 始终运行并等待连接。在您的计算机上,ssh 发起连接。它们是相互独立的程序,也使用不同的配置文件。混淆这两个程序,是修改配置后未生效的最常见原因。
- 服务器读取
/etc/ssh/sshd_config。密码登录在此处禁用,监听端口也在此处设置。 - 客户端先读取系统默认配置
/etc/ssh/ssh_config,然后读取用于设置各主机参数的~/.ssh/config。
在 Debian 和 Ubuntu 上,服务单元名为 ssh。在 RHEL、Rocky 和 Fedora 上,服务单元名为 sshd。新版 Ubuntu 通常通过 socket 激活方式安装该服务。因此,即使计算机完全可以访问,systemctl status ssh 也可能报告 inactive (dead),因为实际负责监听的是 ssh.socket,它会按需启动服务。
客户端不一定是 OpenSSH。Windows 上的 PuTTY、手机上的 Termius,以及编辑器内置的远程支持功能,都可以通过相同的协议连接到同一个 sshd。Windows 10 和 11 也包含 OpenSSH 客户端,因此无需安装任何软件即可在 PowerShell 中使用 ssh you@server。
为什么 SSH 使用 22 端口?
端口是一个数字,用于告诉内核某个传入连接属于哪个监听程序。Linux 中的端口对所有服务的工作方式都相同。SSH 使用 22 端口,是因为 IANA 在 1995 年为其分配了这个端口。Ylönen 申请了一个空闲端口号。这个端口号紧邻 SSH 旨在替代的协议:21 是 FTP,23 是 telnet,22 当时未被使用。
由于 22 是默认端口,所有组件都会默认使用它。Git 远程仓库、备份脚本和服务商的控制面板都会先尝试 22 端口。互联网中的自动化扫描器也会这样做。启用密码登录的新服务器在启动后几分钟内,就会在 /var/log/auth.log 中持续出现类似以下内容的日志:
Failed password for invalid user admin from 203.0.113.55 port 43122 ssh2这类流量会持续产生,并不是专门针对您的服务器。将 sshd 更改为 2222 端口可以消除大多数这类日志,因为扫描器是在整个互联网范围内扫描 22 端口,而不是专门研究您的服务器。对于真正查看该服务器的人来说,这不会增加入侵难度。更改端口只能减少噪声,不要赋予它其他安全作用。
您甚至无需登录,就可以查看服务器的响应:
nc 203.0.113.10 22在 Ubuntu 24.04 上,该命令会输出与 SSH-2.0-OpenSSH_9.6p1 Ubuntu-3ubuntu13 接近的内容。横幅信息会以明文发送,因为此时尚未建立加密连接,双方需要先通过它协商协议版本。按 Ctrl+C 关闭连接。
建立连接时网络上传输了什么
以下序列描述了一个 ssh you@server 在您看到提示符之前执行的操作。
- 客户端将主机名解析为 IP 地址,然后向端口 22 建立 TCP 连接。
- 两端以明文发送版本标识。
- 两端发送各自支持的算法列表:密钥交换、加密、消息认证和压缩。此时仍是明文。双方都支持的最强选项会被选中。
- 执行密钥交换。当前版本的 OpenSSH 优先使用
curve25519-sha256。交换结束后,两端都会持有相同的共享密钥,但该密钥从未在网络上传输。因此,即使有人记录了完整通信内容,事后也无法计算出该密钥。 - 服务器使用其主机私钥为交换结果签名。客户端根据本地保存的主机公钥验证该签名。这一步可以阻止中间设备冒充您的服务器。
- 开始加密。当前版本的 OpenSSH 默认使用
chacha20-poly1305@openssh.com加密算法。 - 现在客户端才会对您进行身份验证,可以使用密码或密钥。您的用户名和密码会在加密通道内传输。
- 客户端打开一个通道,并请求启动 shell。
上述顺序就是 SSH 与 telnet 的全部区别。身份验证发生在通道加密且服务器证明自身身份之后。因此,密码不会有任何时刻以明文形式出现在网络上传输。
监控网络的人仍然可以获知一些信息。他们能看到您的 IP 地址、服务器的 IP 地址、端口 22、两端的明文版本标识,以及每个数据包的时间和大致大小。他们看不到您的用户名、密码、命令或命令输出。第 1 步中的主机名解析不属于 SSH,通常也不是私密操作,因此解析服务器名称的 DNS 查询可能会暴露您即将连接的机器,即使会话本身始终处于加密保护之下。
主机密钥,以及首次连接时的指纹提示
安装 openssh-server 后,它会为该计算机生成主机密钥对,并将其写入 /etc/ssh/,例如 ssh_host_ed25519_key 和 ssh_host_ed25519_key.pub。私钥始终保留在服务器上。公钥用于标识服务器,步骤 5 中的签名正是通过它进行验证的。
首次连接新服务器时,客户端没有可供比对的密钥,因此会提示您:
The authenticity of host '203.0.113.10 (203.0.113.10)' can't be established.
ED25519 key fingerprint is SHA256:E9nVQ5Sm2oQ3nGm5Zf1tOaU7Xh0k2p8bWc4dLrTvYxA.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])?指纹是主机公钥的 SHA256 哈希值,以 base64 格式输出,因此长度适中,便于人工比对。输入 yes 后,该密钥会写入您自己计算机上的 ~/.ssh/known_hosts。此后每次连接同一地址时,客户端都会将服务器提供的密钥与已存储的密钥进行比对。如果两者匹配,客户端不会输出任何内容,并直接进入提示符。
这种机制称为首次使用时信任(trust on first use)。需要明确的是,它有一个安全限制:首次连接时,您接受的是一个此前从未见过的密钥,因此此时缺少验证保护。要消除这一风险,应通过其他渠道获取指纹并进行比对。大多数服务提供商会在 Web 控制台显示的启动输出中打印指纹;您也可以直接在服务器上输出:
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub该命令会输出与提示中显示的 SHA256: 字符串相同的内容。提示中的 [fingerprint] 选项正是用于此目的:粘贴您预期的指纹,只有在它与服务器提供的指纹匹配时,客户端才会继续。
在 Debian 和 Ubuntu 上,known_hosts 默认会进行哈希处理,因此文件中的行会以 |1| 开头,而不是显示可读的主机名。运行 ssh-keygen -F 203.0.113.10 可查找某台主机对应的条目。
为什么 SSH 提示主机密钥已更改?
迟早你会遇到下面这段很长的提示:
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!提示最后显示 Host key verification failed.,客户端会拒绝连接。它还会显示 Password authentication is disabled to avoid man-in-the-middle attacks.,因为此检查正是为了防止你将密码输入到未知计算机中。
这条消息看起来像是紧急故障,但大多数情况下并不是。常见原因如下:
- 你重建或重新安装了服务器,因此
sshd在首次启动时生成了新的主机密钥。这是最常见的原因。 - 你销毁了一台 VPS,又创建了另一台,而服务商将原来的 IP 地址分配给了新机器。
- 你通过转发或负载均衡器连接,而它现在将请求转发到了另一台后端机器。
- 确实有其他设备正在拦截连接。
在清除任何内容前,先确定具体原因。如果你在十分钟前重新安装了机器,原因很明显。如果你这边没有任何变化,请停止操作并调查,因为此警告说明检查机制正在正常工作。确认原因后,删除过期条目,然后重新连接:
ssh-keygen -R 203.0.113.10下次连接时会再次显示指纹提示。你可以借此机会将指纹与服务商控制台中的指纹进行比较。
密码登录与密钥登录
密码身份验证会在已加密的通道中发送您的密码,sshd 通常通过 PAM(可插拔身份验证模块)将其与账户数据库进行比对。它无需任何准备,因此服务提供商可以只提供 root 密码,就交付一台新服务器。
弱点不在于加密,而在于密码是较短的机密信息。您每次登录都要将它发送到服务器,而端口 22 全天候受到自动化程序的猜测,这些程序不会疲倦。
公钥身份验证的工作方式不同。您在自己的计算机上创建密钥对。将公钥放入服务器上您账户中的 ~/.ssh/authorized_keys。私钥保留在您的笔记本电脑上,绝不会传输。登录时,客户端会对一段数据进行签名,这段数据包含密钥交换过程中的会话标识符;服务器使用已保存的公钥验证该签名。由于签名数据与当前会话绑定,截获的签名无法用于其他会话。
请注意方向。弄反方向很常见,而且会造成安全问题:公钥放在服务器上,私钥由您保管。复制到服务器上的私钥将不再可信。
密钥登录也有自己的故障模式。sshd 在文件权限过于宽松时会忽略密钥,并在服务器日志中记录原因:
Authentication refused: bad ownership or modes for directory /home/ubuntu/.ssh客户端只会告诉您 Permission denied (publickey)。这条消息可能对应十几种不同原因,因此在您被拒绝登录之前,值得学习如何正确读取公钥错误信息。创建密钥、使用密码短语保护密钥并将密钥加载到代理中的实际操作,请参阅SSH 密钥管理;在不让自己失去访问权限的情况下关闭密码登录,请参阅加固 VPS 上的 SSH。
SFTP、scp 和端口转发使用同一连接
理解这一点后,SSH 的其他功能就容易掌握了。身份验证会建立加密连接,而该连接可以同时承载多个相互独立的通道。Shell 只是其中一种通道。
- 远程 Shell。
ssh you@server打开会话通道,并请求启动交互式 Shell。 - 单条命令。
ssh you@server uptime打开通道,运行一条命令,输出结果后退出。 - SFTP。客户端请求
sshd启动其sftp子系统,文件传输在同一连接中进行。SFTP 是运行在 SSH 之上的文件传输协议,与 FTP 没有共同的设计基础。在 FTP 上增加加密功能的协议称为 FTPS,与 SFTP 无关。 - scp。使用同一登录会话复制文件。自 OpenSSH 9.0(于 2022 年发布)起,
scp默认在底层使用 SFTP 协议。 - 端口转发。
ssh -L 8080:localhost:80 you@server将笔记本电脑上的 8080 端口转为通往服务器 80 端口的入口,并通过加密连接传输。-R向相反方向转发,-D 1080则将会话转换为 SOCKS 代理。 - Git。类似
git@github.com:user/repo.git的远程端是一次 SSH 登录,其远端运行的是命令处理程序,而不是 Shell。 - rsync 和 Ansible 也是 SSH 客户端。它们打开通道,运行相应操作,然后读取返回的输出。
列表中的每一项都使用相同的端口、相同的主机密钥检查和相同的凭据。因此,只需设置一次密钥身份验证,就能立即减少后续工作:这些工具都会继承该配置。这也是同一个用于缩短登录命令的 ~/.ssh/config 文件,在您通过一台笔记本电脑管理多台 Linux 服务器时仍然适用的原因。
SSH 无法解决的问题
- SSH 不会让服务器自动变得安全。SSH 只保护通往入口的路径。入口仍然存在,攻击者会继续尝试登录。使用 fail2ban 阻止重复登录尝试可处理大量登录尝试;仅允许使用密钥进行身份验证,则可移除攻击者猜测的对象。
- SSH 无法防范来自您自己设备的风险。任何能访问您笔记本电脑的人,都可以获取您的私钥和已加载的代理。
- SSH 无法隐藏您正在使用 SSH。端口号和明文版本横幅都会表明这一点。
- SSH 不负责保护连接建立前发生的事情。名称解析,以及您决定信任哪个地址,都会在连接建立前完成。
接下来做什么
如果您现在正通过云服务商控制台打开一台新服务器,建议按固定顺序操作。先登录服务器,创建普通用户,安装您的密钥,然后关闭容易被利用的访问路径。新 VPS 的前 10 分钟将按这一顺序完整介绍操作步骤;如果您还不熟悉相关术语,VPS 的实际含义会补充介绍服务器本身。之后应依次阅读密钥和安全加固这两篇文章。
FAQ
SSH 代表什么?
SSH 代表 secure shell。它是一种用于登录远程计算机并在其上执行命令的协议,相关定义见 RFC 4251 至 RFC 4254。OpenSSH 是几乎所有人都在使用的实现:您计算机上的 ssh 客户端,以及远程计算机上的 sshd 服务器。SSH 取代了 telnet,后者会以明文通过网络发送包括密码在内的所有内容。
SSH 为什么使用 22 端口?
IANA 于 1995 年将 22 端口分配给 SSH。当时 FTP 使用 21 端口,telnet 使用 23 端口;SSH 正是为取代这两个协议而编写的。没有任何机制强制使用这个端口号:在服务器上的 /etc/ssh/sshd_config 中设置 Port 可以修改端口,客户端则可通过 ssh -p 指定其他端口。由于 22 是默认端口,自动扫描器会持续访问它。因此,新服务器的 /var/log/auth.log 中通常会充满 Failed password for invalid user 行。更改端口可以减少这些噪声,但不会提供真正的安全保护。
SSH 警告主机密钥已更改时应该怎么办?
在清除任何内容前,先查明原因。通常原因并不危险:服务器经过重建,因此 sshd 生成了新的主机密钥;或者新计算机被分配了旧 IP 地址。如果您确认计算机确实经过重建,请运行 ssh-keygen -R <host> 删除已存储的密钥,然后重新连接,并将显示的指纹与服务商控制台报告的指纹进行比较。如果您这边没有任何变更,请不要连接,也不要输入密码。在这种情况下,OpenSSH 已经出于这一确切原因拒绝密码认证。
SFTP 和 scp 与 SSH 不同吗?
它们运行在 SSH 之上。完成认证后,SSH 连接可以承载多个通道,shell 只是其中之一。SFTP 是一种文件传输协议,它通过同一连接使用 sshd 的 sftp 子系统;自 OpenSSH 9.0 起,scp 底层也使用 SFTP 协议。端口转发和通过 SSH 使用 Git 同样是该连接上的通道。它们使用相同的端口、相同的主机密钥检查和相同的登录信息。请注意,SFTP 不是在 FTP 上添加加密功能;带加密的 FTP 称为 FTPS,它是独立的协议。
密钥认证确实比密码更好吗?
是的,对于任何可从互联网访问的服务器都如此。密码是您每次登录时都要提供给服务器的短秘密,而自动化客户端会持续尝试猜测 22 端口。使用密钥对时,私钥部分不会离开您的计算机:客户端对与当前会话关联的数据进行签名,服务器再使用 ~/.ssh/authorized_keys 中的公钥验证该签名。记录下来的签名无法在另一台服务器上重放。请使用口令保护私钥,因为没有口令的密钥文件对任何复制它的人来说都相当于有效的登录凭据。