LabHub
学习 学习路径 课程

SSH 与文件传输

放好了密钥,为什么还问密码

在 LabHub 中继续学习

一句话总结

公钥认证失败的原因大多不在密码学,而在于文件权限。如果权限过于宽松,sshd 会悄无声息地拒绝密钥。

概念图: 文件权限 · 悄无声息地 · 权限本身就是认证策略 · SELinux 上下文错误

为什么需要了解这些

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

客户端配置也更适合写入文件。

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,如果能够访问服务器,这是得到答案最快的方式。

服务器配置也可能禁止这种认证方式。 检查 PubkeyAuthenticationAuthorizedKeysFileAllowUsers/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= 约束的密钥是否确实只能执行该命令。