SSH 免密登录:公钥认证的原理、部署与排错

每次连服务器都要敲密码,既繁琐又是攻击面。SSH 公钥认证用一对密钥替代密码:本地保留私钥,服务器只存公钥,登录时由客户端自动完成挑战响应,不再需要人工输入任何东西。配好之后不仅能免密登录,更重要的是可以关掉密码登录,直接消除暴力破解这条最常见的外部攻击路径。

公钥认证是怎么工作的

理解原理能帮你排错,而不是机械照抄命令。

SSH 公钥认证基于非对称加密。本地有一对密钥,比如 id_ed25519(私钥)和 id_ed25519.pub(公钥)。私钥永远不离开你的机器,公钥可以随便分发。

登录时,服务器用你预先放上去的公钥发起一个数学挑战,只有持有对应私钥的人才能正确应答。整个过程不传输任何密码,服务器也不需要存储任何密码信息。认证通过后直接拿到 shell。

服务器怎么知道用哪个公钥来挑战你?靠 ~/.ssh/authorized_keys 文件。这个文件里每一行是一个公钥,服务器逐行尝试,匹配上你本地私钥的就生效。所以”部署公钥”的本质就是把公钥追加到这个文件里。

权限是这个机制里最容易踩坑的地方。sshd 对权限极其敏感,如果 ~/.sshauthorized_keys 对其他用户可读可写,它会直接拒绝使用,认证失败。原因很直接:如果别人能改你的 authorized_keys,就等于能往里塞自己的公钥,等同于密码泄露。所以下面会反复出现 chmod 700 ~/.sshchmod 600 authorized_keys 这两条命令,不是多余,是硬性前提。

生成密钥对

先看看本地有没有现成的密钥,避免覆盖:

1
2
3
4
5
# Mac / Linux
ls -al ~/.ssh | grep id_

# Windows (PowerShell)
dir C:\Users\<你的用户名>\.ssh\id_*

已经有 id_rsa / id_rsa.pubid_ed25519 / id_ed25519.pub 就可以直接用,跳到下一步。

生成新密钥:

1
ssh-keygen -t ed25519 -C "your_email@example.com"

参数说明:

  • -t ed25519:推荐用 Ed25519 算法。密钥只有 256 位(RSA 4096 是 4096 位),签名快,实现更简洁不易出错。OpenSSH 6.5(2014 年发布)以后都支持。
  • -C:只是个注释,方便你日后在一堆密钥里认出哪个是哪个,不影响功能。

如果你的服务器或客户端比较老(OpenSSH 低于 6.5),退回 RSA:

1
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"

-b 4096 指定 4096 位密钥长度。RSA 2048 位目前仍算安全(NIST 认为可用到 2030 年),但 4096 是兼顾性能和长期安全的常用选择。

执行后会问两个问题:

1
2
Enter file in which to save the key (/home/user/.ssh/id_ed25519): [直接回车用默认路径]
Enter passphrase (empty for no passphrase): [建议设置]

passphrase 是给私钥本身加的密码。不设的话,任何拿到你私钥文件的人就能直接登录服务器。设了的话,每次用密钥需要输一次 passphrase,可以用 ssh-agent 缓存,后面会讲。

我的建议是:个人开发机设 passphrase,用 ssh-agent 缓存;临时环境或 CI 里可以留空,但那台机器本身要做好访问控制。

部署公钥到服务器

方法一:ssh-copy-id(推荐)

1
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@remote-server

这条命令会登录服务器(最后一次输密码)、把公钥追加到 ~/.ssh/authorized_keys、自动设置正确的权限。一行搞定,不容易出错。

方法二:手动部署

没有 ssh-copy-id(比如某些 Windows 环境)就手动来。先复制公钥到剪贴板:

1
2
3
4
5
6
7
8
# Mac
cat ~/.ssh/id_ed25519.pub | pbcopy

# Linux (xclip)
cat ~/.ssh/id_ed25519.pub | xclip -selection clipboard

# Windows (PowerShell)
cat ~/.ssh/id_ed25519.pub | clip

然后登录服务器,追加公钥并设权限:

1
2
3
4
mkdir -p ~/.ssh
echo "粘贴的公钥内容" >> ~/.ssh/authorized_keys
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

chmod 700 ~/.ssh 让只有你自己能读写执行这个目录,chmod 600 authorized_keys 让只有你自己能读写这个文件。这两条是 sshd 接受公钥认证的硬性前提,少一条都登不上。

验证并禁用密码登录

测试免密登录:

1
ssh user@remote-server

看到欢迎信息就成功了:

1
2
Welcome to Ubuntu 22.04.5 LTS
Last login: Mon Jul 15 09:30:28 2024 from 192.168.1.100

确认公钥认证正常后,可以关掉密码登录,堵死暴力破解:

1
sudo nano /etc/ssh/sshd_config

确保这几项:

1
2
PubkeyAuthentication yes
PasswordAuthentication no

改完重载配置:

1
sudo systemctl reload sshd

这一步之后,没有你私钥的人无论如何都登不进来。

注意,关掉密码登录是不可逆的便利性损失。如果你弄丢了私钥,自己也被锁在外面。所以关之前确保私钥有备份,passphrase 也记得住。万一真被锁外面,只能走带外通道:云厂商的 VNC 控制台、物理终端、或者找管理员。

排错:Permission denied (publickey)

最常见的报错。按这个顺序排查。

1. 本地私钥路径对不对

-v 看详细日志:

1
ssh -v user@remote-server

确认 ssh 客户端确实在尝试你期望的那个密钥文件。如果生成了多个密钥,可能需要 ~/.ssh/config 显式指定:

1
2
3
4
Host myserver
HostName remote-server
User user
IdentityFile ~/.ssh/id_ed25519

2. 服务器端权限

1
2
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys

再检查家目录权限,不能对其他用户可写:

1
chmod go-w ~

sshd 还会检查家目录的属主是不是你自己。用 ls -ld ~ 确认。

3. 服务器端 sshd 配置

1
sudo grep -E 'PubkeyAuthentication|AuthorizedKeysFile|PasswordAuthentication' /etc/ssh/sshd_config

确保:

1
2
PubkeyAuthentication yes
AuthorizedKeysFile .ssh/authorized_keys

如果改过配置,记得 sudo systemctl reload sshd

4. SELinux

CentOS / RHEL 上 SELinux 可能拦掉非标准路径下的 authorized_keys。用这条命令修复标签:

1
restorecon -Rv ~/.ssh

安全取舍

几个需要想清楚的决定。

passphrase 要不要设

设了更安全,但每次用密钥要输一次。折中是用 ssh-agent:启动时输一次 passphrase,之后缓存到内存,直到你关机或主动清除。

1
2
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

macOS 上可以配合 ssh-add --apple-use-keychain 把 passphrase 存进钥匙串,Linux 上有 gnome-keyringkwallet 做同样的事。

RSA 还是 Ed25519

新环境无脑选 Ed25519。RSA 4096 也安全,但密钥长、签名慢,而且 RSA 的安全性随算力增长衰减得比 Ed25519 快。只有在兼容老系统时才退回 RSA。

密钥泄露怎么办

立即登录服务器,从 ~/.ssh/authorized_keys 里删掉对应那一行公钥,本地生成新密钥对重新部署。如果连服务器都登不上了(比如私钥丢了又被关了密码登录),就得走前面提到的带外通道。

别用同一对密钥到处部署

每台服务器或每个用途一对密钥。某台机器被攻破时只需要轮换那一对,不影响其他。管理多个密钥用 ~/.ssh/config 文件按 Host 分组指定 IdentityFile