☰
SSH密钥认证配置实战:从生成密钥到免密登录与安全加固
2026/10/14 7:07:29 网站建设 项目流程

1. 先弄明白SSH密钥认证到底解决了什么

先说个我自己的场景。早几年我做某台Linux服务器的日常维护,密码登录看起来没什么问题,但架不住每次部署都要手输密码,而且密码复杂度要求一高,记不住就算了,一不留神还会在终端历史记录里留下痕迹。更烦的是,有一次自动化脚本需要定时从服务器拉取数据,密码登录根本没法无人值守运行。后来我把全部服务器切到SSH密钥认证,一次配置,后续所有手动登录、SFTP传文件、脚本批量操作全都省掉了密码环节。这篇就把我实际配置的过程完整记录下来,步骤不需要你额外猜,照着走基本一次过。

先解释一下密钥认证到底是怎么回事。传统的密码登录,服务器保存的是你的密码,你每次登录时把密码传过去比对。密钥认证换了一套思路:本地生成一对密钥,一个是公开的(公钥),一个是私有的(私钥)。公钥可以随意放到服务器上,私钥必须牢牢守在自己电脑里。登录握手时,服务器会生成一个随机挑战,只有持有对应私钥的客户端才能正确回应。密码从头到尾不会出现在网络上,服务器那边也不存你的私钥。

可以把公钥、私钥的关系理解成一把锁和一把钥匙:公钥是那把锁,你把它装到服务器的门上;私钥是你口袋里的钥匙,只有它能打开这把锁。就算别人拿到锁,也造不出一模一样的钥匙。实际项目中我见过不少刚开始接触Linux的朋友,把这两个概念混成一团,后面我会专门讲那些容易踩的坑。

这套机制解决的核心问题有三个。第一是免密,登录不再需要输入密码,尤其适合手动远程登录时反复连接同一台机器,体验完全是即时响应。第二是安全,私钥不出本地,服务器被拖库也不会直接泄露可用凭证,密码暴力破解这条路基本被堵死。第三是自动化,后台任务、批处理脚本、持续集成管道都可以通过非交互方式连接服务器,不会因为等输入密码而挂住。

适合看这篇的人,我大致分三类:一是刚接触Linux服务器托管,想把手动登录做得更顺手的开发者;二是公司里管了几台甚至几十台云主机的运维,需要一套干净利落的登录方案;三是写过自动化脚本、但一直靠expect模拟输密码的朋友,看完你就会发现原生支持比那些workaround可靠得多。

2. 生成密钥前的环境检查与三处常见误区

2.1 先看看本地是否已经存在密钥

大多数Linux和macOS机器上,OpenSSH客户端是系统自带的,Windows 10以上也自带了OpenSSH。所以在生成新密钥前,先检查一下~/.ssh目录里是不是已经有密钥文件了。我之前帮某位朋友排查时,发现他以为没配置过SSH密钥,结果~/.ssh下躺着一个早期的默认密钥,他完全不知道从哪来的。如果这种情况你直接生成新密钥,后续会出现"明明我加了公钥,服务器还是问我要密码"的怪事——大概率是ssh客户端默认用了旧密钥去连接。

检查命令很简单:

ls -la ~/.ssh

如果看到id_ed25519和id_ed25519.pub这类文件,说明你已经有一套密钥了,可以继续用,也可以为了这个项目单独生成新的。如果~/.ssh目录都不存在,也没关系,ssh-keygen会自动帮你创建。

2.2 生成密钥时的核心参数怎么选

生成密钥我用的是Ed25519算法,这也是现在比较推荐的默认选择。相比传统的RSA,Ed25519密钥更短、生成速度更快、安全性也不错。命令长这样:

ssh-keygen -t ed25519 -a 100 -C "work-server-key" -f ~/.ssh/id_ed25519_work

参数逐一说一下:-t ed25519指定算法;-a 100设置KDF迭代次数,这个值越高,别人拿到你的私钥文件后暴力破解口令的难度越大,我习惯设成100,实测登录没有感觉到明显延迟;-C是注释,一般写上用途,方便日后辨认;-f指定保存路径和文件名,我比较建议按用途来命名,比如id_ed25519_web、id_ed25519_db,别所有服务器都用一个默认名。

如果你确实需要兼容非常老旧的系统,再考虑RSA:

ssh-keygen -t rsa -b 4096

不过从我实际接触过的环境来看,近些年的Linux发行版自带的OpenSSH都支持Ed25519,连内部自建的一些老系统也没有遇到问题。

2.3 到底要不要给私钥设置passphrase

好多人的第一反应是:免密登录嘛,私钥就不设密码了。我强烈建议反过来——私钥一定要设一个passphrase。这里的"免密"指的是登录服务器免密,而不是私钥裸奔。私钥文件一旦落到别人手里,没设passphrase就相当于把门钥匙直接给了别人。设置了passphrase,即便文件被复制走,对方也还得破解口令才能用。

你可能会担心,每次SSH连接都要输passphrase,不是又烦了吗?解决办法是用ssh-agent把密钥加载进内存:

eval "$(ssh-agent -s)" ssh-add ~/.ssh/id_ed25519_work

输入一次passphrase后,之后整个会话窗口内的SSH连接都不再重复询问,重启终端或重启系统后再执行一次即可。macOS上还能用ssh-add --apple-use-keychain把密钥存进钥匙串,连手动加载都省了。桌面Linux也有类似工具,不过我还是习惯手动管理。

2.4 三个新手最容易搞错的误区

第一个误区:把私钥传到服务器上去。我见过某开发者在配置免密时,直接把id_ed25519文件用scp传到了服务器~/.ssh/下面,他以为这样服务器就有了"钥匙"。结果私钥在服务器上等于把钥匙挂在门上,任何能登录服务器的用户都能把它复制走,整个信任链直接崩掉。正确的做法是:公钥放到服务器的authorized_keys,私钥永远留在本地。

第二个误区:权限设置得太随意。SSH对密钥文件的权限非常敏感。authorized_keys如果被同组其他用户可写,SSH会直接拒绝使用它,日志里会出现"bad permissions"之类的提示。我通常给本地私钥设600,给服务器上的~/.ssh目录设700,authorized_keys设600。

第三个误区:把known_hosts和authorized_keys搞混。这两个文件名字长得很像,但作用完全不同。known_hosts是客户端保存的服务器主机指纹,用于连接时校验服务器的身份;authorized_keys是服务器保存的、允许登录的客户端公钥列表。配置免密时只需要操作后者,不需要动前者。

3. 把公钥放上服务器的两种落地方式

3.1 方式一:用ssh-copy-id一键搞定

生成好密钥后,接下来就是把公钥写到服务器的~/.ssh/authorized_keys里。最省事的办法是用OpenSSH自带的ssh-copy-id命令:

ssh-copy-id -i ~/.ssh/id_ed25519_work.pub user@server_ip

第一次执行会让你输入一次服务器密码,因为此时还没有建立免密。这一步是正常的,也是整个配置过程中唯一需要输入密码的地方。命令跑完后,它会自动把公钥追加到服务器用户目录下的authorized_keys里,同时清理掉一些权限隐患。

ssh-copy-id实际做了什么,很多人不关心,但搞清楚可以帮你排查问题。它会登录服务器,检查~/.ssh目录是否存在,不存在就创建,设置好目录权限,然后把公钥内容追加到authorized_keys,再把文件权限调整为600。这一套操作几乎涵盖了所有常见权限坑。

唯一需要注意的是,如果服务器上的目标用户目录是网络挂载或者有特殊SELinux策略,ssh-copy-id可能提示成功但实际无法生效。这种情况很少见,我遇到过后会再检查一遍服务器那边的实际文件内容。

3.2 方式二:手动管道追加公钥内容

有些精简系统没装ssh-copy-id,这时候手动操作也很简单。先把公钥的内容打印出来看一遍:

cat ~/.ssh/id_ed25519_work.pub

然后通过SSH登录服务器,手动创建目录和文件。为了减少重复输入密码的次数,可以一口气用管道完成追加:

cat ~/.ssh/id_ed25519_work.pub | ssh user@server_ip "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

第一条命令把本地公钥内容通过管道发给远程命令;远程命令里,mkdir -p ~/.ssh保证目录存在,chmod 700把目录权限收紧,cat >>把公钥追加到authorized_keys末尾,最后chmod 600收紧文件权限。之所以用>>而不是>,是为了防止覆盖掉文件里已有的其他公钥。

如果服务器上已经存在多个公钥,直接追加即可。其中有个细节常被忽略:管道方式里SSH服务器端默认不分配tty,如果远程用户登录时会触发motd或某些交互式提示,命令可能表现异常。加个-T参数可以避免伪终端分配:

cat ~/.ssh/id_ed25519_work.pub | ssh -T user@server_ip "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

3.3 服务器端文件的权限硬性要求

我自己排查过不少免密失败问题,最后八成都是权限不对。SSH服务端对权限的要求比很多人想象中严格:

  • 用户家目录不能对组和其他用户可写
  • ~/.ssh目录权限不能超过755,通常用700
  • ~/.ssh/authorized_keys权限不能超过644,通常用600
  • 私钥在客户端一侧的权限也不能超过600

如果权限过宽,OpenSSH出于安全考虑会直接忽略这个文件,表现就是"我已经把公钥放进去了,但连接还是让我输密码"。检查方法如下:

ls -ld ~ ~/.ssh ~/.ssh/authorized_keys

正常结果应该类似:

drwx------. user user ... /home/user drwx------. user user ... /home/user/.ssh -rw-------. user user ... /home/user/.ssh/authorized_keys

数字权限分别是700、700、600。权限不对就老老实实修:

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

另外提一句root账号的事情。很多云服务器默认禁止root密码登录,但允许root密钥登录。我不建议日常直接用root操作,建议用普通用户连接,需要提权时再sudo。这样即使密钥泄露,伤害面也小很多。

4. 第一次免密连接成功后的验证与故障排查

4.1 成功连接后先做一次干净验证

公钥放上去之后,退出服务器再重新连接一次,这次正常应该不会再要密码:

ssh -i ~/.ssh/id_ed25519_work user@server_ip

加-i参数是为了明确指定本次连接使用哪把私钥。如果你本地只有一个默认密钥,不加也行;但如果像我一样有多把密钥,不指定的话客户端会逐个尝试,有时候顺序不对就会被服务器拒绝。为了一开始就排除这种干扰,建议测试阶段都显式指定-i。

连接成功后,先执行一个最简单的远程命令确认链路通畅:

ssh -i ~/.ssh/id_ed25519_work user@server_ip 'echo hello-ssh'

能看到返回hello-ssh,说明免密通道完全没问题。这一步也可以用来测试脚本连接、SCP传输等,之后会用到很多。

4.2 常见故障的完整排查链路

真到了"还是输密码"的时候,别急着反复试,按下面的链路一步步排除:

第一步,确认客户端有没有走密钥认证。加上-v参数看调试信息:

ssh -vv -i ~/.ssh/id_ed25519_work user@server_ip

输出里会看到客户端尝试了哪些认证方式,可能顺序是publickey、keyboard-interactive、password。如果publickey那一行下方直接跟着send_pubkey_test被拒绝,说明服务器端确实收到并拒绝了公钥,问题基本在服务器端配置;如果客户端压根没提publickey,那要先检查本地是否指定了正确的密钥文件。

第二步,检查服务器日志。SSH服务通常会把认证失败记录到/var/log/auth.log(Debian系)或者/var/log/secure(RHEL系):

sudo tail -n 50 /var/log/auth.log | grep sshd

日志里常见的关键字包括Connection closed by authenticating user、Failed publickey、Bad owner or permissions。其中权限问题最让人抓狂,因为它是静默拒绝的——客户端看到的是继续问密码,服务器端才在日志里暴露真实原因。看到Bad owner or permissions,就直接返回上面的权限检查环节。

第三步,确认服务器端SSH配置允许密钥认证。有的发行版默认配置没问题,但偶尔会被改过。查看配置文件:

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

PubkeyAuthentication必须是yes;AuthorizedKeysFile通常指向%h/.ssh/authorized_keys,也就是用户家目录下的路径。如果你把公钥放到了这个路径之外,必然失败。修改sshd_config后需要重启服务才生效。

第四步,确认端口和防火墙。服务器如果换了22之外的端口,客户端必须用-p指定:

ssh -p 2222 -i ~/.ssh/id_ed25519_work user@server_ip

防火墙层面,我遇到过安全组明明开放了22,但服务器本地iptables把来自某些来源的SYN丢了的情况。排查时可以用nc -vz server_ip 22测一下端口连通性,快速定位是不是网络层的问题。

第五步,确认known_hosts不捣乱。如果服务器重装过系统,主机指纹变了,客户端会提示REMOTE HOST IDENTIFICATION HAS CHANGED。这不是密钥登录本身的问题,但也会打断连接。处理方式是移除旧指纹:

ssh-keygen -R server_ip

下次连接会重新提示确认指纹。

4.3 多密钥场景下的选择逻辑

当本地有多把密钥时,SSH客户端的默认行为是依次尝试~/.ssh下的密钥文件,直到被接受或全部失败。看起来方便,实际坑很多。比如某台服务器只接受A密钥,但本地的B密钥排在前面,服务端对B返回拒绝后,有些配置会直接终止认证流程,不会继续尝试C。解决方法是显式指定密钥,或者用~/.ssh/config配置别名(下面会讲)。

另外一个容易忽略的点是ssh-agent可能把旧密钥"缓存"在内存里。如果你用ssh-add -D清空过agent,或者切换了项目身份,记得确认当前生效的是哪把密钥:

ssh-add -l

如果列表里显示了很多密钥,但和你预期的不一致,可以用ssh-add -D全部清掉,再重新ssh-add需要的私钥。

5. 多服务器、多身份场景下的密钥管理与安全防护

5.1 用ssh config统一管理多台服务器

手动登录的次数一多,每次敲完整命令会越来越烦。更实际的问题是,不同服务器用不同用户名、不同端口、不同密钥,光靠记忆很容易混淆。我的解决方案是写好~/.ssh/config文件,让客户端替我把参数都记住。

配置文件长这样:

Host web1 HostName 192.0.2.10 User deploy Port 22 IdentityFile ~/.ssh/id_ed25519_web ServerAliveInterval 60 Host db1 HostName 192.0.2.20 User admin Port 2222 IdentityFile ~/.ssh/id_ed25519_db

这样之后登录只需要敲:

ssh web1

客户端会自动读取别名web1对应的HostName、User、IdentityFile等参数。配置里的ServerAliveInterval 60表示每60秒向服务器发送一次保活包,避免长时间没有操作时被服务端断开。干我这种经常开着SSH窗口挂半天的人,这个参数非常实用。

需要注意,~/.ssh/config文件本地权限也不能放开,建议设成600。里面包含了服务器地址、用户名、密钥路径,虽然不是密码本身,但属于高价值信息,别暴露给其他系统用户。

5.2 多把密钥怎么分工

我现在的习惯是:一把私钥用于云服务器和跨机房主机,一把私钥用于公司内部跳板相关的连接,如果临时需要接入客户环境,再单独生成一把专用密钥,绝不混用。这样做的价值在于,某一把私钥万一泄露,我只需要在对应的服务器上撤掉对应公钥,其他环境完全不受影响。

还有一个细节,公钥文件本身可以公开,但注释里别写太敏感的信息。比如-C "web-deploy-key"是可以的,-C "alice-hr-system-root-key"就过于暴露身份信息了。虽然公钥泄露不直接导致安全问题,但减少信息暴露总是好的。

5.3 密钥泄露后的应急处理流程

不管防护做得多到位,总有意外发生。假如怀疑密钥泄露,第一步不是删文件,而是先把密钥对应的公钥从所有服务器的authorized_keys里移除。手动登录每一台服务器执行:

sed -i '/id_ed25519_web/d' ~/.ssh/authorized_keys

这里假设你能够通过其他方式登录服务器,比如还有密码登录作为兜底。所以我不建议在配置密钥认证后立刻关闭密码认证,总要给自己留一条后路,等新旧密钥都验证无误后再按需关闭。

第二步是重新生成一对新密钥,替换本地泄露的私钥。第三,更新所有目标服务器的authorized_keys,顺手检查一下里面有没有你不认识的公钥条目,有就清理掉。最后,如果你的密钥里设置了passphrase,也一并更换;如果没设,这次务必补上。

5.4 服务器端可以做的加固措施

密钥配置稳定后,可以考虑逐步收紧服务器的SSH策略。我比较推荐的做法是:先把密钥登录确认没问题,再编辑/etc/ssh/sshd_config,把PasswordAuthentication改为no,然后重启sshd服务。这样服务器彻底放弃密码认证,只接受密钥,暴力破解密码的可能性基本归零。

不过要特别提醒,关闭密码认证前一定确认至少一把可用私钥,且本地能正常连接。我有一次在某台测试机上直接关掉密码认证,结果发现私钥passphrase被系统钥匙串干扰,连接死活认证不了,最后只能靠云厂商的VNC网页控制台进去抢救,非常狼狈。

进一步的加固还有:限制可登录用户为特定账号(AllowUsers)、限制来源IP、启用Fail2Ban监控重复认证失败等。这些做完之后,服务器的SSH暴露面会变得非常小。

最后,建议每半年左右轮换一次服务器登录密钥。轮换流程不复杂:本地生成新密钥对,用旧通道写入新公钥,验证新密钥能登录,再把旧公钥从authorized_keys里删掉。整个过程完全可以在十几分钟内完成,却能避免很多长期积累的隐性风险。

我在实际项目里还养成了一个习惯:每次配好一台新服务器的免密登录,就把ssh -v的调试输出留一份归档。表面看是多余,实际上后面服务器出问题、需要回溯当时认证链路状态时,这些记录比任何文档都准确。判断SSH配置是否成功,最可靠的标志永远是那一声干净利落的连接建立,而不是任何界面上的"配置成功"提示。希望你也能一次搞定,后面就真的再也不用碰密码了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询