SSH密钥登录实战:从原理到配置,彻底禁用密码提升服务器安全
1. 项目概述:为什么“改端口”只是隔靴搔痒?
每次看到有朋友在服务器安全配置清单里,第一条就是“修改SSH默认端口22”,我就忍不住想说:兄弟,这招真的过时了。这就像你家防盗门,你把锁眼从正中间挪到了侧面,以为小偷就找不到了。实际上,任何一个稍微有点耐心的攻击者,用端口扫描工具(比如nmap)几分钟就能把你的新端口扫出来。真正的安全,核心在于“钥匙”本身——你是用一把全世界可能有几亿把的“密码钥匙”,还是用一把全世界独一无二、几乎无法复制的“物理密钥”?这就是SSH密钥登录与密码登录的本质区别。
我这个在运维和开发一线摸爬滚打了十多年的老鸟,处理过无数次因为弱密码或密码泄露导致的服务器入侵事件。从挖矿脚本、勒索病毒到数据被清空,惨痛的教训告诉我,禁用密码,全面转向密钥登录,是服务器安全入门的“第一道铁闸”。这个项目,就是带你一步步,在Ubuntu和CentOS这两大主流Linux发行版上,完成从“密码+改端口”的脆弱防御,到“密钥登录+完全禁用密码”的钢铁防线升级。无论你是刚买了云服务器的个人开发者,还是需要规范团队服务器访问的运维负责人,这份指南都能让你获得立竿见影的安全提升。
2. 核心原理与方案选型:密钥 vs 密码,降维打击
在深入实操之前,我们必须搞清楚,为什么密钥登录是近乎“降维打击”的安全方案。这不仅仅是“更安全”三个字能概括的。
2.1 密码登录的“阿喀琉斯之踵”
密码登录,本质上是一个“你知道什么”(What you know)的验证模型。它的脆弱性根植于几个无法回避的弱点:
- 可暴力破解:无论你的密码多复杂,只要它长度有限、字符集有限,理论上都可以通过穷举(暴力破解)或基于字典的碰撞尝试出来。攻击者利用自动化工具,可以以每秒成千上万次的速度进行登录尝试。
- 可被窃听与重放:在非绝对安全的网络环境中,密码在传输过程中有被截获的风险。虽然SSH协议本身加密了传输内容,但中间人攻击(MITM)在特定条件下仍有可能发生。
- 人为因素:弱密码、密码复用、密码泄露(通过社工、数据库撞库等)是安全链条中最薄弱的一环。一个团队成员的不慎,就可能导致整个堡垒失守。
你可能会说:“我设置了fail2ban,失败几次就封IP!”这确实有效,但它属于“事后补救”和“增加攻击成本”。一个分布式的、IP池庞大的僵尸网络,完全可以绕过这种限制。
2.2 非对称加密:SSH密钥登录的基石
SSH密钥登录采用的是“你拥有什么”(What you have)的验证模型,其核心是非对称加密(Asymmetric Cryptography)。你需要生成一对密钥:
- 私钥 (Private Key):相当于你的“主钥匙”或“印章”,必须绝对私密地保存在你的本地电脑上,绝不能通过网络传输或分享给他人。它用于生成数字签名。
- 公钥 (Public Key):相当于“公开的锁芯”或“印章的印模”。你可以把它放到任何需要访问的服务器上。它用于验证私钥生成的签名。
其工作流程可以类比为一种特殊的“锁和钥匙”:
- 挑战:当你的客户端(比如
ssh命令或VSCode的Remote-SSH插件)尝试连接服务器时,服务器会生成一个随机的“挑战”字符串。 - 签名:客户端用本地的私钥对这个“挑战”进行加密(即签名),然后将签名发送回服务器。
- 验证:服务器用事先存储在你家目录下的公钥,去尝试解密(即验证)这个签名。如果解密成功,并且得到的原始“挑战”字符串与服务器发出的完全一致,则证明客户端拥有对应的私钥,认证通过。
这个过程的美妙之处在于:
- 无法逆向推导:即使攻击者截获了传输中的签名,或者拿到了服务器上的公钥,他也几乎不可能反向推导出你的私钥(基于数学难题,如大数分解)。
- 无需传输秘密:你的私钥从未离开过你的本地机器,从根本上杜绝了在传输中被窃取的风险。
- 抵抗暴力破解:私钥通常是一个非常长(如RSA 4096位)的随机数据块,其可能的组合数量是一个天文数字,使得暴力破解在现有计算能力下完全不现实。
因此,我们的方案选型非常明确:在个人电脑上生成高强度密钥对,将公钥部署到服务器,并彻底关闭密码登录通道。对于团队协作,则采用每个成员独立生成密钥,由管理员将其公钥添加到服务器相应用户的~/.ssh/authorized_keys文件中的模式。
注意:私钥是你的命根子。务必做好备份(例如使用加密的U盘或密码管理器),并设置强密码短语(Passphrase)来加密私钥文件本身,实现“双因子认证”(你拥有的私钥+你知道的密码短语)。
3. 完整实操流程:从零构建密钥登录体系
下面,我将以同时覆盖Ubuntu(以22.04 LTS为例)和CentOS(以7.x为例)的视角,演示完整的配置过程。假设你已有一台全新安装的服务器,并通过密码登录(用户为root或具有sudo权限的普通用户)。
3.1 第一步:在本地客户端生成SSH密钥对
这是所有操作的起点。在你的个人电脑(Windows/macOS/Linux)上操作。
1. 打开终端(或PowerShell/Git Bash)2. 生成密钥对我们使用ssh-keygen命令,并推荐使用更安全、性能更好的Ed25519算法。如果你需要兼容一些老旧的系统,可以使用RSA 4096位。
# 推荐:使用Ed25519算法 ssh-keygen -t ed25519 -C "your_email@example.com" -f ~/.ssh/id_ed25519_my_server # 或使用兼容性更好的RSA 4096位 ssh-keygen -t rsa -b 4096 -C "your_email@example.com" -f ~/.ssh/id_rsa_my_server-t:指定密钥类型(ed25519或rsa)。-b:指定密钥长度(仅对RSA有效),4096位是当前的安全标准。-C:添加一个注释,通常用邮箱,便于识别密钥所有者。-f:指定生成的私钥文件名和路径。这里我使用了带服务器标识的文件名,便于管理多台服务器。如果不指定-f,默认会生成~/.ssh/id_ed25519或~/.ssh/id_rsa,并覆盖已存在的同名文件。
3. 设置密钥密码短语(Passphrase)命令执行后,会提示你输入保存密钥的文件路径(已通过-f指定),然后会两次提示你输入密码短语。
Enter passphrase (empty for no passphrase): Enter same passphrase again:强烈建议设置一个强密码短语!这为你的私钥文件增加了一层加密保护。即使私钥文件不慎泄露,攻击者没有密码短语也无法使用它。如果直接回车,则表示不设置密码短语(不推荐)。
操作完成后,你会在~/.ssh/目录下看到两个新文件:
id_ed25519_my_server:这是你的私钥,权限必须是600(仅所有者可读可写)。id_ed25519_my_server.pub:这是你的公钥,内容是一长串以算法名开头的文本。
4. (可选)启动ssh-agent并添加私钥如果你设置了密码短语,每次使用密钥登录时都需要输入,可能会有些麻烦。ssh-agent是一个在后台运行的程序,可以帮你安全地缓存已解密的私钥,在一段时间内无需重复输入密码短语。
# 启动ssh-agent(如果尚未运行) eval "$(ssh-agent -s)" # 将私钥添加到agent ssh-add ~/.ssh/id_ed25519_my_server系统会提示你输入一次密码短语,之后在当前终端会话期间,再使用该密钥连接就无需输入了。你可以将ssh-add命令和ssh-agent的启动配置到你的shell配置文件(如~/.bashrc或~/.zshrc)中实现自动加载。
3.2 第二步:将公钥部署到远程服务器
现在,我们需要把上一步生成的公钥内容,放到服务器的对应用户目录下。
方法A:使用ssh-copy-id命令(最简便)如果你的本地系统支持这个命令(macOS和大多数Linux发行版都有),这是首选。
ssh-copy-id -i ~/.ssh/id_ed25519_my_server.pub username@your_server_ip-i:指定公钥文件路径。username:你在服务器上的用户名,如root或ubuntu等。your_server_ip:你的服务器IP地址。
执行后,会提示你输入对应用户的密码(这是最后一次使用密码登录!)。成功后,公钥就会被自动追加到服务器上~/.ssh/authorized_keys文件的末尾。
方法B:手动复制(通用方法)如果ssh-copy-id不可用(如某些Windows环境),可以手动操作。
- 查看并复制公钥内容:
全选并复制终端中输出的整行内容(从cat ~/.ssh/id_ed25519_my_server.pubssh-ed25519 AAA...开始到注释结束)。 - 登录服务器,确保目标用户的家目录下存在
.ssh文件夹,且权限正确:ssh username@your_server_ip # 输入密码登录后 mkdir -p ~/.ssh chmod 700 ~/.ssh - 将公钥写入
authorized_keys文件:echo “你刚才复制的公钥内容” >> ~/.ssh/authorized_keys - 设置
authorized_keys文件权限:
这个chmod 600 ~/.ssh/authorized_keys600权限至关重要,权限过大会导致SSH服务器出于安全考虑拒绝使用该文件。
3.3 第三步:测试密钥登录
在关闭密码之前,务必先测试密钥登录是否成功。
从你的本地机器,使用-i参数指定私钥文件进行连接:
ssh -i ~/.ssh/id_ed25519_my_server username@your_server_ip如果设置了密码短语,此时会提示你输入。如果一切配置正确,你将无需输入服务器用户密码即可登录。
测试成功的关键标志:登录过程没有出现username@your_server_ip‘s password:这个提示。如果出现了,说明密钥认证未生效,请返回检查公钥是否复制正确、文件权限是否正确(服务器上.ssh目录为700,authorized_keys文件为600)。
3.4 第四步:配置SSH服务端,强化安全并禁用密码
密钥登录测试成功后,我们就可以放心地“关上密码这扇门”了。我们需要修改SSH服务端的配置文件/etc/ssh/sshd_config。
1. 备份原始配置文件(好习惯)
sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak2. 编辑SSH服务端配置使用你熟悉的编辑器,如vim或nano:
sudo vim /etc/ssh/sshd_config找到并修改以下关键参数。请注意,有些参数可能被注释(以#开头),你需要取消注释并修改其值。
# 1. 禁止root用户直接登录(强烈建议,先用普通用户+sudo) # 将 `PermitRootLogin` 设置为 `no` PermitRootLogin no # 2. 强制使用密钥认证,禁用密码认证 # 将 `PasswordAuthentication` 设置为 `no` PasswordAuthentication no # 将 `PubkeyAuthentication` 设置为 `yes` (通常默认就是yes) PubkeyAuthentication yes # 3. 禁用其他不安全的认证方式 # 确保以下参数为 `no` ChallengeResponseAuthentication no UsePAM no # 注意:在Ubuntu 22.04及更高版本或某些CentOS配置中,UsePAM可能需要保持为yes以支持其他功能,如果禁用后出现问题请改回yes。最关键是PasswordAuthentication为no。 # 4. (可选但推荐)修改默认端口 # 取消 `Port` 的注释,并将22改为一个1024-65535之间的非知名端口,例如 2345 Port 2345 # 注意:修改端口后,防火墙必须放行新端口,且后续连接命令需加 `-p 2345` 参数。 # 5. (可选)限制登录用户 # 在文件末尾添加,只允许特定用户登录,例如只允许用户 `deploy` 和 `admin` 登录 AllowUsers deploy admin # 6. (重要)确保密钥文件路径设置正确 AuthorizedKeysFile .ssh/authorized_keys3. 检查配置文件语法修改后,使用以下命令检查配置文件是否有语法错误:
sudo sshd -t如果没有任何输出,表示语法正确。如果有错误,它会提示你哪一行有问题。
4. 重启SSH服务使配置生效这是最关键且危险的一步!你必须确保当前的SSH会话不会因为配置错误而中断。最佳实践是:
- 保持当前通过密钥登录的SSH会话窗口不要关闭。
- 打开另一个终端窗口,同样使用密钥登录到服务器。
- 在新的会话中执行重启命令:
# Ubuntu/Debian 系统 sudo systemctl restart ssh # 或 sudo service ssh restart # CentOS/RHEL 7/8 系统 sudo systemctl restart sshd # 或 sudo service sshd restart - 重启后,立即在新的终端窗口尝试建立第三个SSH连接,以验证新配置是否工作。例如,如果你修改了端口:
ssh -i ~/.ssh/id_ed25519_my_server -p 2345 username@your_server_ip - 只有确认新的连接成功后,你才可以安全地关闭最初的测试会话。
致命警告:切勿在唯一的SSH连接会话中重启SSH服务。如果配置有误(例如误禁用密钥认证),你会立刻被踢出并永远无法连接,只能通过云服务商的控制台VNC或物理接触服务器来修复。保持一个“逃生会话”是铁律。
3.5 第五步:配置防火墙(如果修改了端口)
如果你修改了SSH默认端口(第4步中的可选操作),必须更新防火墙规则,放行新的端口,并考虑是否关闭旧端口22的访问。
对于UFW(Ubuntu常用):
sudo ufw allow 2345/tcp # 允许新端口 sudo ufw deny 22/tcp # 拒绝旧端口(可选,但推荐) sudo ufw reload对于Firewalld(CentOS 7/8常用):
sudo firewall-cmd --permanent --add-port=2345/tcp # 添加新端口 sudo firewall-cmd --permanent --remove-service=ssh # 移除默认的ssh服务(即端口22) sudo firewall-cmd --reload对于iptables(通用):
# 允许新端口 sudo iptables -A INPUT -p tcp --dport 2345 -j ACCEPT # 保存规则(根据系统不同) sudo iptables-save | sudo tee /etc/sysconfig/iptables # 或使用 iptables-persistent 等工具4. 高级配置与多服务器管理
当你需要管理多台服务器或多个密钥时,合理的本地配置能极大提升效率。
4.1 本地SSH客户端配置(~/.ssh/config)
在你的本地~/.ssh/目录下创建(或编辑)config文件,可以为不同的服务器设置别名和默认参数。
Host myserver1 # 自定义别名,方便记忆 HostName 192.168.1.100 # 服务器真实IP或域名 Port 2345 # 端口号,如果修改过 User ubuntu # 登录用户名 IdentityFile ~/.ssh/id_ed25519_myserver1 # 指定使用的私钥 IdentitiesOnly yes # 只使用指定的密钥,避免尝试其他密钥 Host myserver2 HostName example.com User centos IdentityFile ~/.ssh/id_rsa_myserver2 Host github.com # 为Git服务配置 User git IdentityFile ~/.ssh/id_ed25519_github配置完成后,连接服务器只需:
ssh myserver1SSH会自动使用配置文件中指定的所有参数,无需再输入-i、-p、username@等。
4.2 为服务器上的不同用户添加密钥
如果需要让多个用户都能通过密钥登录同一台服务器,或者为一个用户添加多个公钥(例如你和你的同事),只需将各自的公钥内容逐行追加到相应用户家目录下的~/.ssh/authorized_keys文件中即可。
4.3 密钥的备份与恢复
私钥丢失意味着永久失去访问权限。备份策略:
- 加密备份:将
~/.ssh/目录下的私钥文件(如id_ed25519_*)复制到加密的U盘、硬盘或使用gpg加密后存储到云盘。 - 密码管理器:一些高级密码管理器支持安全地存储SSH私钥。
- 恢复:恢复时,将备份的私钥文件放回本地
~/.ssh/目录,并确保权限为600。将对应的公钥重新部署到服务器。
5. 故障排查与常见问题实录
即使按照步骤操作,也可能会遇到问题。这里记录了我踩过的坑和解决方案。
5.1 密钥登录失败,依然提示输入密码
这是最常见的问题。请按以下清单逐项检查:
| 问题点 | 检查命令(在服务器上执行) | 正确设置/现象 |
|---|---|---|
| 1. 文件权限 | ls -la ~/.ssh/ | .ssh目录权限应为700(drwx------),authorized_keys文件权限应为600(-rw-------)。权限不对会导致SSH出于安全考虑直接忽略密钥。 |
| 2. 公钥内容 | cat ~/.ssh/authorized_keys | 确认公钥内容完整、没有多余空格或换行,且与本地公钥文件内容完全一致。可以手动vim编辑删除旧内容,重新粘贴一次。 |
| 3. SELinux(CentOS) | getenforce | 如果返回Enforcing,SELinux可能会阻止非标准目录或权限的访问。尝试临时禁用测试:sudo setenforce 0。如果解决问题,需要为.ssh目录添加正确的SELinux上下文:sudo restorecon -Rv ~/.ssh。 |
| 4. 用户家目录权限 | ls -ld ~ | 用户家目录的权限不能过于开放(如drwxrwxrwx)。通常应为755(drwxr-xr-x) 或700。过宽的权限也会导致SSH拒绝密钥认证。 |
| 5. SSH服务配置 | `sudo grep -E “^(PasswordAuthentication | PubkeyAuthentication)” /etc/ssh/sshd_config` |
| 6. 连接命令 | ssh -v -i /path/to/key user@host | 使用-v(详细)模式连接,观察输出日志。通常会明确提示失败原因,如“Permission denied (publickey)”并指出它尝试了哪些密钥文件。 |
5.2 修改端口后无法连接
- 防火墙未放行:这是首要原因。确保新端口已在服务器防火墙(ufw/firewalld/iptables)中正确添加并启用。
- 连接命令未指定端口:如果修改了端口,连接时必须用
-p参数指定,例如ssh -p 2345 user@host,或在~/.ssh/config中配置Port。 - SSH服务监听错误端口:检查SSH服务是否真的在监听新端口:
sudo netstat -tlnp | grep sshd。确保输出中包含你设置的端口号(如:2345)。
5.3 禁用密码后,如何临时恢复密码登录?
如果你把自己锁在外面了,而你有服务器控制台访问权限(如云厂商的VNC/Web终端):
- 通过控制台登录服务器。
- 编辑
/etc/ssh/sshd_config,将PasswordAuthentication临时改为yes。 - 重启SSH服务:
sudo systemctl restart sshd。 - 用密码登录后,立即检查并修复你的密钥配置,然后记得再把密码登录关掉。
5.4 使用VSCode Remote-SSH或Cursor等编辑器连接
这些编辑器底层也使用SSH协议。配置好本地的~/.ssh/config文件后,在它们的远程连接窗口中直接输入你配置的Host别名(如myserver1)即可,它们会自动读取配置。
如果连接失败,检查编辑器是否使用了自带的SSH还是系统SSH。通常可以在设置中指定系统SSH的路径(C:\Windows\System32\OpenSSH\ssh.exeon Windows,/usr/bin/sshon Linux/macOS)。
5.5 服务器上.ssh目录及文件权限参考表
为了彻底避免权限问题,记住这个“金科玉律”:
| 文件/目录 | 推荐权限 | 命令 |
|---|---|---|
用户家目录 (~) | 755(drwxr-xr-x) 或700(drwx------) | chmod 755 ~或chmod 700 ~ |
.ssh目录 | 700(drwx------) | chmod 700 ~/.ssh |
authorized_keys文件 | 600(-rw-------) | chmod 600 ~/.ssh/authorized_keys |
| 私钥文件 (本地) | 600(-rw-------) | chmod 600 ~/.ssh/id_ed25519 |
| 公钥文件 (本地) | 644(-rw-r--r--) | chmod 644 ~/.ssh/id_ed25519.pub |
config文件 (本地) | 600或644 | chmod 600 ~/.ssh/config |
最后,关于“禁用空密码”和“限制身份验证最大尝试次数”这类需求,它们确实是安全加固的一部分,通常通过修改/etc/ssh/sshd_config中的PermitEmptyPasswords no和MaxAuthTries 3来实现。但在彻底禁用密码认证(PasswordAuthentication no)之后,这些针对密码的防护措施实际上已经不再起作用了,因为密码认证的通道已经被完全关闭。我们的方案是在更根本的层面上解决了问题。当然,在混合环境(仍需保留部分密码登录)下,这些参数依然有价值。但对于追求极致安全的个人服务器或生产环境,我的建议始终是:生成强密钥,彻底关密码。这套组合拳打下来,你的服务器SSH入口安全性,已经超越了90%的默认配置机器。
