教程

SSH 密钥:配置无密码登录服务器

更新于: · Skillaria.Top

配置 SSH 密钥只需三步:用 ssh-keygen -t ed25519 生成密钥对,用 ssh-copy-id user@host 把公钥上传到服务器,然后验证登录。之后可以在 /etc/ssh/sshd_config 中设置 PasswordAuthentication no 禁用密码登录,并在 ~/.ssh/config 中为常用连接定义别名。

SSH 密钥是一对文件:私钥留在你自己的电脑上,公钥放到服务器上。连接时,服务器会检查你是否持有与某个已授权公钥对应的私钥,验证通过即允许登录,无需密码。这比密码方便得多,也安全得多:靠暴力破解猜出密钥几乎不可能。下面介绍从生成密钥到禁止密码登录的完整配置过程,并讲解最常遇到的错误。

密钥认证的工作原理

密钥是非对称的。公钥(id_ed25519.pub)可以告诉任何人,无法由它推算出私钥。私钥(id_ed25519)绝不离开你的电脑,也不会通过网络传输。

登录时,客户端告诉服务器它想使用哪把密钥。服务器在用户的 ~/.ssh/authorized_keys 中查找这个公钥;如果找到,就要求客户端用私钥对会话数据签名。服务器用公钥验证签名,通过后允许登录。整个过程中密码都不会被发送。

第一步:生成密钥,ssh-keygen

在你自己的电脑上(Linux、macOS,或者带有内置 OpenSSH 的 Windows 10/11)执行:

ssh-keygen -t ed25519 -C "alena@laptop"

选项 -t 指定算法,-C 设置注释,注释会附加在公钥末尾,方便以后辨认密钥属于谁。Ed25519 是目前推荐的现代算法:密钥短、速度快、安全性高。如果服务器太旧、不支持 Ed25519,可以使用长度不少于 3072 位的 RSA:ssh-keygen -t rsa -b 4096。

Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/alena/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/alena/.ssh/id_ed25519
Your public key has been saved in /home/alena/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:Xq3v0m6c1y2pVv8m3N0uYv9yQm0e6dFv5gH2rZt1aBc alena@laptop

是否需要口令

口令(passphrase)用来加密磁盘上的私钥。如果笔记本电脑被盗或文件泄露,没有口令的私钥就无法直接使用。个人密钥最好设置口令;为了避免每次连接都输入,可以使用 ssh-agent,它会把解密后的密钥保存在内存中,直到会话结束。

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519
ssh-add -l      # 查看已加载的密钥

没有口令的密钥适用于自动化场景,例如 CI 或备份脚本。这类密钥最好在服务器端加以限制(见下文)。

第二步:把公钥上传到服务器,ssh-copy-id

最简单的方法是 ssh-copy-id。它会用密码登录一次,把你的公钥追加到服务器的 ~/.ssh/authorized_keys 中,并设置正确的权限。

ssh-copy-id -i ~/.ssh/id_ed25519.pub deploy@203.0.113.10
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/alena/.ssh/id_ed25519.pub"
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
deploy@203.0.113.10's password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'deploy@203.0.113.10'"
and check to make sure that only the key(s) you wanted were added.

如果服务器使用非标准端口,加上 -p 2222。

如果没有 ssh-copy-id

Windows 中没有 ssh-copy-id 工具。可以用一条命令手动上传密钥:

cat ~/.ssh/id_ed25519.pub | ssh deploy@203.0.113.10 "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

云服务商通常会在创建服务器时就让你指定公钥,它会通过 cloud-init 写入 authorized_keys。

第三步:验证登录

ssh deploy@203.0.113.10

如果不再要求输入密码(或只要求输入密钥的口令),说明配置成功。第一次连接新服务器时,SSH 会显示主机密钥指纹并询问是否信任。输入 yes 后,指纹会保存到 ~/.ssh/known_hosts 中,之后如果服务器的密钥发生变化,SSH 会发出警告。

要查看客户端尝试了哪些密钥,以及服务器为何拒绝,可以以详细模式连接:

ssh -v deploy@203.0.113.10

~/.ssh/config 文件:为服务器设置简短名称

服务器一多,每次都输入用户名、IP、端口和密钥路径就很麻烦。这些信息可以写在 ~/.ssh/config 中:

Host prod
    HostName 203.0.113.10
    User deploy
    Port 2222
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes

Host bastion
    HostName bastion.example.com
    User alena

Host db-internal
    HostName 10.0.0.15
    User admin
    ProxyJump bastion

Host *
    ServerAliveInterval 60
    AddKeysToAgent yes

之后只需执行 ssh prod,而且 scp、rsync 和 Git 也都能识别这些名称:rsync -av ./site/ prod:/var/www/。

  • IdentitiesOnly yes:只使用指定的密钥。如果没有这一行,客户端会尝试 agent 中的所有密钥,服务器可能在到达正确密钥之前就因尝试次数过多而断开连接。
  • ProxyJump:通过中间服务器(堡垒机)一条命令连接内网服务器。
  • ServerAliveInterval:每 60 秒发送一次心跳,防止空闲时连接断开。

配置文件的权限必须是 600,否则 SSH 可能拒绝读取:chmod 600 ~/.ssh/config。

Agent forwarding:何时需要,有什么风险

有时需要从服务器再连接到另一台机器,例如从私有仓库执行 git pull。不能把私钥复制到服务器上。相反,可以使用 ssh -A 或 ForwardAgent yes:服务器在会话期间可以使用你本地的 agent。但是,服务器上的 root 用户在你连接期间同样可以使用这个 agent。因此,只对可信主机开启转发;需要经过中间服务器跳转时,应使用 ProxyJump,它不会暴露 agent。

第四步:禁用密码登录

只要允许密码登录,服务器就仍然是暴力破解的目标。确认可以使用密钥登录之后,就应禁用密码。重要的是:在新窗口中确认登录成功之前,不要关闭当前会话,否则可能把自己锁在外面。

打开 /etc/ssh/sshd_config(如果主配置通过 Include 指令加载了其他文件,也可以在 /etc/ssh/sshd_config.d/ 中创建文件),并设置:

PasswordAuthentication no
KbdInteractiveAuthentication no
PubkeyAuthentication yes
PermitRootLogin no

PermitRootLogin no 禁止 root 直接登录:管理员使用自己的用户登录,再通过 sudo 提升权限。如果必须允许 root 登录,可以使用 PermitRootLogin prohibit-password,即只允许密钥登录。

检查语法并重新加载服务:

sudo sshd -t
sudo systemctl reload ssh      # Debian/Ubuntu;在 RHEL 系列中使用 sshd

如果配置正确,sshd -t 不会有任何输出。然后在新终端中确认密钥登录正常,并确认密码登录会被拒绝:

$ ssh -o PubkeyAuthentication=no deploy@203.0.113.10
deploy@203.0.113.10: Permission denied (publickey).

额外的防护

禁用密码是最重要的一步。接下来通常还会用防火墙限制 22 端口的访问(ufw allow from 198.51.100.0/24 to any port 22),并安装 fail2ban,它会在多次失败尝试后封禁 IP。查看 SSH 防护状态可以使用 sudo fail2ban-client status sshd。

用于自动化的密钥可以在 authorized_keys 中通过选项加以限制。例如,from="198.51.100.7",no-port-forwarding,no-pty ssh-ed25519 AAAA... backup 表示只允许这个地址连接,并且不能使用交互式 shell。

常见错误及解决方法

Permission denied (publickey)

这是最常见的错误。原因按出现频率从高到低排列:

  1. 服务器上的权限不正确。如果目录或文件对其他用户可写,sshd 会忽略 authorized_keys。解决方法:chmod 700 ~/.ssh、chmod 600 ~/.ssh/authorized_keys,并且用户的主目录不能对组或其他人可写。
  2. 所有者不正确。如果你用 sudo 创建了这些文件,它们会属于 root。解决方法:sudo chown -R deploy:deploy /home/deploy/.ssh。
  3. 密钥被放到了错误的用户下。你为 root 复制了密钥,却以 deploy 登录,或者反过来。
  4. 客户端提供了错误的密钥。查看 ssh -v 的输出:Offering public key 行显示了正在尝试哪些密钥。使用 -i 或 IdentityFile 指定正确的密钥。
  5. 复制时密钥损坏。公钥必须是一整行。从剪贴板粘贴时如果带入了换行,就会破坏密钥。

服务器端的日志会显示拒绝的原因:sudo journalctl -u ssh -n 30(在 RHEL 中为 -u sshd)。其中可能出现类似这样的记录:Authentication refused: bad ownership or modes for directory /home/deploy/.ssh。

UNPROTECTED PRIVATE KEY FILE

如果私钥可以被其他用户读取,客户端会拒绝使用它。解决方法:chmod 600 ~/.ssh/id_ed25519。

REMOTE HOST IDENTIFICATION HAS CHANGED

服务器的主机密钥与 known_hosts 中保存的不一致。服务器重装或 IP 变更后会出现这种情况,但中间人攻击的表现也完全相同。确认服务器确实被重新创建过,然后删除旧记录:ssh-keygen -R 203.0.113.10。

Too many authentication failures

agent 向服务器提供了太多密钥,服务器按 MaxAuthTries 断开了连接。解决方法:在 ~/.ssh/config 中设置 IdentitiesOnly yes 并明确指定 IdentityFile。

总结

配置 SSH 密钥只需五分钟:ssh-keygen -t ed25519、ssh-copy-id、验证登录,然后在保持第二个会话打开的同时设置 PasswordAuthentication no 并重新加载 sshd。~/.ssh/config 可以省去冗长的命令,而 700/600 权限能解决大多数“Permission denied”错误。SSH、rsync,以及用防火墙和 fail2ban 保护服务器的内容,都会在 Linux 课程中结合实践讲解。

常见问题

一把 SSH 密钥可以用于所有服务器吗?

技术上可以,很多人也这样做。但对于不同的使用场景(工作、个人项目、CI),最好分别生成密钥:一把密钥泄露,不会让所有服务器都暴露。

Windows 中的 SSH 密钥保存在哪里?

内置的 OpenSSH 把它们保存在 C:\Users\用户名\.ssh\ 中,其中有 id_ed25519、id_ed25519.pub、known_hosts 和 config。在 PowerShell 中使用同样的 ssh-keygen 命令即可生成。

怎样修改已有密钥的口令?

执行 ssh-keygen -p -f ~/.ssh/id_ed25519,它会先要求输入旧口令,再输入新口令。密钥本身不变,无需重新上传到服务器。

怎样撤销某把密钥的访问权限?

从服务器的 ~/.ssh/authorized_keys 中删除对应的公钥行。公钥末尾的注释有助于找到正确的那一行。

只剩私钥时,怎样得到公钥?

执行 ssh-keygen -y -f ~/.ssh/id_ed25519,它会从私钥计算并输出公钥。

通过视频学习本主题

课程“Linux: с нуля до уверенного администрирования”结合实践详细讲解了这一主题。

前往课程