1. 天天输入SSH密码的日子,到底卡在哪里
如果你和我一样,手里管着三五台Linux服务器,每天的工作流离不开ssh user@host,那你一定懂这种痛:公司网络的密码策略三个月一换,测试机、生产机、跳板机的密码还不一样,每次登录都要从密码管理器里翻半天;好不容易输对了,过一会儿又要重新建会话,再来一遍。
我用Mac做主力开发,终端主要就是iTerm2。很早之前就想把SSH公钥免密登录配好,但一直拖到有一天连续在三台机器之间切换、输了整整六次密码之后,才下决心一次性解决。配完之后最大的感受是:这活儿真该第一天就做。整个过程不算复杂,核心就是SSH的公钥认证机制,加上iTerm2的Profile管理,做好之后“双击打开标签页直接进服务器”,跟开本地终端一样顺畅,再也不用记那堆容易混淆的密码。
这篇文章就把我从原理到实操、从最朴素的免密登录到iTerm2进阶配置的完整过程整理出来。适合这几类人看:刚把工作主力从Windows换成Mac、第一次认真配置SSH的开发者;手头有多台服务器、受够了反复输密码的运维或后端;以及明明配过公钥但偶尔还是被要求输密码、想彻底搞清楚原因的人。
需要说明的是,我接下来写的内容基于一个非常常见且稳妥的做法:在本机生成密钥对,把公钥放到服务器的authorized_keys文件里,利用SSH的挑战-响应机制完成免密认证。全程不涉及任何额外软件,纯靠macOS自带的OpenSSH和iTerm2本身就能搞定。
2. SSH公钥认证到底是怎么“对上暗号”的
很多人配SSH公钥免密的时候,只是照着命令敲了一遍,成功了就不管了,失败就网上搜答案。结果一遇到权限问题、密钥不生效、换机器连不上,就完全懵了。我之前也这样,直到有一次排查公司某台服务器的连接问题时,把原理彻底撸了一遍,之后所有奇怪问题基本都能自己定位了。
2.1 公钥和私钥的关系:锁在门外,钥匙在自己手里
想象一个场景:我有一间屋子,门上挂了一把锁。这把锁很特殊,锁头上明晃晃地刻着“锁扣样式A”,任何人都能看见这个样式,但只有我手里那把特制的钥匙能打开它。
放在SSH里,这个“锁扣样式A”就是公钥,可以随便给别人看、可以放在服务器上;那把“特制钥匙”就是私钥,必须死死攥在自己手里,绝对不能离开本机。服务器把公钥存进~/.ssh/authorized_keys文件,就等价于在门上挂好了锁。你每次发起SSH连接,本质上是向服务器证明“我有那把能打开锁的钥匙”。
关键点是:证明的方式不是“把钥匙递给服务器看看”,而是服务器出一道题,你用私钥“签名”之后交回去,服务器用公钥验签。签名的数学特性保证了——只有持有私钥的人才能做出有效签名,而公钥只能验证、无法伪造。整个过程中私钥数据永远不出现在网络里,这正是公钥认证安全性的根基。
2.2 挑战-响应认证流程:一句话版本
具体握手过程大致是这样:
- 客户端发起 SSH 连接,告诉服务器“我想用公钥认证,这是我的公钥指纹”。
- 服务器查
authorized_keys,找到对应的公钥,生成一段随机数据(挑战)发给客户端。 - 客户端用本地私钥对这个挑战做签名,把签名结果返回。
- 服务器用手里那把公钥验证签名,通过就放行。
整个过程没有密码在网络上传输,所以也不存在抓包抓到密码的问题。这跟传统的密码认证有本质区别——密码认证是把“秘密本身”交出去核对,公钥认证是“证明我知道某个秘密而不交出秘密”。
2.3 为什么authorized_keys和known_hosts不是一回事
初学者最容易混的就是这两个文件。
authorized_keys位于服务器端的~/.ssh/目录下,里面存的全是允许登录这台机器的用户公钥。它是“门上的锁扣样式”,每一位能免密登录该账户的用户,公钥都会以一行一行的形式出现在这个文件里。
known_hosts则在客户端,也就是你的Mac上,~/.ssh/known_hosts。它的作用是记住你曾经连接过的服务器指纹,防止中间人攻击。当你第一次连接一台新服务器时,SSH会提示“无法确认主机真实性,是否继续连接”,你输入yes之后,主机指纹就被记进known_hosts。之后如果服务器重装系统、密钥变了,SSH会发现指纹对不上,直接报Host key verification failed,宁可拒绝连接也不让你稀里糊涂地把数据发给一个可疑的“陌生人”。
理解这两者的分工之后,很多报错就很好猜了:authorized_keys的问题导致“不让你进”,known_hosts的问题导致“连都不让你连”。
3. macOS上生成密钥、投递公钥的完整流程
原理讲清楚了,下面进入实操。这一段是整篇的核心,我会把每一步的命令、参数、以及为什么要这样写都交代清楚,你照着做就能打通免密登录。
3.1 先看看本机是不是已经有密钥了
很多人在Mac上折腾过Git、配过代码托管平台,可能已经生成过SSH密钥。如果重复生成,倒也没什么破坏性,但会产生一堆用不上的文件,反而增加管理成本。所以第一步永远是检查:
ls -al ~/.ssh/如果看到id_ed25519和id_ed25519.pub,或者id_rsa和id_rsa.pub,那说明已经有密钥了。.pub结尾的是公钥,没有.pub的是私钥。
如果目录不存在,或者里面是空的,那就正式生成一对新密钥。
3.2 生成密钥:我为什么推荐Ed25519
生成命令很简单:
ssh-keygen -t ed25519 -C "用于公司服务器登录"这里两个参数值得说清楚。-t指定密钥算法,我推荐ed25519;-C是注释,一般写用途或自己的邮箱,方便以后在authorized_keys里一眼认出这把公钥是谁的。
关于密钥算法,我做过一个小对比:
| 算法 | 长度 | 安全性 | 兼容性 | 我的建议 |
|---|---|---|---|---|
| Ed25519 | 256位 | 高,现代密码学界广泛认可 | 需要OpenSSH 6.5+,2014年后的系统基本都支持 | 首选 |
| RSA | 传统建议4096位 | 足够,但需要更长密钥达到相当强度 | 极广,老系统也能用 | 备用方案 |
现在绝大多数服务器都是CentOS 7、Ubuntu 18.04往后的版本,Ed25519完全没问题。我自己的做法是日常服务器一律Ed25519,除非遇到某个特别老的嵌入式系统,才临时用RSA。如果你拿不准服务器能不能用Ed25519,那就用ssh-keygen -t rsa -b 4096,稳妥但生成慢一点、密钥长一点。
运行ssh-keygen之后,终端会问你要把密钥存到哪里,默认是~/.ssh/id_ed25519,直接回车即可。接着会问是否设置 passphrase(口令短语)。这个地方很多人直接回车留空,我建议还是设一个。
passphrase 相当于私钥的最后一层保险。如果私钥文件被拷走了,没有 passphrase 的人拿到私钥也白搭。担心麻烦?后面我马上讲怎么用ssh-agent让它只在第一次解锁,之后都自动放行。
3.3 让ssh-agent记住passphrase,而不是每次都问你
设置完 passphrase 之后,如果直接拿这把密钥去连接服务器,SSH会提示你输入私钥的 passphrase,每次连接都要来一遍的话,体验跟输密码也没差多少。
解决办法是让macOS自带的ssh-agent把解锁后的私钥“记在”内存里。先确认agent在运行:
eval "$(ssh-agent -s)"然后把私钥交给agent,同时让它把passphrase存进macOS的钥匙串(Keychain),这样连重启电脑之后都不用重新输:
ssh-add --apple-use-keychain ~/.ssh/id_ed25519第一次执行时,它会要求你输入一次私钥的passphrase,之后就不再问了。注意,--apple-use-keychain是macOS上OpenSSH的特定选项,如果你在某些Linux发行版上看到类似教程,命令不完全一样,那是正常的。
如果你希望以后每次新生成的密钥都自动进入这个“免打扰”流程,还可以在~/.ssh/config里加上两行配置,后面第4章会详细讲这个文件的写法,这里先提前贴出来:
Host * AddKeysToAgent yes UseKeychain yesAddKeysToAgent yes表示只要用到某个私钥,就让agent记住它;UseKeychain yes表示passphrase放钥匙串托管。配上之后,基本就是配一次、用到天荒地老。
3.4 把公钥投递到服务器:标准法和手动法
拿到公钥之后,需要把它放到服务器的~/.ssh/authorized_keys文件里。最省事的是用ssh-copy-id:
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server_ip这条命令会提示你输入一次服务器登录密码,输入之后,它自动把公钥追加到服务器的~/.ssh/authorized_keys,并设置好权限。
如果你手头那台服务器没有ssh-copy-id命令(个别精简版系统确实没有),可以用下面这条手动命令,效果一模一样:
cat ~/.ssh/id_ed25519.pub | ssh user@server_ip "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"拆开看就是:先ssh登录服务器,创建.ssh目录(如果不存在),把目录权限改成700,然后把本机公钥内容追加到authorized_keys,最后把该文件权限改成600。
这里有个必须养成的习惯:公钥文件写完之后,服务端的权限一定要严格检查。~/.ssh目录应该是700,authorized_keys文件应该是600。如果权限太宽松,比如~/.ssh是755,很多SSH服务器出于安全策略会直接忽略这个公钥,表现为“明明公钥放对了,还是让你输密码”。我在第5章会专门列这个坑。
3.5 首次连接:确认known_hosts并验证免密
公钥投递完成后,验证一下:
ssh user@server_ip如果是第一次连接这台服务器,会看到类似这样的提示:
The authenticity of host 'server_ip (x.x.x.x)' can't be established. ED25519 key fingerprint is SHA256:... Are you sure you want to continue connecting (yes/no)?这是SSH在问你要不要把服务器指纹写进known_hosts。确认这就是你要连的那台机器,输入yes回车。之后如果一切正常,你将直接进入服务器,全程不需要输入密码。
此时再顺手测试一下scp或rsync是否也免密,比如:
scp -i ~/.ssh/id_ed25519 localfile.txt user@server_ip:/tmp/没有提示输密码,说明整条链路彻底打通了。后面你会发现,不仅SSH登录爽了,写脚本做部署、用rsync同步文件都变得非常顺滑,因为脚本里不再需要处理“expect”之类交互密码的脏活。
4. iTerm2里更高效的连接组织方式
免密登录配好之后,基础体验已经提升了一大截。但如果你手里有多个环境、多个用户、多把私钥,光靠每台机器一条ssh user@ip还是有点低效。iTerm2的价值在这里就体现出来了。
4.1 用Profile把“主机、用户名、私钥、窗口标题”打包
iTerm2的Profile,通俗讲就是提前保存好的一套终端启动配置。你可以为每一台服务器建一个Profile,双击选中之后,它自动帮你打开一个标签页并执行连接命令。
操作路径:打开iTerm2,按Cmd + ,进入 Preferences,找到Profiles,左侧点+新建。右侧关键设置项如下:
- Name:给这个连接起个容易认的名字,比如
api-prod、web-test-01。 - Command:把默认的
Login Shell改成Command,然后在输入框里填:
ssh -i ~/.ssh/id_ed25519 user@server_ip如果你后面配置了~/.ssh/config(下面马上讲),这里甚至可以只写ssh api-prod,连IP和用户名都不用写。
- Working Directory:登录后进入什么目录。SSH登录一般跟本地目录无关,保持默认即可。
- Badge:这是iTerm2一个很有用的可视化功能,可以在标签页角落显示一块“铭牌”。我习惯设为
api-prod,这样一排标签页开着的时候,一眼就知道当前在哪台机器上,不用一个个点开看命令行的登录提示。
配置完成后,按Cmd + O会弹出Profile列表,键入名字回车,新标签页直接打开并连上服务器。整排标签页开着的状态下,Cmd + 数字可以快速切换,配合Badge文字,管理多台机器的体验接近无缝。
4.2 用SSH config统一管理主机信息,让命令更短
如果你有大量服务器,我更推荐先把主机信息统一写进~/.ssh/config,再配合iTerm2的Profile。这个文件是OpenSSH客户端的通用配置,macOS完全兼容。
一个典型的多主机配置长这样:
Host api-prod HostName 192.168.1.10 User deploy IdentityFile ~/.ssh/id_ed25519 AddKeysToAgent yes UseKeychain yes Host db-backup HostName 192.168.1.21 User backup IdentityFile ~/.ssh/id_ed25519_backup AddKeysToAgent yes UseKeychain yes Host * AddKeysToAgent yes UseKeychain yes配置完成之后,你可以在终端直接ssh api-prod,iTerm2的Profile里Command也可以简化为ssh api-prod。以后服务器IP变了,只需要改~/.ssh/config一处,所有Profile和命令行引用都不受影响。我的经验是,把能塞进Host配置的信息全塞进去,终端的输入量会大幅减少,也降低了记错IP、记错用户名的概率。
我自己按环境维护了一份表格,你可以参考着建:
| Host别名 | 用途 | 用户 | 私钥文件 |
|---|---|---|---|
| api-prod | 线上API服务器 | deploy | ~/.ssh/id_ed25519 |
| web-test-01 | 测试环境Web-1 | ubuntu | ~/.ssh/id_ed25519_test |
| db-backup | 数据库备份跳板 | backup | ~/.ssh/id_ed25519_backup |
| sftp-file | 文件传输专用 | sftpuser | ~/.ssh/id_ed25519_sftp |
多把私钥对应多台机器的时候,IdentityFile必须显式指定,不然SSH默认只尝试id_ed25519或id_rsa,错了就直接跳去用密码认证。
4.3 Trigger触发器:顺手解决sudo还要输密码的问题
很多时候,免密登录是通了,但登录之后执行sudo还要输入当前用户的登录密码,这依然会打断操作节奏。iTerm2的Trigger功能可以在这里帮上忙,只是使用时要稍微谨慎一点。
Trigger的配置思路是:终端输出里一旦出现特定正则表达式,iTerm2就自动执行预设动作。以sudo为例,步骤是:
打开对应Profile的Advanced->Triggers,点Edit,新增一条规则:
- 正则表达式:
(?i)password: - 动作:选择
Send Text,值填你的sudo密码\n(注意最后的\n,相当于回车)
以后这个Profile里只要出现password:提示,iTerm2就往终端里自动送去密码。真实体验是:sudo命令敲下去,还没反应过来,就已经回到提示符了。
但要提醒两点。第一,Trigger的密码是明文存在iTerm2配置文件里的,任何能读到这台Mac配置的人都能看到,所以务必只在个人设备、可信环境中使用;第二,正则匹配要写得收敛一点,别把SSH登录的密码提示也误触发了。如果你已经配好公钥免密,正常登录根本不会出现密码提示,Trigger只会在sudo这类场景里生效,冲突概率不大。说实话这个功能属于锦上添花,可配可不配,我用了很久没有出过问题,但如果你对安全极度敏感,跳过这一节完全不影响免密登录的体验。
5. 配置后最常见的坑和排查路径
我见过不少同事配置免密登录后,过一段时间突然失效,然后一脸懵。这里把我在实际工作中踩过、帮别人排查过的常见问题整理成一份清单,按概率排序。遇到问题先别急着删了重新配,对照着查一遍,通常十分钟内能定位。
5.1 权限不对导致的Permission denied
这是最高频的坑。表现形式是:authorized_keys里明明有公钥,SSH还是提示输入密码,或者干脆直接Permission denied (publickey,password)。原因基本跑不出下面这张权限表:
| 路径 | 属主 | 权限要求 | 原因 |
|---|---|---|---|
用户主目录~ | 登录用户 | 不能是其他用户可写,建议755或700 | 太宽松会被sshd拒绝信任 |
~/.ssh目录 | 登录用户 | 700 | 防止其他用户往里塞公钥或读私钥 |
~/.ssh/authorized_keys文件 | 登录用户 | 600 | 防止被篡改 |
排查命令:
ls -la ~ | grep ".ssh" ls -la ~/.ssh如果发现权限不对,登录服务器后执行:
chmod 700 ~/.ssh chmod 600 ~/.ssh/authorized_keys chown -R $(whoami):$(whoami) ~/.ssh改完再重新连接。这个坑之所以反复出现,是因为很多人喜欢用sudo去执行投递公钥的命令,结果authorized_keys的属主变成了root,普通用户登录时sshd一检查属主不对,直接不认。
5.2 服务器重装或指纹变化导致的Host key verification failed
这种情况往往发生在服务器重做系统、重置云主机之后。你的Mac上known_hosts里存着旧指纹,服务器提供的指纹已经变了,SSH为了防中间人攻击会直接拒绝连接。报错里通常带着REMOTE HOST IDENTIFICATION HAS CHANGED。
解决办法是删掉旧记录,重新确认:
ssh-keygen -R server_ip然后再ssh user@server_ip,它会重新询问是否信任新指纹,输入yes即可。
顺带一提,如果只是偶尔用不同系统连同一台服务器,出现这个提示是正常的,别慌,确认不是有人在恶意劫持连接后,清理旧记录重连即可。
5.3 服务端sshd_config没开公钥验证
一般正常安装的Linux发行版默认开启PubkeyAuthentication yes,但有些最小化安装或加固过的系统会把它关掉。表现是客户端这边看起来一切正常,服务器却一直要求密码。这时候到服务器上检查:
sudo grep -E "PubkeyAuthentication|PasswordAuthentication" /etc/ssh/sshd_config确认PubkeyAuthentication yes,再看看PasswordAuthentication是否开着。如果你希望改成“只允许公钥登录、彻底禁用密码登录”来防爆破,可以把它改成no,然后重载sshd:
sudo systemctl reload sshd # 或较老系统 sudo service ssh reload这里要特别叮嘱一句:在任何生产服务器上做禁用密码登录的操作之前,一定要开一个新的SSH会话,确认公钥登录稳定可用之后再关掉密码认证。不然中间任何一个环节出错,你可能进不去机器,只能去云控制台瞎折腾。我有一次就是改完配置忘了reload,顺手把会话关了,结果被挡在门外,后来只能从另一台跳板机绕进去。
5.4 多把私钥导致的“总是要我输密码”
如果你配置完~/.ssh/config,发现ssh 别名能免密,但ssh 完整user@host又要密码,多半是没指定IdentityFile,SSH默认只试默认路径的私钥。
用-i明确指定试试:
ssh -i ~/.ssh/id_ed25519_special user@host能通的话,就去~/.ssh/config对应Host段里把IdentityFile补上。另外,如果你用了ssh-add -D清空过agent里的密钥,某些场景下即使IdentityFile写对了,SSH也只走agent里的密钥,这时候重新执行ssh-add ~/.ssh/你的私钥就好。
5.5 实在是不知道问题在哪,开调试模式看
如果以上都排除了还是不生效,直接开SSH的调试输出,一步步看认证过程:
ssh -v user@host想更详细就ssh -vvv。重点看输出里有没有类似这样的行:
debug1: Offering public key: /Users/yourname/.ssh/id_ed25519 ED25519 SHA256:xxx debug1: Server accepts key: /Users/yourname/.ssh/id_ed25519 ED25519 SHA256:xxx debug1: Authentications that can continue: publickey,password如果看到Offering public key后紧接着Authentications that can continue,说明服务器拒绝了你提供的公钥;如果压根没有Offering public key那一行,说明客户端这边就没打算用这把私钥,去查IdentityFile和agent状态。这个方法比瞎猜快很多,我用它解决过至少三次“看起来完全正常但就是连不上”的问题。
说到底,SSH公钥免密这件事,原理并不复杂,真正影响体验的都是细节。你在自己Mac上把密钥生成好、权限管好、配置项摸熟之后,这套东西就会变成日常工作里最不起眼但又最可靠的一环。我现在的习惯是,新拿到一台服务器,第一件事就是生成专用密钥、写入config、投递公钥,前后不到五分钟,以后再也不用翻密码本。希望这篇文章能帮你把这条路一次走通。