
1. 引言:从密码时代到密钥时代
SSH(Secure Shell)作为网络设备、服务器和代码托管平台的标准远程管理协议,其身份认证是整个安全体系的基石。传统的密码认证虽然简单,却面临着暴力破解、中间人攻击和凭据泄露等诸多风险。
公钥认证的出现彻底改变了这一局面。它基于非对称加密算法,让用户无需在网络中传输密码即可完成身份验证。然而,现实中的配置远比理论复杂——“用户密码未启用,公钥认证也无效” 是一个让无数运维人员和开发者头痛的经典陷阱。
本文将系统性地剖析SSH公钥认证的完整原理,从数学基础到实战配置,再到各类疑难杂症的排查方法,帮助读者真正理解并掌握这一核心技术。
2. SSH 认证的两种模式对比
在深入公钥认证之前,有必要先理解SSH提供的两种认证方式及其适用场景。
2.1 密码认证(Password Authentication)
工作原理:
- 客户端向服务器发起认证请求,发送用户名
- 服务器生成随机数作为挑战,返回给客户端
- 客户端使用密码加密挑战,返回给服务器
- 服务器验证加密结果,确认用户身份
优缺点分析:
| 优点 | 缺点 |
|---|---|
| 配置简单,开箱即用 | 密码在网络中传输(虽然加密) |
| 无需额外管理密钥文件 | 易受暴力破解攻击 |
| 适合临时或一次性访问 | 无法实现自动化脚本登录 |
| 用户无需掌握密钥知识 | 密码泄露风险高 |
2.2 公钥认证(Public Key Authentication)
工作原理:
- 客户端使用私钥对服务器发出的挑战进行签名
- 服务器使用预先存储的公钥验证签名
- 验证通过则允许登录
优缺点分析:
| 优点 | 缺点 |
|---|---|
| 私钥从不离开客户端,安全性高 | 需要额外的密钥生成和部署步骤 |
| 可设置空密码,实现免密登录 | 密钥文件管理有复杂度 |
| 支持多用户共享同一账号(各自用自己的密钥) | 密钥丢失意味着访问权限丢失 |
| 适合自动化脚本和CI/CD流程 | 多密钥环境需要精细配置 |
2.3 核心差异对比表
| 对比维度 | 密码认证 | 公钥认证 |
|---|---|---|
| 认证凭据 | 用户设定的密码字符串 | 非对称密钥对(公钥+私钥) |
| 网络传输 | 加密后的密码 | 数字签名,不传输密钥本身 |
| 抗暴力破解 | 弱(可被字典攻击) | 强(密钥长度2048+位) |
| 免密能力 | 需要配合ssh-agent | 原生支持(空密码短语) |
| 适用场景 | 交互式用户登录 | 自动化脚本、CI/CD、高频访问 |
| 配置复杂度 | 低 | 中 |
| 安全性评级 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
3. 公钥认证的完整原理剖析
3.1 非对称加密基础
公钥认证的核心是非对称加密算法。与对称加密使用同一把钥匙不同,非对称加密使用一对数学相关的密钥:
- 公钥(Public Key):可以公开分发,用于加密数据或验证签名
- 私钥(Private Key):必须严密保管,用于解密数据或生成签名
从公钥推导出私钥在计算上不可行,这一特性构成了SSH认证的安全基础。
SSH支持的密钥算法:
| 算法 | 推荐强度 | 默认状态 | 备注 |
|---|---|---|---|
| Ed25519 | 256位 | ✅ 默认 | 最推荐,性能好,安全性高 |
| ECDSA | 256/384/521位 | ✅ 支持 | 椭圆曲线算法,性能良好 |
| RSA | 3072位+ | ⚠️ 需配置 | 旧版默认,OpenSSH 8.8+部分禁用 |
| DSA | 1024位 | ❌ 已废弃 | 不再安全,不推荐使用 |
OpenSSH 8.8+版本的RSA变化:自OpenSSH 8.8开始,默认禁用ssh-rsa算法。如需兼容旧客户端,需在sshd_config中添加:
PubkeyAcceptedAlgorithms +ssh-rsa
HostKeyAlgorithms +ssh-rsa
但强烈建议将现有RSA密钥升级为Ed25519。
3.2 SSH完整认证流程
SSH认证分为两个主要阶段:会话密钥协商和用户认证。
sequenceDiagram
participant C as SSH客户端
participant S as SSH服务器
Note over C,S: 第一阶段:会话密钥协商(密钥交换)
C->>S: 1. 连接请求,发送支持的算法列表
S->>C: 2. 返回协商的算法、服务器公钥、会话ID
C->>C: 3. 生成会话密钥,用服务器公钥加密
C->>S: 4. 发送加密的会话密钥
S->>S: 5. 用服务器私钥解密,获得会话密钥
Note over C,S: 后续所有通信由此会话密钥加密
Note over C,S: 第二阶段:用户认证
S->>C: 6. 发送随机数挑战,请求认证
C->>C: 7. 用用户私钥签名挑战
C->>S: 8. 发送签名结果
S->>S: 9. 用用户公钥验证签名
S->>C: 10. 认证成功,允许登录
3.3 认证流程详细拆解
第一阶段:会话密钥协商
- 客户端发起TCP连接,同时发送自己支持的算法列表(密钥交换算法、加密算法、MAC算法等)
- 服务器从客户端列表中选出双方都支持的算法,返回给客户端
- 服务器发送自己的主机公钥和一个会话ID
- 客户端生成一个临时会话密钥,使用服务器公钥加密后发送
- 服务器用私钥解密,获得会话密钥
此时,双方拥有了相同的会话密钥,后续所有通信都使用此密钥进行对称加密。
第二阶段:用户公钥认证
- 服务器生成一个随机数作为认证挑战(Challenge)
- 服务器将该挑战发送给客户端
- 客户端使用用户私钥对挑战进行数字签名
- 服务器收到签名后,在
~/.ssh/authorized_keys中查找对应用户的公钥 - 使用该公钥验证签名:
- 验证通过 → 认证成功
- 验证失败 → 尝试下一个公钥或拒绝登录
3.4 关键安全设计
私钥永不离线:在整个认证过程中,私钥始终存储在客户端本地,从不通过网络传输。服务器只验证客户端用私钥生成的签名。
公钥的信任锚点:服务器通过authorized_keys文件存储所有信任的客户端公钥。只有当客户端持有与其中某个公钥配对的私钥时,才能成功认证。
密码短语(Passphrase)的双重保护:如果生成密钥时设置了密码短语,那么即使攻击者获取了私钥文件,也还需要密码短语才能使用。这构成了“你所拥有的(密钥文件)+ 你所知道的(密码短语)”的双因素认证。
4. 完整配置指南
4.1 密钥生成
基础生成命令
# 使用Ed25519算法(强烈推荐)
ssh-keygen -t ed25519 -C "your_email@example.com"
# 使用RSA算法(兼容旧系统,需4096位)
ssh-keygen -t rsa -b 4096 -C "your_email@example.com"
# 使用ECDSA算法
ssh-keygen -t ecdsa -b 521 -C "your_email@example.com"
参数说明:
-t:指定密钥算法类型-b:指定密钥长度(RSA默认3072,推荐4096)-C:添加注释,通常填写邮箱或用途标识
交互过程详解
$ ssh-keygen -t ed25519
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/user/.ssh/id_ed25519):
# 直接回车使用默认路径,或输入自定义路径
Enter passphrase (empty for no passphrase):
# 建议输入密码短语,可为空(自动化脚本场景)
Enter same passphrase again:
Your identification has been saved in /home/user/.ssh/id_ed25519
Your public key has been saved in /home/user/.ssh/id_ed25519.pub
关于密码短语的最佳实践:
- 交互式登录:强烈建议设置密码短语
- 自动化脚本/CI/CD:设置空密码短语,配合严格的文件权限和访问控制
- 高安全环境:使用密码短语 +
ssh-agent实现一次性输入
4.2 生成的文件
| 文件 | 权限要求 | 用途 | 是否可分享 |
|---|---|---|---|
id_ed25519 |
600 | 私钥文件 | ❌ 绝不可分享 |
id_ed25519.pub |
644 | 公钥文件 | ✅ 可部署到服务器 |
4.3 公钥部署
方法一:ssh-copy-id(推荐)
# 最简单的方式,自动处理权限
ssh-copy-id user@hostname
# 指定非默认端口
ssh-copy-id -p 2222 user@hostname
# 指定特定密钥
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@hostname
方法二:手动部署
# 一行命令完成
cat ~/.ssh/id_ed25519.pub | ssh user@hostname "mkdir -p ~/.ssh && cat >> ~/.ssh/authorized_keys"
方法三:Windows环境部署
Windows环境下的部署略有不同,特别是管理员账户:
标准用户:
# 使用PowerShell远程部署
$authorizedKey = Get-Content -Path $env:USERPROFILE\.ssh\id_ecdsa.pub
$remotePowershell = "powershell New-Item -Force -ItemType Directory -Path $env:USERPROFILE\.ssh; Add-Content -Force -Path $env:USERPROFILE\.ssh\authorized_keys -Value '$authorizedKey'"
ssh user@hostname $remotePowershell
管理员用户:Windows管理员账户使用administrators_authorized_keys而非authorized_keys,并需要配置特定权限:
# 管理员账户需要特殊处理
icacls.exe "C:\ProgramData\ssh\administrators_authorized_keys" /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"
4.4 权限配置(关键!)
SSH对文件和目录权限极其敏感,权限过松会导致认证失败。
客户端(本地)权限要求
# .ssh目录权限必须是700
chmod 700 ~/.ssh
# 私钥文件权限必须是600
chmod 600 ~/.ssh/id_ed25519
# 公钥文件权限可以是644
chmod 644 ~/.ssh/id_ed25519.pub
# config文件权限可为644
chmod 644 ~/.ssh/config
服务器端权限要求
# 用户home目录不能有群组写权限
chmod g-w ~/
chmod o-w ~/
# .ssh目录权限必须为700
chmod 700 ~/.ssh
# authorized_keys文件权限必须为600
chmod 600 ~/.ssh/authorized_keys
Windows特别注意:Windows SSH对authorized_keys文件有特殊的ACL要求,必须确保只有Administrators和SYSTEM有访问权限。
4.5 服务器端配置
编辑/etc/ssh/sshd_config,确认以下配置项:
# 启用公钥认证(必须为yes)
PubkeyAuthentication yes
# 指定公钥文件路径
AuthorizedKeysFile .ssh/authorized_keys
# 密码认证可选
PasswordAuthentication yes # 保留密码作为备用
# PasswordAuthentication no # 仅允许密钥登录
# 禁止空密码(安全加固)
PermitEmptyPasswords no
# 可选:指定允许登录的用户
AllowUsers user1 user2
修改配置后重启SSH服务:
# systemd系统
sudo systemctl restart sshd
# 或
sudo service ssh restart
5. 典型踩坑案例与解决方案
5.1 案例一:服务器未启用公钥认证(最常见)
现象:
Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
或
No supported authentication methods available (server sent: publickey,gssapi-keyex,gssapi-with-mic)
原因:服务器/etc/ssh/sshd_config中的PubkeyAuthentication被设置为no或被注释,导致服务器根本不接受公钥认证。
解决方案:
# 1. 通过其他方式登录服务器(VNC、控制台等)
# 2. 编辑配置文件
sudo vim /etc/ssh/sshd_config
# 3. 确保以下配置存在且未注释
PubkeyAuthentication yes
# 4. 重启SSH服务
sudo systemctl restart sshd
5.2 案例二:文件权限错误(高频问题)
现象:公钥已正确添加,PubkeyAuthentication yes已配置,但仍然Permission denied。
原因:SSH对权限的严格要求:
.ssh目录或authorized_keys文件权限过松(如777、755)- 用户home目录被其他用户可写
解决方案:
# 检查并修复权限
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
chmod 600 ~/.ssh/id_rsa # 客户端私钥
# 检查home目录权限
ls -ld ~/
# 权限应为 drwx------ 或 drwxr-xr-x,不能有群组/其他写权限
验证技巧:使用ssh -v user@host查看详细的认证过程,日志会明确提示权限问题。
5.3 案例三:authorized_keys位置错误
现象:客户端明确提示公钥被拒绝,但authorized_keys文件中确实有公钥。
原因:sshd_config中的AuthorizedKeysFile可能指向了非标准路径。
检查方法:
# 查看服务器配置
grep AuthorizedKeysFile /etc/ssh/sshd_config
# 默认值通常是
AuthorizedKeysFile .ssh/authorized_keys
# 这意味着公钥文件位于用户home目录下的 .ssh/authorized_keys
特殊情况 - Windows管理员账户: Windows OpenSSH对管理员账户使用不同的公钥文件路径:
- 普通用户:
%USERPROFILE%\.ssh\authorized_keys - 管理员用户:
%ProgramData%\ssh\administrators_authorized_keys
5.4 案例四:算法不兼容(新版OpenSSH问题)
现象:新版客户端连接旧服务器,或反之,提示密钥不被支持。
原因:OpenSSH 8.8+默认禁用了ssh-rsa算法,而旧服务器可能只支持该算法。
诊断:
# 使用详细模式查看协商的算法
ssh -vvv user@host
# 如果看到类似以下信息,说明是算法问题
"no matching host key type found"
解决方案一(临时):在客户端命令行重新启用RSA:
ssh -o PubkeyAcceptedAlgorithms=+ssh-rsa -o HostKeyAlgorithms=+ssh-rsa user@host
解决方案二(永久):修改客户端~/.ssh/config:
Host old-server
HostName old-server.example.com
PubkeyAcceptedAlgorithms +ssh-rsa
HostKeyAlgorithms +ssh-rsa
解决方案三(推荐):将服务器升级或更换密钥为Ed25519。
5.5 案例五:SELinux阻止(Linux特有)
现象:权限和配置都正确,但仍无法认证。
原因:SELinux安全上下文错误,阻止sshd读取authorized_keys。
诊断:
# 临时禁用SELinux测试
sudo setenforce 0
# 如果此时可以登录,说明是SELinux问题
# 查看SELinux审计日志
sudo ausearch -m avc -ts recent | grep sshd
解决方案:
# 恢复正确的SELinux上下文
restorecon -R -v ~/.ssh
# 或者设置正确的类型
chcon -t ssh_home_t ~/.ssh/authorized_keys
5.6 案例六:Windows ACL权限问题
现象:Windows服务器上密钥认证失败。
原因:Windows OpenSSH对administrators_authorized_keys文件有严格的ACL要求。
解决方案:
# 以管理员身份运行
icacls.exe "C:\ProgramData\ssh\administrators_authorized_keys" /inheritance:r /grant "Administrators:F" /grant "SYSTEM:F"
# 重启SSH服务
Restart-Service sshd
5.7 案例七:多个密钥的优先级混乱
现象:有多个密钥(个人、工作),但连接时总是使用错误的密钥。
解决方案:使用~/.ssh/config为不同主机指定密钥:
# 个人GitHub
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_personal
IdentitiesOnly yes
# 工作GitLab
Host gitlab.work.com
HostName gitlab.work.com
User git
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes
# 公司跳板机
Host bastion
HostName bastion.company.com
User admin
IdentityFile ~/.ssh/id_rsa_company
ProxyJump none
6. 排错方法论
6.1 客户端调试命令
使用-v(verbose)参数获取详细连接信息,v越多信息越详细:
# 基本调试
ssh -v user@host
# 更详细的调试(推荐)
ssh -vv user@host
# 最详细的调试
ssh -vvv user@host
关键日志解读:
| 日志内容 | 含义 | 解决方向 |
|---|---|---|
debug1: Authentications that can continue: publickey |
服务器接受公钥认证 | 正常 |
debug1: Offering public key: /home/user/.ssh/id_rsa |
客户端尝试使用该密钥 | 检查密钥是否正确 |
debug1: Authentication refused: bad permissions |
权限错误 | 检查文件权限 |
debug1: No more authentication methods to try |
所有认证方式均失败 | 检查服务器配置 |
debug1: connect to address X.X.X.X port 22: Connection refused |
连接被拒绝 | 检查端口和防火墙 |
debug1: Remote: Invalid user |
用户名不存在 | 确认用户名 |
6.2 服务器端日志分析
Linux日志位置:
# CentOS/RHEL
sudo tail -f /var/log/secure
# Ubuntu/Debian
sudo tail -f /var/log/auth.log
关键日志解读:
| 日志内容 | 含义 | 解决方案 |
|---|---|---|
Authentication refused: bad ownership or modes |
权限问题 | chmod 700 ~/.ssh; chmod 600 ~/.ssh/authorized_keys |
Failed publickey for user |
公钥验证失败 | 检查authorized_keys中的公钥是否正确 |
User user not allowed because shell /bin/false does not exist |
Shell配置错误 | 检查/etc/passwd中的shell路径 |
Connection closed by X.X.X.X |
连接被关闭 | 检查防火墙或MaxAuthTries限制 |
6.3 系统化排查流程
graph TD
Start[SSH连接失败] --> Q1{错误信息?}
Q1 -->|"Permission denied (publickey)"| C1[检查服务器配置]
Q1 -->|"Connection refused"| C2[检查端口和防火墙]
Q1 -->|"No matching key type"| C3[检查算法兼容性]
C1 --> Step1[PubkeyAuthentication yes?]
Step1 -->|否| Fix1[修改sshd_config]
Step1 -->|是| Step2[authorized_keys存在且正确?]
Step2 -->|否| Fix2[部署公钥]
Step2 -->|是| Step3[文件权限正确?]
Step3 -->|否| Fix3[chmod修复权限]
Step3 -->|是| Step4[SELinux阻止?]
Step4 -->|是| Fix4[restorecon/临时禁用]
Step4 -->|否| Final1[查看详细日志]
C2 --> Step5[服务器SSH服务运行?]
Step5 -->|否| Fix5[systemctl start sshd]
Step5 -->|是| Step6[防火墙允许22端口?]
Step6 -->|否| Fix6[ufw/firewalld允许]
C3 --> Step7[OpenSSH版本差?]
Step7 -->|是| Fix7[启用ssh-rsa兼容]
Step7 -->|否| Step8[更新密钥为Ed25519]
6.4 快速诊断命令集合
# 检查服务器SSH服务状态
systemctl status sshd
# 检查端口监听
netstat -tlnp | grep :22
ss -tlnp | grep sshd
# 检查防火墙规则
iptables -L -n | grep 22
ufw status
# 测试密钥文件格式
ssh-keygen -l -f ~/.ssh/id_ed25519
# 验证私钥是否有密码短语
ssh-keygen -y -f ~/.ssh/id_ed25519
# 测试特定密钥文件
ssh -i ~/.ssh/specific_key user@host
# 强制使用特定认证方式
ssh -o PreferredAuthentications=publickey user@host
# 跳过known_hosts检查(仅测试)
ssh -o StrictHostKeyChecking=no user@host
7. 最佳实践与安全加固
7.1 密钥生命周期管理
| 阶段 | 最佳实践 |
|---|---|
| 生成 | 使用Ed25519算法,至少设置密码短语 |
| 存储 | 私钥存于加密存储(如macOS钥匙串、Windows Credential Manager),备份至加密介质 |
| 部署 | 最小权限原则,仅授予必要的服务器访问权限 |
| 轮换 | 每90-180天更换密钥对 |
| 撤销 | 立即从所有服务器的authorized_keys中删除 |
7.2 服务器安全加固配置
# /etc/ssh/sshd_config 安全配置示例
# 禁用不安全的认证方式
PubkeyAuthentication yes # 启用公钥认证
PasswordAuthentication no # 禁用密码认证
ChallengeResponseAuthentication no # 禁用挑战响应认证
KerberosAuthentication no # 禁用Kerberos
# 禁用root直接登录
PermitRootLogin prohibit-password # 或设置为no
# 限制登录尝试
MaxAuthTries 3
MaxSessions 10
# 空闲超时断开
ClientAliveInterval 300
ClientAliveCountMax 2
# 禁用不安全的算法
Ciphers aes256-ctr,aes192-ctr,aes128-ctr
MACs hmac-sha2-512,hmac-sha2-256
KexAlgorithms curve25519-sha256,diffie-hellman-group-exchange-sha256
7.3 使用ssh-agent管理私钥
Linux/macOS:
# 启动ssh-agent
eval "$(ssh-agent -s)"
# 添加私钥(输入一次密码短语)
ssh-add ~/.ssh/id_ed25519
# 查看已加载的密钥
ssh-add -l
# 删除所有密钥
ssh-add -D
Windows PowerShell(管理员):
# 设置ssh-agent自动启动
Get-Service ssh-agent | Set-Service -StartupType Automatic
# 启动服务
Start-Service ssh-agent
# 添加私钥
ssh-add $env:USERPROFILE\.ssh\id_ed25519
7.4 硬件密钥支持
对于高安全环境,可考虑使用硬件密钥存储私钥:
- YubiKey PIV:将SSH私钥存储在YubiKey中,私钥不可导出
- PKCS#11:通过
ssh-keygen -D使用PKCS#11模块
# 使用YubiKey中的密钥
ssh -I /usr/lib/libykcs11.so user@host
# 或配置~/.ssh/config
Host *.example.com
PKCS11Provider /usr/lib/libykcs11.so
7.5 审计与监控
# 监控SSH登录日志
sudo journalctl -u sshd -f
# 查看最近的认证失败记录
sudo grep "Failed password" /var/log/auth.log
# 查看公钥使用情况
sudo grep "Accepted publickey" /var/log/auth.log
# 设置登录告警(使用fail2ban)
sudo apt install fail2ban
sudo systemctl enable fail2ban
8. 总结
8.1 核心要点提炼
SSH公钥认证的本质是不传输秘密,只验证身份——客户端用私钥签名,服务器用公钥验证。这背后的数学原理确保了认证过程的安全性。
三个最容易踩的坑:
-
服务端未启用公钥认证:
PubkeyAuthentication no是一切的根源——即使配置再完美,服务器根本“不听”公钥认证请求,认证必然失败。 -
权限错误:SSH对
.ssh(700)和authorized_keys(600)权限要求极为严格,细微的权限偏差都会导致认证失败。 -
算法不兼容:新版OpenSSH禁用
ssh-rsa后,旧密钥可能突然失效。
8.2 排查流程速查
SSH公钥认证失败排查清单:
□ 1. 检查服务器配置:PubkeyAuthentication yes
□ 2. 确认公钥已添加到 authorized_keys
□ 3. 修正权限:.ssh目录700,authorized_keys文件600
□ 4. 检查算法兼容性,必要时启用ssh-rsa
□ 5. 使用 ssh -vvv 查看详细错误信息
□ 6. 检查服务器日志 /var/log/auth.log
□ 7. 验证SELinux/AppArmor未阻止
□ 8. 确认防火墙允许SSH端口
□ 9. 检查用户shell路径是否正确
□ 10. Windows环境检查管理员账户的特殊配置
8.3 最终建议
对于新部署的环境:
- 算法选择:首选 Ed25519,次选 ECDSA,最后才是 RSA(且必须4096位以上)
- 安全策略:生产环境建议
PasswordAuthentication no,仅允许密钥登录 - 密钥管理:使用 ssh-agent 减少密码短语输入频率
- 备份策略:私钥必须加密备份,公钥保持同步
理解SSH公钥认证的原理和陷阱,不仅能让你在配置时少走弯路,更能建立起一套安全、高效的远程访问体系。无论是管理成百上千台服务器,还是维护个人的代码仓库,这都是一项值得深入掌握的技能。
原文 https://blog.csdn.net/2301_79518550/article/details/145260477