ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

SSH免密登录实战:密钥生成、公钥部署与权限排查全指南

SSH免密登录实战:密钥生成、公钥部署与权限排查全指南

1. SSH免密登录,到底免的是什么

做运维和开发的同学,几乎每天都要跟远程服务器打交道。登录服务器最烦的事情是什么?就是每次都要输密码,尤其是现在密码策略越来越严,动不动要求十六位混大小写带特殊字符,一天要敲几十遍,谁受得了。SSH免密登录解决的就是这个问题——生成一对密钥,把公钥放到服务器上,之后每次ssh过去直接进,不用再敲密码。这套机制不仅能让你少打几十遍密码,更重要的是它消除了密码被暴力破解的可能性,安全性反而更高。

我是做服务端开发的,平时要管理好几台服务器,有测试环境也有生产环境。最早我也是老老实实输密码,后来有一次要批量执行几十台机器的命令,密码输得我手快断了,才下定决心把免密登录一次性搞定。弄完之后回头看,整个过程其实不复杂,核心就三步:生成密钥对、把公钥放到服务器、配置并验证登录。但这三步里藏了不少细节,比如说密钥类型选哪种、权限该给多少、为什么明明公钥放上去了还是报错,这些坑我全踩过。这篇文章就从零开始,把每个环节掰开揉碎了讲清楚,保证你看完能一次配通。

这套技术适合谁来用?方向其实很广。如果你是刚入行的运维或者后端开发,配置免密登录是基本功;如果你自己买了一台云服务器跑个人项目,每天要登上去看日志、重启服务,配了免密之后体验会舒服很多;哪怕你只在本地虚拟机里折腾Linux环境,这套流程也完全一致。不管你的场景是哪种,原理和操作都是同一套,学会了一次搞定,后面全复用。

2. 从零创建你的第一对密钥

2.1 ssh-keygen的完整参数解读

生成密钥用的命令是ssh-keygen,这个命令在Linux、macOS、Windows(装了OpenSSH客户端)上都是标配,不需要额外安装。我先说最基础的用法,一行命令:

ssh-keygen -t ed25519 -C "my-server-key" -f ~/.ssh/myserver

乍一看三个参数有点懵,挨个解释一下:

  • -t:指定密钥类型。你可以理解为“加密算法”,常见的有ed25519、rsa、ecdsa。我后面会专门讲类型怎么选,这里先记住ed25519是当前推荐选项。
  • -C:注释。这个参数就是给密钥加一个备注信息,通常写自己的邮箱或者用途,比如dev@example.com或者prod-backup-key。它的作用是让你在管理多把密钥的时候能分清哪把是哪把,不影响功能,但强烈建议写上。
  • -f:指定生成的文件名。如果不加这个参数,ssh-keygen会默认生成~/.ssh/id_ed25519或者~/.ssh/id_rsa。我习惯明确指定文件名,因为管理多把密钥时一一对应更清晰。

执行这条命令之后,终端会问你Enter passphrase (empty for no passphrase),这里是在问你给不给私钥加一个额外的保护口令。关于passphrase我多说几句:它不等于服务器密码,而是对你私钥文件本身的一层加密。如果设置了,每次使用这把私钥时都要输入这个口令,相当于在免密的基础上加了一道保险。如果留空,私钥文件就是裸奔状态,谁拿到了文件谁就能用它登录。那到底设不设?我个人建议:笔记本电脑这种个人设备可以不设,图的就是开机即用;如果是共享机器或者需要长期保存的备份密钥,建议设上,配合ssh-agent可以达到“设备上只输入一次、后续自动用”的效果,后面我专门讲。

2.2 密钥类型与位数怎么选

密钥类型这个问题,我在踩过坑之后才真正重视起来。很多老教程还在推荐用RSA,而且强调位数要4096,这个说法不算错,但眼下有了更好的选择。

先给一个对照表,方便你做决策:

密钥类型算法特点密钥长度安全性兼容性推荐度
ed25519现代椭圆曲线算法,速度快,签名短固定256位高OpenSSH 6.5+(2014年后)首选
rsa经典算法,生态兼容性最广2048/4096位高(4096更强)几乎所有SSH版本兼容场景首选
ecdsa椭圆曲线算法256/384/521位中部分旧设备支持不佳不常用
dsa已废弃的老算法1024位低现代系统已默认禁用不要用

我的实际建议是:全新的机器、全新环境,直接用ed25519。原因很简单:它的签名速度更快,私钥文件更短,安全性在同等比特数下比RSA更好。OpenSSH从6.5版本开始就支持了,现在你手头的服务器如果系统版本不是那种十年不更新的古董,基本都能用。

但有一种情况要换成RSA:你需要在一些老旧设备上做免密,比如某些嵌入式设备、老版本的交换机、特定型号的存储设备,它们的SSH实现可能只认RSA。这时候老老实实用RSA 4096,别纠结,兼容性优先。我之前就遇到过一台老机器,只支持RSA,ed25519的公钥送上去完全不认,后来换了RSA才搞定。

还有一个小提示:不管是哪种类型,ssh-keygen默认生成的私钥是没有注释的,但公钥文件里那串字符串末尾会带上你指定的-C内容。公钥文件里那串长长的ssh-ed25519 AAAA...就是公钥本体,它长这样:

ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK5xUxVdBQ9r3uR2Pv8RgXmysqlLJbByW8NQ7H1Uuqz my-server-key

公钥是可以随便给人看的,密钥对的安全性完全取决于私钥的保密程度,这两者的关系后面讲原理时会说清楚。

2.3 从零实操:完整生成流程

空谈没用,直接上一段完整实操。假设我现在要给一台新服务器配置免密,服务器IP是192.0.2.10,登录用户是dev,端口是默认的22。第一步,在本机生成密钥对:

ssh-keygen -t ed25519 -C "dev@myhost" -f ~/.ssh/dev_server

终端提示输入passphrase,我这边个人笔记本选留空,直接回车两次。此时~/.ssh/目录下会多出两个文件:

  • dev_server:私钥,这个是核心机密,绝不能外传。
  • dev_server.pub:公钥,可以放心部署到服务器上。

验证一下文件是否生成:

ls -l ~/.ssh/dev_server*

能看到两个文件,权限分别为-rw-------(私钥,600)和-rw-r--r--(公钥,644)。如果私钥权限不对,比如是644,后面SSH连接时会直接报错,所以第一次生成完就养成检查权限的习惯。

有人会问:我看到的教程标题里常写“SSH免密登录”,为什么有些老教程里是“RSA免密登录”?因为RSA是当年最主流的算法,很多写教程的人沿用了这个说法。算法本身一直在演进,你只要理解“创建密钥对”的本质就行,具体用什么算法(ed25519还是rsa)按我上面的表格选。

到这里,密钥对已经握在手里了。下一步就是怎么把公钥放到服务器上,让它认你。

3. 把公钥送上服务器

3.1 最省事的方式:用ssh-copy-id一键推送

生成密钥之后,你需要把.pub公钥文件的内容追加到服务器上对应用户的~/.ssh/authorized_keys文件里。这是一个专门存放“允许谁免密登录”的清单文件,一行一个公钥。

最省事的工具是ssh-copy-id,它把这个过程封装成了一步命令。以刚才生成的dev_server密钥为例:

ssh-copy-id -i ~/.ssh/dev_server.pub dev@192.0.2.10

注意-i后面跟的是公钥文件路径,不是私钥。执行后它会提示你输入服务器密码(这是最后一次输密码),输入正确后,它会自动创建~/.ssh目录、写入authorized_keys、设置好权限。整个过程其实做了好几件事,我拆开说一下:

  1. 连接服务器的22端口,用密码完成身份验证。
  2. 检查当前用户的家目录下是否存在.ssh目录,没有就自动创建。
  3. 把目标公钥追加到~/.ssh/authorized_keys文件末尾。
  4. 顺手修正权限:.ssh目录设为700,authorized_keys文件设为600。

如果你刚才没用-f指定文件名,而是生成了默认的id_ed25519,那可以直接执行ssh-copy-id dev@192.0.2.10,它默认找~/.ssh/id_*.pub这种形式的公钥文件。

如果你的服务器SSH端口不是22,比如是2222,需要加-p参数:

ssh-copy-id -i ~/.ssh/dev_server.pub -p 2222 dev@192.0.2.10

这一步做完,理论上免密配置已经完成了。但我不急着测,先讲手动方式,因为你会遇到各种环境,有时候ssh-copy-id不可用(比如Windows原生终端没有这个命令,或者目标机器被限制了密码登录),就得手动来。

3.2 手动部署方式与权限校准

手动部署的原理和ssh-copy-id完全一样,就是自己完成那几步。第一步,先看本机公钥的内容,用cat查看:

cat ~/.ssh/dev_server.pub

输出是一长串,全选复制。第二步,登录服务器:

ssh dev@192.0.2.10

第三步,在服务器上执行以下命令:

mkdir -p ~/.ssh chmod 700 ~/.ssh echo "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIK5xUxVdBQ9r3uR2Pv8RgXmysqlLJbByW8NQ7H1Uuqz dev@myhost" >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys

这里的关键点有两个:

一是echo必须用>>追加而不是>覆盖。如果这台服务器上authorized_keys已经存在其他人的公钥,>会直接清空文件,等于把别人的免密配置全毁了。我见过不止一次这种事故,多个运维共用一台机器,新来的用>覆盖,结果所有人的钥匙都失效了。

二是权限必须精确匹配。服务器上的sshd服务对权限非常敏感,它发现~/.ssh目录权限是755、或者authorized_keys是644,会直接拒绝用这些公钥完成认证,日志里会提示“bad permissions”。原因很直接:如果目录和文件能让其他用户写入或修改,那公钥文件就可能被篡改,认证也就不安全了。

3.3 为什么权限一定要用600和700

我单独把权限拎出来说,是因为这是我踩坑最多的地方。很多人(包括当年的我)在服务器上把公钥粘贴进authorized_keys后就完事了,结果一连接就报Permission denied (publickey),排查半天,最后发现就是文件权限多了一个可读权限。

SSH的校验逻辑是这样的:服务器端的sshd在启动时会读取authorized_keys,但它不是无脑信任这个文件,而是在读取前检查文件的属主、权限和路径安全性。具体要求如下:

  • 登录用户的家目录必须是当前用户拥有,且不能是组可写或其他人可写。
  • ~/.ssh目录的属主必须是当前用户,权限一般不能超过700(即drwx------)。
  • ~/.ssh/authorized_keys的属主必须是当前用户,权限不能超过600(即-rw-------)。

组可写、其他用户可读都不行。因为SSH安全模型假设同一台机器上的其他用户也可能靠本地漏洞读取文件,如果你把授权文件的权限放得太开,就相当于给了别人一把可复制的钥匙。实际排查的时候,可以一条命令检查关键文件:

ls -la ~ | grep .ssh ls -la ~/.ssh

输出应该长这样:

drwx------ 2 dev dev 4096 Jan 15 10:00 .ssh -rw------- 1 dev dev 400 Jan 15 10:01 authorized_keys

如果你发现权限不对,用chmod纠正:

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

这两条命令几乎每个SSH免密教程里都会有,但其背后的原因很少有人说透。明白原理之后,遇到类似问题你会更快地判断根源,而不是漫无目的地乱试。

4. 免密登录的配置与验证

4.1 用~/.ssh/config管理多台服务器

公钥部署好之后,基础免密登录已经可以用了。直接连一下试试:

ssh dev@192.0.2.10

如果一切正常,你会直接进入服务器的shell界面,没有任何密码提示。如果你的服务器端口不是22,需要显式指定:

ssh -p 2222 dev@192.0.2.10

到这里“免密登录”已经从目标变成了现实。但实际工作中我们往往不只管理一台服务器,如果每次都要敲ssh -p 2222 dev@192.0.2.10 -i ~/.ssh/dev_server这种长命令,体验依然不好。这时候就要用~/.ssh/config文件,给每台服务器配置一个短别名。

配置文件在~/.ssh/config,没有就新建一个。写入内容如下:

Host myserver HostName 192.0.2.10 User dev Port 2222 IdentityFile ~/.ssh/dev_server IdentitiesOnly yes

逐行解释一下:

  • Host:你给这台服务器起的别名,连的时候直接ssh myserver。
  • HostName:真实域名或IP地址。
  • User:登录用户名。
  • Port:SSH端口,默认是22可不写。
  • IdentityFile:指向这把密钥对应的私钥文件路径。
  • IdentitiesOnly yes:只使用这里指定的密钥文件,不默认尝试~/.ssh下的所有私钥。这一行很重要,否则本机密钥多了以后,SSH会依次尝试所有密钥,一旦某把密钥在服务器上匹配失败,认证流程会被拉长,甚至触发服务器的防护策略。

配置好之后,连接就变成了:

ssh myserver

一个单词搞定。如果你有几十台服务器,这个方法能把你的日常操作简化到极致。而且不用记端口、IP、用户名,全部沉淀在配置文件里,换电脑的时候拷贝~/.ssh/config和~/.ssh/目录就能无缝迁移。

4.2 验证登录并查看调试信息

配置完免密后,最重要的不是“能连上”,而是“稳定地能连上”。我强烈建议你在第一时间用调试模式做一次完整验证:

ssh -v myserver

-v是verbose调试模式,它会打印整个连接过程中的日志。你会看到类似这样的关键阶段:

  1. Offering public key: /home/user/.ssh/dev_server ED25519 SHA256:xxx——客户端在向服务器出示这把公钥。
  2. Server accepts key: ed25519 SHA256:xxx——服务器端确认接受这把公钥。
  3. Authenticated to 192.0.2.10 ([192.0.2.10]:2222)——认证成功。

如果中途卡住了,输出停在某个阶段,比如一直在Authentications that can continue: publickey附近打转,说明密钥对没被服务器接受。这时候就进入下一步的排查流程。

一个容易忽略的细节是:验证时如果本机~/.ssh/下有多个密钥,SSH客户端默认会按顺序尝试所有私钥。如果某个私钥没有对应密码直接跳过,输出里会有一段load pubkey之类的记录。用IdentitiesOnly yes配合config文件可以避免这种多余的探索流程,因为指定了就用那一把。

4.3 从密码到免密的过渡期策略

有一种常见的场景:你的服务器现在还开着密码认证,你也已经配好了免密,但不敢直接把密码登录关掉,怕哪天密钥出问题把自己锁在外面。我建议采用过渡期策略,分两步走。

第一步,保持现状,免密和密码同时可用,观察一段时间。这期间你可以确认免密登录在各场景下都稳定,比如从公司网络登录、从家里登录、从手机终端登录都没问题。第二步,确认无误后,再考虑关掉密码登录。具体操作是修改服务器上的SSH配置文件,一般路径是/etc/ssh/sshd_config,把这几项改掉:

PasswordAuthentication no ChallengeResponseAuthentication no

然后重启SSH服务:

sudo systemctl restart sshd

注意:PasswordAuthentication和ChallengeResponseAuthentication都要关掉,只关第一个是不够的。有些系统默认开启ChallengeResponseAuthentication,它一开,服务器依然会提示输入密码。改完之后先用一个新终端窗口测试免密是否正常,确认能免密进入后,再小心地关闭当前的连接。千万不要全部关完才测试,一旦密钥有问题,那就真的进不去了。

过渡期实在是没把握的话,可以再留一个后门,比如服务器厂商的网页终端(VNC或云控制台)。万一密钥失效,还能通过网页终端进去修复。很多人吃过“改完配置顺手关掉密码,结果密钥对不上彻底锁死”的亏,这属于运维界最痛的几个失误之一,宁可多留一手,不要赌一把。

5. 常见问题与排查实录

5.1 常见的几种Permission denied

免密配置完成后,最常遇到的错误提示就那几类。我把它们列成一张速查表,方便你直接对照排查:

错误现象可能原因处理方向
Permission denied (publickey,password)公钥未正确写入,或私钥未匹配重新核对authorized_keys内容
Permission denied (publickey)公钥认证失败,且服务器禁用了密码检查密钥文件、权限,或先临时开启密码登录
Bad owner or permissions on /home/user/.ssh.ssh目录或文件权限过宽chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys
Load key "xxx": incorrect passphrase supplied私钥设置了口令,但输入有误重新输入正确的passphrase,或改用ssh-agent管理
Server refused our key服务器未接受这把公钥检查公钥是否追加到了正确用户的authorized_keys
Too many authentication failures本机密钥太多,逐一尝试时失败次数过多用IdentitiesOnly yes或-i指定具体密钥

我在实际工作中见过最多的情况就是第二种:publickey方式失败。原因通常有几个方向,一是公钥文件被用>覆盖写坏了,二是行尾有换行符问题(从Windows粘贴到Linux时容易带出\r),三是服务器上配置了AuthorizedKeysFile指向了别的文件路径。第三种情况比较隐蔽,需要看服务器配置:

grep -i authorizedkeys /etc/ssh/sshd_config

如果输出的是AuthorizedKeysFile .ssh/authorized_keys,那是默认值,没问题。如果指向了/etc/ssh/authorized_keys或者别的路径,说明公钥要写到指定位置,而不是用户目录下。

5.2 排查步骤:一步步定位问题

遇到连接失败不要慌,按照下面这个顺序排查,90%的问题五分钟内能定位:

第一步,确认基线:你用的是不是正确用户和IP。很多人以为自己连的是服务器A,其实config里写的是服务器B的地址,连接失败当然正常。先执行不带简化的原始命令,比如:

ssh -i ~/.ssh/dev_server -p 2222 dev@192.0.2.10

如果原始命令失败而config方式成功,那是config配置有误;如果两者都失败,才进入下一步。

第二步,确认密钥文件路径:本机检查~/.ssh/下是否有dev_server和dev_server.pub,私钥权限是否为600。检查命令我一般这么写:

ls -la ~/.ssh/ ssh-keygen -y -f ~/.ssh/dev_server

ssh-keygen -y -f可以从私钥反推出公钥内容,如果执行报错说明私钥文件本身有问题,如果输出的内容和你的.pub文件不一致,说明私钥和公钥可能不配对。这种“文件内容对不上”的问题很少见,但一旦发生了很难想到,值得排查。

第三步,确认服务器端状态:用密码登录(或从其他已有免密的机器登录)服务器,检查authorized_keys里有没有你的公钥:

grep "ssh-ed25519" ~/.ssh/authorized_keys

不仅要确认存在,还要确认内容和你本机.pub文件一致。最容易出错的是复制时漏字或者多了空格,严谨比对一下。

第四步,确认服务配置和日志:如果前几步都没问题,就看服务器SSH服务的运行状态和日志。日志位置一般是/var/log/auth.log(Debian系)或/var/log/secure(RHEL系),搜关键字:

grep "Failed" /var/log/secure | tail -20

如果你常遇到这台服务器重启后免密失效的情况,那要考虑是不是authorized_keys写入的路径和用户实际使用的不一致,或者服务器的pam配置里有特殊限制。这种情况一般用sshd -T查看实际生效的配置:

sudo sshd -T | grep authorizedkeysfile

这招能直接看到当前有效路径,排查一次少走很多弯路。

5.3 踩过几次坑之后的一些真心建议

整个流程走完,把经验沉淀下来,有几点我真觉得值得单独说:

第一,密钥文件要起好名字,别全叫id_rsa。当你机器上积攒到三五个密钥之后,再回头看那些凭文件名认不出的密钥,会非常痛苦。我在命名上吃过亏,一开始全是默认id_rsa,后来一台服务器对应一个密钥,全都混在一起,连调试都不知道试的是哪把。现在我的命名规范是用途_环境,比如aliyun_prod_key、home_nas_key,配合config里的Host别名,一目了然。

第二,多台服务器的密钥尽量分开生成,尽量别“一把钥匙开所有锁”。虽然技术上完全可以,把同一份公钥复制到十几台服务器,一台设备一个密钥的做法的确是管理复杂度更高,但安全上要清楚:一把密钥的私钥泄露,所有服务器都会被攻破。我的做法是:测试环境共用的服务器可以共用一把,生产环境每台独立密钥,方便单独吊销。

第三,ssh-agent是个好东西,但大部分人没用明白。如果你在生成密钥时设置了passphrase,可以把它加入agent,让这个口令只输一次,之后在同一会话内不再询问:

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

ssh-add -l可以列出当前加载的所有密钥,ssh-add -D清空。注意agent只对当前会话有效,重启电脑后重新加载一次。配合~/.ssh/config里的AddKeysToAgent yes,可以在每次连接时自动把密钥加进agent,体验会顺滑很多。

第四,多设备同步密钥时要小心。如果你换了新电脑,只需要把原来那台设备的~/.ssh目录整体拷贝过去,尤其是私钥和config文件。拷贝时注意用scp或者rsync,最好用自己的免密通道,传输前校验一下源文件权限。切勿通过U盘在公共电脑上拷贝私钥,那等于把家门钥匙丢在街上。

第五,备份私钥并不意味着把它存到网盘或聊天记录里。如果你真的担心丢失,最稳妥的办法是本地加密压缩后放进加密盘,或者干脆多生成一把备用密钥放在离线存储设备中。凡事多做一手准备,别哪天服务器没变、你的电脑先坏了,私钥就再也找不回来了。

最后再分享一个非常实用的小习惯:每次配完一台服务器的免密,我都会顺手在本地执行一次ssh -v的完整连接,确认输出里的Authenticated to出现后再关闭终端。这个习惯看似多了一步,但正因为每次配完都立即验证,我很少因为“配完之后不知道到底成没成”而来回折腾。你以后配得多了会发现,免密登录这件事,真正花时间的不是敲那几行命令,而是排查那些藏在权限、路径、复制粘贴里的隐形问题。把这篇文章里的检查逻辑和排查顺序装进脑子里,以后再碰到任何SSH连接问题,你都不会一头雾水了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进