SSH免密登录原理与配置:从密钥生成到排错实战
做运维这些年我每天打交道最多的就是服务器登录。只要是Linux环境就绕不开ssh而ssh绕不开的一件事就是免密登录。网上关于这个主题的教程一抓一大把但很多资料只告诉你要把本地密钥上传到服务器却说不清楚为什么这样就能免密结果就是照着敲完命令要么权限不对要么配置没生效最后还是老老实实输密码。这篇文章我就把本地密钥上传、免密登录这一整条链路彻底讲清楚。我会从密钥认证的原理说起把生成密钥、上传公钥、调整权限、排查故障这几步拆开揉碎最后再聊聊多台服务器批量登录、配合Git和编辑器使用这些日常场景。你可能是刚入行的开发也可能是被服务器折腾够呛的运维只要想告别密码输入这篇文章都值得看完。1. 免密登录的本质为什么“上传密钥”能代替输密码1.1 先搞懂上传的是什么“公钥”不是“钥匙”很多新手第一次操作时会有一个误解——以为免密登录是把本地保存的密钥文件直接传一份到服务器服务器和客户端各存一份相同的“钥匙”登录时对上号就行。这个理解是错的而且错得很危险。SSH免密登录用的是一种非对称加密方案也就是密钥对。每个人本地会生成两个文件一个叫私钥一个叫公钥。私钥必须永远留在你自己的电脑上谁都不能给公钥则是可以公开的随便分发也没关系。我们要上传到服务器的是公钥不是私钥。用生活里的例子来说公钥是锁私钥是开锁的钥匙。服务器上装了一把“锁”这把锁人人可见但只有持有你本地那把“钥匙”的人才能把它打开。整个过程里私钥从头到尾不会离开你的电脑服务器上也永远不会有你的私钥。注意如果你在操作时不小心把私钥文件比如id_rsa、id_ed25519传到了服务器上第一件事就是立刻在服务器上删除它同时重新生成一对新密钥。私钥泄露意味着任何拿到它的人都能冒充你登录所有配置过对应公钥的机器。1.2 服务器是怎么“认”出你的一次握手背后的数学验证理解了公钥和私钥的分工再看登录流程就顺了。当你执行ssh userhost时客户端和服务器之间会进行几次交换我按顺序给你拆开服务器出示身份信息服务器把自己的主机密钥指纹发给客户端客户端会检查这个指纹是不是之前信任过的。这就是你第一次连接某台服务器时看到的“确认指纹”提示的由来。客户端证明自己持有私钥服务器想验证你是不是一个合法用户但它不会直接向你要私钥。相反它生成一串随机数据发给你要求你用私钥对这串数据做签名然后把签名结果发回去。服务器用公钥验证签名服务器上存着你的公钥它用公钥去验证客户端发回来的签名。只要签名和随机数据对得上服务器就能确定对方确实持有与公钥匹配的私钥于是允许登录。这里有个很关键的设计思路私钥从不传输只参与本地运算。所以即便网络被监听、服务器被入侵攻击者拿到的也只是“锁”没有“钥匙”。相比起密码——每登录一次就要在网络上传一次——密钥认证的安全性要高一个量级。想不明白的话可以把它类比成你去银行取钱。柜员不会问你要存折密码的明文而是让你在键盘上按几个数字他只看你按得对不对。你没有把密码喊出来柜员也没有真的“看到”密码但你们双方都完成了验证。SSH的签名验证就是这个逻辑的加密版本。2. 从零到免密生成密钥、上传公钥的完整操作2.1 本地生成一对密钥选对加密算法很重要不管是Windows、macOS还是Linux本地生成密钥用的都是同一个命令ssh-keygen。我建议选Ed25519算法而不是以前常用的RSA。# 生成Ed25519密钥对-C是注释通常写你的邮箱或机器名 ssh-keygen -t ed25519 -C my-work-laptop # 如果一定要用RSA某些老旧系统只支持RSA指定2048位以上 ssh-keygen -t rsa -b 4096 -C my-work-laptop命令执行后系统会让你选择密钥保存路径默认是~/.ssh/id_ed25519直接回车即可。接下来的提示是“设置私钥口令短语”passphrase很多人会直接跳过但我强烈建议设置一个。设置passphrase之后私钥文件被加密存储每次使用私钥时都要输入一次口令。这确实增加了一步操作可一旦笔记本电脑丢了光有私钥文件别人也解不开。想要方便和安全的平衡可以通过ssh-agent来记住口令这个后面我会专门说。生成成功后你的~/.ssh目录下会出现两个文件id_ed25519私钥权限必须是600绝对不能让其他人读。id_ed25519.pub公钥内容是一长串字符可以放心给别人。2.2 最省事的上传方式ssh-copy-id一条命令现在的主流Linux发行版和macOS一般都自带ssh-copy-id这是上传公钥最不容易出错的方式。# 把本地的公钥安装到服务器上 ssh-copy-id -i ~/.ssh/id_ed25519.pub root192.168.1.101 # 如果服务器ssh端口不是默认的22加-P指定端口 ssh-copy-id -i ~/.ssh/id_ed25519.pub -P 2222 root192.168.1.101这条命令会自动完成以下几件事登录服务器。检查服务器上~/.ssh目录是否存在不存在就创建。把本地公钥追加到服务器的~/.ssh/authorized_keys文件末尾。自动设置好~/.ssh和authorized_keys的正确权限。第一次执行时它会要求输入服务器密码这是整个流程中唯一一次使用密码登录。等命令跑完你再执行ssh root192.168.1.101会发现直接进去了不再提示输密码。2.3 没有ssh-copy-id怎么办手动追加公钥有些精简系统或Windows环境里没有ssh-copy-id这时候可以用手动方式追加公钥。核心思路就是把本地的公钥内容通过网络发送到服务器通过管道直接追加到authorized_keys文件。cat ~/.ssh/id_ed25519.pub | ssh root192.168.1.101 mkdir -p ~/.ssh chmod 700 ~/.ssh cat ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys这条命令看起来长其实拆开理解很简单。cat ~/.ssh/id_ed25519.pub先读出本地公钥内容。|把内容通过管道送给后面的ssh命令。ssh root192.168.1.101 ...远程执行一段shell脚本。mkdir -p ~/.ssh chmod 700 ~/.ssh确保目录存在并把目录权限设置为700。cat ~/.ssh/authorized_keys把收到的公钥内容追加到文件末尾。chmod 600 ~/.ssh/authorized_keys把文件权限设置为600。关键在于cat 是追加模式不是覆盖。这保证了如果你已经在这台服务器上配置过其他公钥不会被新公钥冲掉。很多人在这一步犯错用了cat 等号结果把服务器上已有的公钥全清空了其他同事全被“踢”了出去。这种事故我在生产环境里见过不止一次真遇上了会非常尴尬。2.4 我先上传再手动处理scp方式的完整流程还有一种经常被问到的上传方式用scp把公钥文件传到服务器上再在服务器端手动追加。这种方式适合你想在服务器端多检查一步、或者不想通过管道直接操作的情况。# 第一步把本地公钥文件拷贝到服务器临时位置 scp ~/.ssh/id_ed25519.pub root192.168.1.101:/tmp/mykey.pub # 第二步登录服务器手动追加 ssh root192.168.1.101 mkdir -p ~/.ssh chmod 700 ~/.ssh cat /tmp/mykey.pub ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys rm /tmp/mykey.pub这种方式的好处是你可以在服务器上先看一眼公钥内容确认没有乱码再追加。坏处是多了一步临时文件操作忘记删除的话会留下一个公钥文件躺在/tmp里。虽然没有私钥那么敏感但养成随手清理的习惯总归是好的。3. 密钥上传了还是不停输密码先查权限和sshd配置3.1 权限检查清单700、600、644一个都不能错密钥上传成功只是第一步。我排查过无数“明明上传了公钥还是免密失败”的问题十有八九出在权限上。OpenSSH对文件权限极度敏感只要相关文件权限过宽服务器直接拒绝读取公钥登录请求就像石沉大海。服务器端需要检查三个位置的权限对象正确权限权限过宽的后果用户家目录/root或/home/user755或700不能是777sshd拒绝处理该用户的密钥认证~/.ssh目录700即drwx------报错“bad permissions”~/.ssh/authorized_keys文件600即-rw-------密钥文件不被读取登录仍然要密码本地私钥文件~/.ssh/id_ed25519600客户端直接拒绝使用私钥本地私钥权限不对的话客户端的表现很典型执行ssh命令后它不会问你密码而是提示“Permissions too open”意思是你的私钥文件全世界都能读它为了安全拒绝使用。修复权限的命令就三行最好一键敲完chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys chmod 600 ~/.ssh/id_ed25519注意如果你发现authorized_keys文件在你上传之前就已经存在还要顺带检查一下它的属主。服务器要求这个文件必须归属于你当前登录的用户不能是root也不能是别人。先ls -l ~/.ssh/authorized_keys看一眼再决定是否要chown。3.2 sshd_config里决定密钥登录命运的开关权限没错但依然无法免密那就要看服务器端的SSH服务配置了。配置文件一般是/etc/ssh/sshd_config不同发行版路径大同小异。用grep快速检查这几个关键项sudo grep -E ^PubkeyAuthentication|^PasswordAuthentication|^AuthorizedKeysFile|^PermitRootLogin /etc/ssh/sshd_config正常情况下看到的结果应该是PubkeyAuthentication yes PasswordAuthentication yes AuthorizedKeysFile .ssh/authorized_keys PermitRootLogin prohibit-password几个字段分别是什么意思PubkeyAuthentication决定服务器是否接受公钥认证。如果被改成no你有再多的公钥也没用服务器直接不跟你玩这套。AuthorizedKeysFile指定从哪个文件读取公钥默认是.ssh/authorized_keys保持默认即可。有人会改成绝对路径或者自定义文件名改成之后自己又忘了排查时容易绕弯子。PermitRootLogin分为yes、no、prohibit-password几种。prohibit-password表示root可以登录但禁止密码认证只能用密钥登录。如果你用root账号却一直免密失败先看看是不是这里写了no。PasswordAuthentication它决定服务器允不允许密码登录。很多教程喜欢直接改成no来强制全员使用密钥但我不建议你在没有完全切换好的时候就这么干。万一有人还没配置密钥你改完配置重启sshd他就彻底进不来了自己也跟着被锁在外面。正确的做法是先保证所有该配置的人都配好密钥验证免密登录稳定了再考虑关闭密码认证。修改配置后必须重启服务才能生效不同系统命令有所区别# systemd系统CentOS 7、Ubuntu 15.04 sudo systemctl restart sshd # 或叫ssh服务 sudo systemctl restart ssh # 老式SysVinit系统 sudo service sshd restart3.3 SELinux环境下的额外一步这一条主要说给CentOS、RedHat系的用户。这些系统默认开启SELinux即使你的权限和配置全对SSH读取公钥时仍可能被SELinux拦截。排查时你可以试试临时关闭SELinux验证一下但生产环境我不建议用“关闭”来解决问题。正确的做法是恢复正确的上下文标签sudo restorecon -Rv ~/.ssh如果你是自己手工在/root下创建的特殊目录来存公钥还需要检查目录的SELinux类型是否为sshd_key_t不是的话用chcon调整。这一块知识比较冷门但很多老运维在CentOS上免密失败最后就是被这一步卡住的。4. 免密失败排错链路我从ssh -v输出里看到了什么4.1 首选手动排错工具ssh -v和sshd日志权限、配置、SELinux都检查过了还是连不上那就别再瞎猜了直接上调试命令。客户端执行ssh -v root192.168.1.101 # 想看到更详细的信息可以再加一个-v最多支持三个-v ssh -vvv root192.168.1.101-v输出的信息非常直白它会把连接过程中每次尝试认证的细节都打出来。我教你挑重点看看有没有进入认证阶段输出里会出现Authentications that can continue: publickey,password看到这一行说明服务器已经做好接受认证的准备。看客户端有没有发出公钥输出里会有Offering public key:后面跟着密钥路径。如果一直不出这一行说明客户端压根没打算用你的私钥常见原因是私钥文件没有加入ssh-agent或者本地没有可用的私钥。看服务器有没有接受如果看到Server accepts key: /root/.ssh/id_ed25519说明服务器验证通过接下来就该直接登进去了。如果只看到Offering public key然后紧跟着Authentications that can continue: publickey,password说明公钥被服务器拒绝了问题出在服务器端。服务器端日志也是个好东西。在服务器上执行sudo tail -f /var/log/secure # CentOS、RHEL系列 sudo tail -f /var/log/auth.log # Ubuntu、Debian系列然后从客户端重新发起一次登录观察日志里有没有关键词比如authentication failure、invalid user、Permission denied、Unable to authenticate。日志会具体告诉你失败的根本原因比你在客户端瞎猜高效得多。4.2 高频故障对照表照着查比猜效率高我把几年里遇到过的免密失败原因整理成了一张对照表每一条都是实战验证过的。表现根因解决办法客户端提示“Permission denied (publickey,password)”公钥没被服务器接受检查公钥是否真的追加到了正确的authorized_keys客户端提示“Bad owner or permissions”本地私钥或服务器authorized_keys权限过宽执行chmod 700/600/644标准权限修改提示“Connection refused”sshd服务没启动或端口不对重启sshd或检查端口配置提示“Host key verification failed”服务器的指纹变了本地known_hosts认证冲突从known_hosts中删除旧指纹重新连接能登录root但无法登录普通用户普通用户家目录或authorized_keys权限/属主不对检查并chown给对应用户客户端提示“Permission denied”但加了-A可以ssh-agent里加载了错误私钥清空agent并重新添加正确私钥服务器日志出现“read_key_file: bad permissions”authorized_keys权限不够严格改为600服务器日志出现“maximum authentication attempts exceeded”尝试次数过多或本地有多个私钥反复被试用配置ssh-agent、指定-i参数明确该使用哪个私钥4.3 最容易瞒天过海的一个坑authorized_keys内容格式问题有时你什么都对权限也改了、配置也正常就是登不进去。然后你手动打开authorized_keys一看公钥确实在里面但挤成了一行没有任何换行或者末尾多了个看不见的回车符。手动追加公钥时最容易出现这种问题。比如你从网页或聊天工具里复制公钥内容粘贴时被自动换行、空格污染了。OpenSSH对公钥内容是一行一行解析的公钥格式是由“类型 密钥内容 注释”三段组成段之间用空格分隔。一旦格式被打乱、被截断、多了换行服务器就无法解析直接忽略这一条。遇到这种问题最简单的方法是回到本地重新复制公钥内容注意完整复制ssh-ed25519 AAAA... 你的邮箱整行不要自己手动换行。在服务器上编辑完authorized_keys后可以用下面命令验证一下格式是否合法# 在服务器上执行逐个读取并校验公钥行 ssh-keygen -lf ~/.ssh/authorized_keys这个命令会把authorized_keys里每一条公钥的指纹都列出来。如果某条公钥格式不对它会直接报错停在那一行。能列出指纹说明这行公钥基本没问题。5. 免密登录上路之后多主机、Git和自动化工具的使用心得5.1 台式机上有20台服务器用config文件给每台机器起别名当你手头管理的服务器多起来之后每次登录都要敲ssh root192.168.1.101虽然能免密但这串地址依然不够友好。我更习惯在~/.ssh/config里维护一份连接配置。这个文件不存在就自己创建格式非常简单Host prod-web1 HostName 192.168.1.101 User root Port 22 IdentityFile ~/.ssh/id_ed25519 Host dev-db HostName 192.168.1.202 User deploy Port 2222 IdentityFile ~/.ssh/id_ed25519配置好之后登录直接写别名ssh prod-web1 ssh dev-db这个配置文件的好处不仅仅在于少打几个字。你的每台服务器可以指向不同的用户名、不同端口、不同的私钥随时可以改。配合你已有的免密认证整个体验非常接近“点图标进电脑”的感觉敲命令的速度能快好几倍。5.2 本地密钥上传不只服务于ssh登录Git和VSCode一样受益很多人配置免密登录不是为了天天敲命令而是为了让工具跑得更顺手。先说Git。你在GitHub、GitLab、Gitee上配置SSH密钥本质上和刚才做的“上传公钥到服务器”是一回事。Git仓库服务器并没有你的密码它只保存你的公钥每次git push时用私钥签名来确认是你的操作。# 查看本地是否已有密钥 ls ~/.ssh/id_ed25519.pub # 没有的话生成一个专用的 ssh-keygen -t ed25519 -C git-account # 把公钥内容添加到Git平台后台 cat ~/.ssh/id_ed25519.pub再说VSCode。用VSCode的Remote-SSH插件连远程服务器开发时每次连接都弹密码框会让你崩溃。你在终端里配置好免密登录后VSCode连接时其实是复用同一套密钥认证不需要额外配置直接就能连上。前提是操作系统能把密钥喂给VSCode进程Windows下建议先确保ssh-agent服务运行。5.3 批量自动化脚本和工具真正用上了“免密”免密认证的最高价值体现在没有人在场时机器自己就能把事干完。我在自己服务器上写过不少定时备份脚本脚本里需要从一台机器ssh到另一台机器拉取数据。如果登录还需要密码脚本就只能在终端里手动执行配置好密钥免密以后脚本放入crontab就能定时运行完全不需要人工介入。# 一个非常简单的远程拉取示例 scp rootprod-web1:/var/log/nginx/access.log /opt/logs/access-$(date %F).log这只是最基础的用途。像Ansible这类自动化工具底层连接方式默认就是SSH密钥认证。你需要在管控机上配置好多台主机的免密登录才能执行一句ansible all -m ping去批量探活、批量改配置。没有密钥认证这些工具根本没法发挥“批量”的价值。使用密钥认证的时候客户端会用到ssh-agent这个组件来管理私钥。简单说你把私钥“喂”给它之后它会记住私钥和口令之后连接时自动把私钥提供给服务器全程不用你再输入口令。把私钥加入agent的命令是ssh-add ~/.ssh/id_ed25519 # macOS下如果遇到权限问题加-K参数写入钥匙串 ssh-add -K ~/.ssh/id_ed25519 # 查看当前agent里加载了哪些私钥 ssh-add -l这里有几点实操心得如果你的私钥设置了passphrase第一次ssh-add会要求输入口令之后agent会帮你记住后续连接不用重复输入。Windows的OpenSSH默认会启动ssh-agent服务但不是总在运行在“服务”里找到“OpenSSH Authentication Agent”设为自动启动即可。如果经常出现“太多认证尝试失败”多半是agent里塞了一堆私钥服务器挨个验证都失败才报错。用ssh -o IdentitiesOnlyyes roothost或者config里指定IdentityFile可以解决。5.4 别急着关闭密码登录先做一个稳妥的过渡很多安全教程都鼓动你马上把PasswordAuthentication改成no但直接从密码切到密钥中间有很多意外可能让你彻底失联。我建议的稳妥顺序是生成密钥并上传到服务器确保所有日常登录的电脑都能免密登录。连续一周日常操作都使用密钥登录确认稳定。修改sshd_config里PasswordAuthentication no重启ssh服务。开着另一个终端窗口保持已连接的会话防止重启服务时配置写错导致连接断开。新开一个窗口测试密钥登录成功后保持观察一段时间再把旧的密码登录方式彻底清理。如果你管理的是公司生产环境的服务器这个过渡周期更要拉长而且要通知到所有可能登录这台机器的同事。我曾见过有人刚改完配置重启第二天发现业务系统连不上数据库——因为某个自动化脚本还在用密码方式做跳板登录。生产环境里的任何改动务必先确认依赖关系。结尾的一点实际经验做这行越久越觉得ssh免密登录这种基础功能真正用好了还是很有成就感的事。它省掉的不只是每天几十次输密码的重复劳动更重要的是打开了自动化操作的空间——定时备份、日志拉取、批量部署、远程调试全都因为有了这层密钥信任关系才变得顺滑。我最想提醒后来人的一件事是私钥的安全比免密的便利重要得多。平时勤用ssh-agent、给私钥加passphrase、服务器上严格留痕、定期轮换密钥这些习惯如果从一开始就养成后面就不会因为一次泄露搞到焦头烂额。免密登录本质上是把信任关系建立在密钥上那就更要管好自己手里的钥匙。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →