// 安全研究 · 2025-01-25

SSH 公钥认证机制解析:原理、配置与疑难排查

在这里插入图片描述

1. 引言:从密码时代到密钥时代

SSH(Secure Shell)作为网络设备、服务器和代码托管平台的标准远程管理协议,其身份认证是整个安全体系的基石。传统的密码认证虽然简单,却面临着暴力破解、中间人攻击和凭据泄露等诸多风险。

公钥认证的出现彻底改变了这一局面。它基于非对称加密算法,让用户无需在网络中传输密码即可完成身份验证。然而,现实中的配置远比理论复杂——“用户密码未启用,公钥认证也无效” 是一个让无数运维人员和开发者头痛的经典陷阱。

本文将系统性地剖析SSH公钥认证的完整原理,从数学基础到实战配置,再到各类疑难杂症的排查方法,帮助读者真正理解并掌握这一核心技术。


2. SSH 认证的两种模式对比

在深入公钥认证之前,有必要先理解SSH提供的两种认证方式及其适用场景。

2.1 密码认证(Password Authentication)

工作原理:

  1. 客户端向服务器发起认证请求,发送用户名
  2. 服务器生成随机数作为挑战,返回给客户端
  3. 客户端使用密码加密挑战,返回给服务器
  4. 服务器验证加密结果,确认用户身份

优缺点分析:

优点 缺点
配置简单,开箱即用 密码在网络中传输(虽然加密)
无需额外管理密钥文件 易受暴力破解攻击
适合临时或一次性访问 无法实现自动化脚本登录
用户无需掌握密钥知识 密码泄露风险高

2.2 公钥认证(Public Key Authentication)

工作原理:

  1. 客户端使用私钥对服务器发出的挑战进行签名
  2. 服务器使用预先存储的公钥验证签名
  3. 验证通过则允许登录

优缺点分析:

优点 缺点
私钥从不离开客户端,安全性高 需要额外的密钥生成和部署步骤
可设置空密码,实现免密登录 密钥文件管理有复杂度
支持多用户共享同一账号(各自用自己的密钥) 密钥丢失意味着访问权限丢失
适合自动化脚本和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 认证流程详细拆解

第一阶段:会话密钥协商

  1. 客户端发起TCP连接,同时发送自己支持的算法列表(密钥交换算法、加密算法、MAC算法等)
  2. 服务器从客户端列表中选出双方都支持的算法,返回给客户端
  3. 服务器发送自己的主机公钥和一个会话ID
  4. 客户端生成一个临时会话密钥,使用服务器公钥加密后发送
  5. 服务器用私钥解密,获得会话密钥

此时,双方拥有了相同的会话密钥,后续所有通信都使用此密钥进行对称加密。

第二阶段:用户公钥认证

  1. 服务器生成一个随机数作为认证挑战(Challenge)
  2. 服务器将该挑战发送给客户端
  3. 客户端使用用户私钥对挑战进行数字签名
  4. 服务器收到签名后,在~/.ssh/authorized_keys中查找对应用户的公钥
  5. 使用该公钥验证签名:
    • 验证通过 → 认证成功
    • 验证失败 → 尝试下一个公钥或拒绝登录

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公钥认证的本质是不传输秘密,只验证身份——客户端用私钥签名,服务器用公钥验证。这背后的数学原理确保了认证过程的安全性。

三个最容易踩的坑:

  1. 服务端未启用公钥认证:PubkeyAuthentication no是一切的根源——即使配置再完美,服务器根本“不听”公钥认证请求,认证必然失败。

  2. 权限错误:SSH对.ssh(700)和authorized_keys(600)权限要求极为严格,细微的权限偏差都会导致认证失败。

  3. 算法不兼容:新版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