ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

SSH密钥认证实战指南:从原理到安全加固

SSH密钥认证实战指南:从原理到安全加固 如果你还在用密码登录 SSH那我强烈建议你花十分钟读完这篇。不管是自己买的云服务器被暴力扫描盯上还是每天连测试机都要输好几遍密码输到烦又或者是帮公司维护一批 Linux 主机、想找个更省事的登录方案这篇文章就是冲着你来的。我会从 SSH 密钥认证的底层逻辑讲起一路讲到密钥怎么生成、怎么分发、怎么管理多台服务器多个账号再带你完整梳理一遍最常见的连接失败场景和排查思路最后聊聊生产环境里关密码登录之前必须做好的安全检查。内容不算短但每一步都是可以直接照着操作的已经帮你把坑都踩平了。1. 为什么 SSH 密钥比密码安全得多一次搞懂原理很多人用 SSH 只知道ssh userhost然后输密码觉得挺正常。但你要知道密码认证这种方式在今天的网络安全环境下简直就像把家门钥匙挂在门口。1.1 密码认证的致命短板密码认证的核心问题是可猜测性和可重放性。机器不知道输入密码的到底是不是你本人它只验证密码对不对。这就带来几个实际风险互联网上扫描 22 端口的自动化脚本从来没停过天天对着全网 IP 跑字典爆破你设个123456或者Pssw0rd这种所谓符合复杂度要求的密码扛不住多久。密码会在网络上传输虽然是加密通道但服务端必须拿到明文才能比对一旦你访问的跳板机、堡垒机被植入木马密码就泄露了。很多人都喜欢一个密码走天下只要一个平台泄露所有服务器全完蛋。1.2 密钥认证私钥永不离开你的电脑SSH 密钥认证走的是另一套逻辑。它用的是非对称加密技术简单说就是一对密钥公钥可以随便分发放在服务器上。私钥只保存在你自己的电脑里永远不出设备。连接过程也不复杂我用人话给你捋一遍客户端发起 SSH 连接时表明自己持有一把私钥并让服务器提供一段随机数据作为挑战。客户端用私钥对这段数据进行签名运算把签名结果发给服务器。服务器用事前存好的公钥去验证这个签名是否有效验证通过就放行。这个流程的精妙之处在于私钥从头到尾没有离开过你的电脑也没有在网络上传送。服务器只是验证你确实拥有和公钥配对的私钥整个过程不依赖任何可猜测的密码暴力破解在数学上不成立。这就好比你把一把特制的印章样式公开但印章本身只有你有对方只需要看印出来的字对不对就行完全不需要知道你印章长什么样。1.3 为什么公钥加锁、私钥开锁的说法不够准确很多教程喜欢把公钥加密类比成公钥锁箱子私钥开箱子这个说法帮你理解方向没问题但严格来说不太准确。SSH 密钥认证实际上更常见的是签名验证机制也就是我刚才说到的挑战-响应模式私钥对服务器随机发送的数据做签名公钥验证签名是否有效。两者的实用区别在于公钥加密模式下任何持有公钥的人都能往服务器发送只有私钥持有者才能解开的数据服务器无法区分这是主动发起的消息还是攻击者的数据而签名验证模式下服务器每次都会发一段全新的随机数据就算攻击者截获了某次签名结果也无法在下一次连接中重放。理解了这层原理你就能真正明白为什么私钥泄露等于家门钥匙给人也就明白后面每一步操作背后的安全考量了。2. 生成密钥对ssh-keygen 的选择比你想象中多理清了原理我们来动手生成自己的一对密钥。2.1 算法选型Ed25519 还是 RSA很多人一上来就是ssh-keygen -t rsa -b 4096这没问题但我个人更推荐你用 Ed25519算法密钥长度安全性性能兼容性RSA3072/4096 位高依赖大整数分解难题加解密速度较慢几乎所有老系统都支持Ed25519固定 256 位高基于椭圆曲线速度快签名短需要 OpenSSH 6.52004 年后的系统基本都支持Ed25519 的优势非常明显密钥短、生成快、签名验签性能好而且安全性在当前公认强度也足够。如果你的服务器还在用 CentOS 6、Ubuntu 14.04 这种老古董那确实得考虑 RSA但正常来说现在的系统全都能用 Ed25519。提示在 GitHub、GitLab 这类代码托管平台Ed25519 密钥早就被完整支持了不用有任何顾虑。2.2 一条命令生成密钥参数别嫌多ssh-keygen -t ed25519 -C lisiwork-2024 -f ~/.ssh/id_ed25519 -N 逐个参数说下-t ed25519指定算法装了就千万别轻易降级到 RSA。-C lisiwork-2024给密钥加一条注释。这条注释会出现在公钥文件的末尾也是你在 GitHub 等平台添加公钥时看到的那段 title。建议注释里带上你的身份和用途比如lisiwork-2024或aws-prod-key管理大量密钥的时候一目了然。-f ~/.ssh/id_ed25519指定密钥存放路径和文件名。默认文件名是id_ed25519但如果你一台电脑要管理多个密钥后面会讲最好改成有辨识度的名字比如~/.ssh/id_ed25519_github、~/.ssh/id_ed25519_company。-N passphrase 留空。这样连接时不用输入额外口令但安全性会打折扣。我的建议见下节。运行完你会得到两个文件~/.ssh/id_ed25519私钥打死都不能给别人看。~/.ssh/id_ed25519.pub公钥内容是ssh-ed25519 AAAAC3... lisiwork-2024这样一串可以随便分发。2.3 passphrase 到底要不要设我的建议是必须设很多人因为嫌麻烦-N 一把梭。但你要明白一旦私钥文件泄露——比如电脑中木马、备份被盗——没有 passphrase 的私钥直接就能用对方连破解的功夫都省了。我的实际做法是私钥一定要设置 passphrase然后配合 ssh-agent后面会讲做到一次输入、长时间免密。这样既有保护又不会每次连接都让你输密码。设了 passphrase 之后如果后悔了随时可以改ssh-keygen -p -f ~/.ssh/id_ed25519它会提示你输入旧 passphrase再设置新 passphrase非常方便。2.4 检查生成结果的小窍门用ls -l ~/.ssh/看文件权限。正常情况下私钥必须是600即-rw-------公钥是644-rw-r--r--。如果私钥权限变成了664甚至777SSH 会直接拒绝使用报错类似Permissions too open后面排查部分会细说。还可以用ssh-keygen -l -f ~/.ssh/id_ed25519.pub查看公钥指纹将来在服务器端确认是不是同一把钥匙时用得上。3. 公钥分发让服务器认你的钥匙密钥生成只是第一步真正要解决问题得把公钥放到目标服务器的指定位置。3.1 标准做法ssh-copy-id 一行搞定ssh-copy-id -i ~/.ssh/id_ed25519.pub userserver_ip这个命令会做以下几件事用你的密码登录服务器首次运行时需要。在服务器上的~/.ssh/目录下找authorized_keys文件不存在就创建。把公钥内容追加进去同时自动修正目录和文件的权限。所以跑完这条命令你下次再ssh userserver_ip就可以直接进去了。3.2 手动分发理解了原理才能应对特殊情况有时候服务器不允许密码登录、或者你手上只有 Web 终端就得分步手动操作# 在本地打印公钥内容 cat ~/.ssh/id_ed25519.pub然后把输出内容整段复制在服务器上执行mkdir -p ~/.ssh chmod 700 ~/.ssh echo ssh-ed25519 AAAAC3... lisiwork-2024 ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys这里每一步权限设置都是有讲究的~/.ssh目录必须是700只有自己可读写执行否则 SSH 会判定目录不安全拒绝使用里面的密钥文件。authorized_keys文件必须是600只有自己可读写防止其他用户往里面塞自己的公钥。家目录~本身也不能让组和其他用户有写权限这是很多新手忽略的坑。3.3 批量分发十几台服务器怎么搞逐个跑ssh-copy-id连密码输十几遍太蠢了。我常用的方案有两种方案一先手动搞定一台再用密钥滚动分发# 在第一台服务器上把公钥加到 authorized_keys ssh-copy-id -i ~/.ssh/id_ed25519.pub userhost1 # 直接以 host1 为跳板批量把公钥推到 host2、host3…… for host in host2 host3 host4; do scp ~/.ssh/id_ed25519.pub userhost1:/tmp/user.pub ssh userhost1 cat /tmp/user.pub ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys # 这里可以根据需要从 host1 再跳到目标机器 done方案二用 Ansible 等自动化工具如果你管理的机器多了别手搓循环了直接用 Ansible 的authorized_key模块- name: 分发公钥到所有机器 authorized_key: user: lisi state: present key: {{ lookup(file, ~/.ssh/id_ed25519.pub) }}原理都一样把本地公钥注入到每台机器的authorized_keys文件里。区别只是用工具帮你统一管理状态不容易漏机器。3.4 一处细节公钥是怎么认主人的服务器拿到公钥后并不是简单地比对字符串是否一致而是会检查你的私钥签名能否通过该公钥验证。所以如果服务器上authorized_keys里存了很多人的公钥系统会逐个尝试直到某一对配对成功。这个过程对用户完全透明你感觉到的就是免密登录成功了。4. ssh-agent 与免密体验一次解锁到处通行设置 passphrase 之后每次 SSH 连接还要输一次 passphrase那和不设密码有什么区别区别就在于 ssh-agent 这个内存中的钥匙串。4.1 ssh-agent 的工作机制ssh-agent 是一个常驻在你本机后台的守护进程它能在内存里保存已解锁的私钥。只要私钥被ssh-add添加一次之后所有 SSH 连接都会自动通过 agent 完成签名不需要再输 passphrase。类比一下agent 就像一个帮你保管钥匙的管家。你只需要当着他的面把保险柜打开一次之后让他帮你拿钥匙开门不用每次都在门口现开保险柜。4.2 实际操作ssh-add 加进去# 启动 agent多数发行版登录时会自动启动可以跳过 eval $(ssh-agent -s) # 把私钥加入 agent会提示输入 passphrase ssh-add ~/.ssh/id_ed25519ssh-add -l可以列出当前 agent 里有哪些密钥ssh-add -D清空所有。macOS 上如果你希望密钥持久化保存到钥匙串可以这样# 老版本 macOS ssh-add --apple-use-keychain ~/.ssh/id_ed25519 # 新版 macOS 上直接 ssh-add -K ~/.ssh/id_ed25519Windows 10/11 系统自带的 OpenSSH 客户端会在服务里自动启动 ssh-agent把它设为自动启动就行之后的体验和 Linux/macOS 一样。4.3 SSH Agent Forwarding方便要有边界ssh -A userhost开启 agent forwarding 后你在 host 上再连其他机器时会沿用本机的 agent 身份非常方便从跳板机跳到内网机器。但这个功能要谨慎用一旦你连的这台跳板机被攻破攻击者就能操作你的 agent 去访问你所有配置了密钥的机器。我的建议是只在可信的跳板机上开启-A。永远不要在公共机器或者不了解底细的机器上开。更安全的替代方案是用ProxyJump下一节会讲它不需要在目标机器上暴露 agent。5. 多服务器多密钥管理~/.ssh/config 是你该早点学会的武器管理两三台机器还好一旦密钥多了、端口不统一、用户名乱糟糟你一定会怀念能一条命令就连接目标服务器的日子。~/.ssh/config就是干这个的。它允许你给一组连接参数起个别名之后ssh 别名就能连。5.1 一个典型的 config 文件长什么样# 个人 GitHub 账号 Host github.com HostName github.com User git IdentityFile ~/.ssh/id_ed25519_github IdentitiesOnly yes # 公司 GitLab Host gitlab.company.com HostName gitlab.company.com User git IdentityFile ~/.ssh/id_ed25519_company IdentitiesOnly yes # 生产服务器通过跳板机 Host prod-01 HostName 10.0.0.11 User deploy Port 2222 IdentityFile ~/.ssh/id_ed25519_prod ProxyJump jump-server # 跳板机 Host jump-server HostName 203.0.113.5 User admin IdentityFile ~/.ssh/id_ed25519_jump配置完我只需要ssh prod-01就会自动使用deploy用户连接10.0.0.11的2222端口并且通过jump-server跳转。这体验比ssh deploy10.0.0.11 -p 2222 -J admin203.0.113.5强多了。5.2 为什么IdentitiesOnly yes不能省当你本地有很多私钥时SSH 默认会拿着这些密钥挨个去试直到服务器通过某一个。这有两个问题试错的次数多连接速度变慢。如果某个密钥刚好被服务器接受但并不是你想用的那把就会出现连错身份的情况。IdentitiesOnly yes让 SSH 只使用你在IdentityFile里指定的那把密钥避免密钥太多选择困难的问题。在 Git 多账号场景下这个选项几乎是必备的。5.3 Git 多账号场景的实战配置假设你同时有 GitHub 和 GitLab 的账号而且密钥不同。如果不在 config 里区分Git 会拿默认的密钥去连十有八九失败。上面的 config 已经覆盖了这种情况# 验证是否通 ssh -T gitgithub.com # 输出 Hi username! Youve successfully authenticated... ssh -T gitgitlab.company.com # 输出 Welcome to GitLab, username!这个场景常出现在同时使用多个代码托管平台、或者公司 GitLab 和个人 GitHub 并存的情况下。配置一次之后git clone gitgithub.com:xxx都会自动匹配正确密钥不需要每次手动指定。5.4 ProxyJump比 agent forwarding 更干净的跳板方案前面提过ProxyJump配置里写作ProxyJump命令行参数是-J它的原理是本地 SSH 先连到跳板机然后通过跳板机建立到目标机的加密通道整个过程不需要在跳板机上启用 agent forwarding。这个方案的好处是目标机看到的连接源 IP 是跳板机的 IP但实际认证用的还是你本地的私钥。你不再需要担心跳板机被攻破后 agent 被滥用因为 agent 根本没有暴露给跳板机。6. 连接失败排查手册从为什么连不上到彻底修好这部分是全文含金量最高的地方我把日常运维和帮人解决问题时遇到的高频故障场景都列出来直接给排查链路。6.1 第一步开 verbose 看细节遇到连接问题第一件事不是瞎猜而是加-vvv参数重新连接ssh -vvv userserver_ip输出里你会看到三件关键信息debug1: Offering public key: ...客户端尝试了哪些密钥。debug1: Authentications that can continue: ...服务器允许哪些认证方式。debug1: Server accepts key: ...服务器接受了哪把密钥。如果服务器日志显示No more authentication methods to try基本可以定位到认证环节的问题。6.2 Permission denied (publickey) 的几种常见原因这个报错几乎是 SSH 新手期最常遇到的。按出现频率排原因排查方法修复服务器上 authorized_keys 里没有你的公钥ssh -v看Offering public key确认你用的密钥是否正确重新用ssh-copy-id或手动追加公钥服务器的 sshd 配置禁用了公钥认证检查服务端/etc/ssh/sshd_config中PubkeyAuthentication yes修改配置后重启 sshd但注意别把自己锁在外面家目录或 .ssh 权限过宽查看/var/log/auth.log如果有bad ownership or modes就是这个问题按前面说的700/600/644修正权限密钥没被提供config 指定错误ssh -v看客户端Offering的是哪个文件修正~/.ssh/config的IdentityFile服务器 home 目录本身权限过大同上auth.log会提示chmod go-w /home/user6.3 排查链路我遇到的一个真实案例之前帮朋友排查一台 Ubuntu 服务器无法用密钥登录现象是ssh -i key.pem userip始终Permission denied。他的第一反应是密钥坏了但我让他按顺序做了这些用ssh -vvv连接看到Offering public key证明客户端没问题而且服务器在Authentications that can continue里列了publickey,password说明服务器允许公钥认证。那就说明问题在服务端的authorized_keys。通过云控制台 VNC 登录服务器查看/var/log/auth.log果然看到一行Authentication refused: bad ownership or modes for directory /home/lisi。查了家目录/home/lisi权限是777组和其他用户都有写权限。执行chmod go-w /home/lisi后重新连接秒通。这个案例很典型一半的密钥认证失败不是密钥本身的问题而是权限不给力。所以排查一定要有条理别一上来就重新生成密钥。6.4 ssh服务器拒绝了密码密码登录也被拒的完整排查顺序这种报错常见于你本来想用密码登录却被服务端策略挡了回来。按下列顺序排查确认服务器 sshd 配置PasswordAuthentication是否为yes现在很多云镜像默认改成no了。确认你输入的用户名对不存在或者该用户被AllowUsers/DenyUsers规则限定了。查看auth.log或secure日志看有没有Failed password记录以及是否有Maximum authentication attempts限制。如果服务器启用了PermitRootLogin no你试图用 root 登录也会被拒。确认你连的端口对不对有些服务器把 SSH 改到了非 22 端口默认连接当然被拒。6.5 VSCode Remote-SSH 连不上服务器的几个坑VSCode 的 Remote-SSH 本质上还是调系统 ssh 命令所以系统层面的 SSH 问题它都会遇到。额外要留意它读取~/.ssh/config的路径和终端里是一样的如果配置文件写错VSCode 会直接报Cant resolve host xxx。连接时经常卡在 Setting up SSH Host 阶段多半是远端~/.vimrc或 shell 初始化脚本里有耗时的交互命令导致 SSH session 建立后迟迟不能进入正常工作状态。试着排查远端的.bashrc、.zshrc里是否有卡住的命令。VSCode 自身的问题Remote-SSH 插件版本和 VSCode 版本不匹配也会导致连接失败。把它更新到最新版或者卸载重装经常能解决莫名其妙的连接问题。6.6 known_hosts 相关的连接失败~/.ssh/known_hosts是记录这台服务器的指纹曾经长什么样的文件作用是防止中间人攻击。当你重装服务器、更换密钥对之后本地保存的指纹就和实际不符SSH 会拒绝连接。报错通常是WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!解决办法ssh-keygen -R server_ip # 或者 ssh-keygen -R server_hostname删除旧指纹后重新连接接受新指纹即可。这是重装云服务器后最常见的连不上原因很多新手卡在这里一头雾水。6.7 Ubuntu/CentOS 系统层级的 SSH 服务检查如果问题不在密钥而在服务本身用下面几条命令快速定位# 服务是否在运行 systemctl status sshd # CentOS/麒麟等 systemctl status ssh # Ubuntu # 端口是否在监听 ss -tlnp | grep :22 # 防火墙是否放行 ufw status firewall-cmd --list-all如果服务死了systemctl restart sshd或systemctl restart ssh启动即可。很多时候云服务器连不上是安全组/防火墙没放行 22 端口跟本机 SSH 配置没有半点关系。6.8 大量陌生 SSH 连接进来怎么办先隔离再加固如果发现服务器日志里全是Failed password扫描记录先别慌。一个立竿见影的办法是换掉默认的 22 端口改到高位端口比如 22022能过滤掉绝大多数自动扫描流量。再用 fail2ban 做自动封禁sudo apt install fail2ban # Ubuntu sudo yum install fail2ban # CentOS配置/etc/fail2ban/jail.local[sshd] enabled true port ssh filter sshd logpath /var/log/auth.log maxretry 5 bantime 86400同时检查sshd_config里的LoginGraceTime、MaxAuthTries等参数适当收紧连接限制。核心思路是先减少暴露面再用自动化工具兜底最后一定要确保自己手里的密钥还能登进去。7. 关闭密码登录前请先确认这四件事很多安全教程看到一半就直接让你改配置把密码登录禁了。这里我必须泼一盆冷水如果你不是有十足把握千万别在生产环境冲动操作。7.1 禁用密码登录的正确姿势修改/etc/ssh/sshd_configPasswordAuthentication no PubkeyAuthentication yes PermitRootLogin prohibit-password然后systemctl reload sshd让它生效。PermitRootLogin prohibit-password的含义是root 允许通过密钥登录但不允许密码登录。这样即使密码泄露攻击者也无法直接拿到 root shell。7.2 改配置前必须确认的四件事至少有两把有效密钥能登录。一把锁在电脑里一把放在备用电脑或者手机端的 Termius 上以防主力设备坏了被锁在门外。保留一个备用登录通道。云服务器的话确保云控制台的 VNC/远程连接功能可用防止 SSH 配置写错导致失联。确认 sudo 权限正常。给普通用户配好密钥登录后要能通过 sudo 提权否则以后想改回密码登录都要折腾半天。先测新配置再 reload。改完配置最好先sshd -t检查语法再systemctl reload sshd。千万不要restart会断掉所有现有连接reload 是平滑重载。7.3 密钥轮换与吊销不只是换把新钥匙密钥用久了难免存在泄露风险比如员工离职、笔记本电脑遗失。定期轮换密钥是生产环境的基本功。轮换流程不复杂本地生成新密钥对。用新公钥追加到目标机器的authorized_keys。验证新密钥可以登录后再从authorized_keys里删除旧公钥。本地删除或归档旧私钥。如果你用的是 GitHub/GitLab直接在网页端删除旧公钥即可。这里有个容易漏的环节旧公钥的注释-C字段一定要记清楚是谁的、哪台机器上的否则过两个月你看着一堆公钥根本不知道哪个该删。7.4 给密钥上双保险用安全硬件或者物理隔离对安全要求极高的环境可以考虑把私钥放进 YubiKey 这类硬件令牌里私钥永远无法导出每次签名都在硬件内部完成。这种方式平时用不上但如果你的工作涉及核心资产提前了解没坏处。退一步说哪怕不用硬件把私钥用加密压缩包存一份到离线 U 盘里也是值得养成的习惯。别把备份放在网盘或代码仓库里那就等于没备份。8. 写在最后我的几点实战体会这篇文章从原理讲到工具再到故障排查信息量不小但内容都是日常在用的东西。我个人最大的体会是SSH 密钥管理的核心不是会敲命令而是建立一套习惯。命名规范、passphrase 策略、config 文件维护、定期审查 authorized_keys这些事情单独拿出来都不难难的是长期坚持。我之前帮人收拾过一台服务器authorized_keys 里堆了上百把公钥问谁都不清楚哪把是谁的最后只能全部清掉重新分发给所有在职同事。从那以后我对密钥的注释和归档就格外较真。另外一个建议是别怕折腾。第一次配 config 文件、第一次配置 agent、第一次写 Ansible 批量分发公钥都会踩点小坑但搞通之后那种一条命令连到任何目标机器的体验绝对值回之前的折腾时间。你可以从今天开始先给自己常用的一台服务器配上密钥登录顺手建一个 config 文件把alias或者主机名固化下来。跑通了再慢慢把剩余机器纳入管理。这个流程走完你的服务器安全水平和日常效率都会有实打实的提升。
返回列表