放好了密钥,为什么还问密码
一句话总结
公钥认证失败的原因大多不在密码学,而在于文件权限。如果权限过于宽松,sshd 会悄无声息地拒绝密钥。
为什么需要了解这些
用 ssh-copy-id 写入密钥后,系统仍然要求输入密码。查看客户端日志(ssh -vvv)会发现客户端已经提交密钥(Offering public key),服务器却不接受。服务器日志里也没有明显的原因。
原因通常如下。
chmod 755 ~ # 홈이 그룹 쓰기 가능이면 거부된다
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
sshd 如此严格是有原因的。如果其他用户能够写入 authorized_keys,他就可以添加一行自己的密钥,从而登录别人的账户。也就是说,权限本身就是认证策略。
在 RHEL 系发行版中还有一个陷阱。手动创建主目录或从其他路径复制文件时,可能会因为 SELinux 上下文错误而拒绝密钥。日志不会明确显示为权限问题,因此往往很耗排查时间。标准解决方法是执行 restorecon -Rv ~/.ssh。
工作原理
密钥分为两部分。私钥只保存在自己手中,公钥则作为一行内容写入服务器的 authorized_keys。认证时,客户端使用私钥对服务器发来的挑战进行签名,因此私钥不会通过网络传输。
密钥类型实际上已经收敛为两种。
ssh-keygen -t ed25519 -C 'youngju@laptop-2026' -f ~/.ssh/id_ed25519
ssh-keygen -t rsa -b 4096 -C 'youngju@laptop-2026' -f ~/.ssh/id_rsa
ssh-keygen -t ed25519-sk -f ~/.ssh/id_ed25519_sk # FIDO2 하드웨어 키
默认选择 Ed25519;只有 FIPS 等组织策略要求时才使用 RSA 4096。使用 -sk 时,私钥不会离开硬件设备。
养成在 -C 注释中注明人员和设备的习惯很重要。当 authorized_keys 积累了 20 行时,这几乎是判断每一行归属的唯一线索。
可以为 authorized_keys 中的每一行设置约束。
restrict,from="10.0.0.0/8",command="/usr/local/bin/deploy-only" ssh-ed25519 AAAAC3Nza... deploy@ci
restrict是关闭全部功能的安全默认值,之后只重新启用确有需要的功能。from=限制连接来源。command=无论客户端请求什么,都只执行指定命令,特别适合部署密钥。
客户端配置也更适合写入文件。
Host bastion
HostName bastion.example.com
User youngju
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes
ServerAliveInterval 30
Host prod-*
User deploy
ProxyJump bastion
IdentitiesOnly yes
IdentitiesOnly yes 尤其重要。没有它时,客户端会依次尝试代理中加载的所有密钥,可能在轮到正确密钥之前就触发服务器的 MaxAuthTries(默认为 6)而认证失败。这是持有多个密钥的人经常遇到的问题。
生产现场中的常见情况
撤销密钥并不只是从文件中删除一行。 已建立的会话仍会继续存活。完整的撤销流程是:删除对应行 → 使用 who 检查会话 → 终止该用户的 sshd 会话。最后一步也可能断开自己的会话,因此务必确认目标账户。
习惯性忽略主机密钥警告。 每次重装服务器时都会出现警告,人们因而容易养成这种习惯。但这会让抵御中间人攻击的唯一防线失效。规模扩大后,正确做法是迁移到 SSH 证书,即由 CA 签署主机密钥。
有密钥却无法登录时
Permission denied (publickey) 可能由多种原因引起,但只要固定检查顺序,通常可以
在 1 分钟内解决。
首先确认客户端发送的是哪把密钥。
ssh -v user@host 2>&1 | grep -E "Offering|Authentications|Server accepts"
如果持有多把密钥,ssh 会按顺序尝试,但可能先触发服务器的
MaxAuthTries(默认为 6),还没发送真正正确的密钥就被断开。
此时应明确指定要使用的密钥。
Host prod
IdentityFile ~/.ssh/id_prod
IdentitiesOnly yes # 다른 키는 아예 시도하지 않는다
权限哪怕只宽松一点,sshd 也会拒绝。 这是最常见的原因。
| 对象 | 权限 |
|---|---|
~ |
组和其他用户均无写权限(不高于 755) |
~/.ssh |
700 |
~/.ssh/authorized_keys |
600 |
| 私钥 | 600 |
服务器端日志(journalctl -u sshd)中会留下 Authentication refused: bad ownership or modes,如果能够访问服务器,这是得到答案最快的方式。
服务器配置也可能禁止这种认证方式。 检查 PubkeyAuthentication、
AuthorizedKeysFile、AllowUsers/AllowGroups 以及 PermitRootLogin。
近期发行版默认拒绝陈旧的密钥格式(使用 SHA-1 的 ssh-rsa),因此旧密钥可能突然失效。此时正确做法是创建新密钥;如果情况紧急,可以临时将对应算法加入 PubkeyAcceptedAlgorithms。
SSH 代理很容易被遗忘。 如果 ssh-add -l 为空,就无法使用受密码保护的密钥。代理转发(-A)虽然方便,但那台服务器上的 root 可以直接使用你的代理。如果目的只是通过跳板机连接,应使用 ProxyJump——这样密钥不会暴露给中间服务器。
Host prod
ProxyJump bastion
下一次实验要做什么
创建密钥、正确设置权限、将其登记到 authorized_keys,并实际连接到 127.0.0.1:2222。通过 ~/.ssh/config 创建别名,再确认设置了 command= 约束的密钥是否确实只能执行该命令。