SSH 服务器完全指南 / 第4章 密钥认证与证书
第4章 密钥认证与证书
4.1 密钥认证原理
SSH 密钥认证基于非对称加密的挑战-响应机制:
客户端 服务器
│ │
│ 1. 发送公钥 ID ────────────────────────>│
│ │ 检查 authorized_keys
│<── 2. 返回用公钥加密的随机挑战 ──────────│
│ │
│ 3. 用私钥解密挑战 │
│ 生成响应(含会话 ID + 挑战结果) │
│ 发送响应 ───────────────────────────>│
│ │ 用公钥验证响应
│<── 4. 认证成功 ─────────────────────────│
优势对比:
| 方面 | 密码认证 | 密钥认证 |
|---|---|---|
| 暴力破解风险 | 高(弱密码) | 极低(256/4096 bit) |
| 中间人攻击 | 可能 | 防范 |
| 自动化使用 | 需要明文存储密码 | 安全使用 ssh-agent |
| 密钥轮换 | 改密码 | 替换公钥 |
| 多因素支持 | 可以 | 可以 |
4.2 authorized_keys 文件详解
文件位置
# 用户级授权文件(默认)
~/.ssh/authorized_keys
# 系统级授权文件(需要在 sshd_config 中配置)
/etc/ssh/authorized_keys/%u
# 自定义路径(在 sshd_config 中指定)
AuthorizedKeysFile /etc/ssh/authorized_keys/%u .ssh/authorized_keys
文件格式
每行一个公钥,格式为:
[选项] 算法类型 密钥数据 注释
完整示例:
# ~/.ssh/authorized_keys
# 普通公钥(无选项)
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx admin@workstation
# 带选项的公钥
from="192.168.1.0/24",command="/usr/local/bin/restricted.sh",no-port-forwarding,no-X11-forwarding ssh-rsa AAAAB3... deploy@ci
# 另一个普通公钥
ssh-rsa AAAAB3NzaC1yc2EAAAA... user@laptop
常用公钥选项
| 选项 | 说明 |
|---|---|
from="pattern" | 限制来源 IP/主机 |
command="cmd" | 只允许执行指定命令 |
no-port-forwarding | 禁止端口转发 |
no-X11-forwarding | 禁止 X11 转发 |
no-agent-forwarding | 禁止 Agent 转发 |
no-pty | 不分配伪终端 |
no-user-rc | 不执行 ~/.ssh/rc |
permitopen="host:port" | 只允许转发到指定地址 |
environment="KEY=value" | 设置环境变量 |
expiry-time="20251231" | 公钥过期时间(OpenSSH 8.7+) |
cert-authority | 标记为证书授权密钥 |
principals="user1,user2" | 允许的证书主体 |
配置选项示例
限制来源 IP
# 只允许从公司网络连接
from="10.0.0.0/8" ssh-ed25519 AAAAC3... admin@company
# 允许多个网段
from="192.168.1.*,10.0.0.*" ssh-ed25519 AAAAC3... admin@office
限制只能执行特定命令
# 只允许执行 backup 脚本
command="/usr/local/bin/backup.sh",no-port-forwarding,no-X11-forwarding ssh-ed25519 AAAAC3... backup@ci
部署专用密钥(只读 Git)
# Git 服务器专用:只允许 git 操作
command="git-shell -c \"$SSH_ORIGINAL_COMMAND\"",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-ed25519 AAAAC3... git@deploy
多个选项组合
# CI/CD 部署密钥:只允许从特定 IP,只执行部署脚本
from="192.168.1.100",command="/opt/deploy/deploy.sh",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-ed25519 AAAAC3... deploy@jenkins
4.3 公钥部署方法
方法一:ssh-copy-id(推荐)
# 自动部署公钥到远程服务器
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@remote-host
# 使用特定端口
ssh-copy-id -i ~/.ssh/id_ed25519.pub -p 2222 user@remote-host
# 使用别名
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@remote-host
注意:
ssh-copy-id需要密码登录(或已有的密钥认证)才能工作。
方法二:手动复制
# 本地复制公钥内容
cat ~/.ssh/id_ed25519.pub | ssh user@remote-host "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"
# 或者分步操作
# 1. 复制公钥
cat ~/.ssh/id_ed25519.pub
# 2. 粘贴到远程服务器
ssh user@remote-host
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "粘贴的公钥" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
方法三:Ansible 批量部署
# deploy-keys.yml
---
- name: Deploy SSH public keys
hosts: all
tasks:
- name: Ensure .ssh directory exists
file:
path: ~/.ssh
state: directory
mode: '0700'
- name: Deploy authorized_keys
authorized_key:
user: "{{ item.user }}"
key: "{{ lookup('file', item.key_file) }}"
state: present
loop:
- { user: 'admin', key_file: '~/.ssh/id_ed25519.pub' }
- { user: 'deploy', key_file: '~/.ssh/id_deploy.pub' }
方法四:批量脚本部署
#!/bin/bash
# deploy-keys.sh
SERVERS="web01 web02 web03 db01 db02"
KEY_FILE="$HOME/.ssh/id_ed25519.pub"
USER="admin"
for server in $SERVERS; do
echo "Deploying key to $server..."
ssh-copy-id -i "$KEY_FILE" "$USER@$server" 2>/dev/null
if [ $? -eq 0 ]; then
echo " ✅ Success"
else
echo " ❌ Failed"
fi
done
4.4 密钥管理最佳实践
一对密钥 vs 多对密钥
| 策略 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 一对密钥通用 | 管理简单 | 泄露影响大 | 个人开发机 |
| 按环境分离 | 影响可控 | 管理稍复杂 | 开发/生产分离 |
| 按用途分离 | 最安全 | 管理复杂 | 企业环境 |
推荐的密钥分离方案
~/.ssh/
├── id_personal # 个人开发机
├── id_personal.pub
├── id_work # 工作环境
├── id_work.pub
├── id_prod # 生产环境(最严格保护)
├── id_prod.pub
├── id_deploy # CI/CD 部署
├── id_deploy.pub
├── id_github # GitHub/GitLab
├── id_github.pub
└── config # 客户端配置
密钥轮换
定期更换 SSH 密钥是安全最佳实践:
#!/bin/bash
# rotate-key.sh - 密钥轮换脚本
SERVER="$1"
NEW_KEY="$HOME/.ssh/id_ed25519_new.pub"
OLD_KEY="$HOME/.ssh/id_ed25519.pub"
if [ -z "$SERVER" ]; then
echo "Usage: $0 user@server"
exit 1
fi
# 1. 部署新密钥
echo "Deploying new key to $SERVER..."
ssh-copy-id -i "$NEW_KEY" "$SERVER"
# 2. 验证新密钥可用
echo "Verifying new key..."
ssh -i "$HOME/.ssh/id_ed25519_new" "$SERVER" "echo 'New key works!'"
if [ $? -eq 0 ]; then
# 3. 删除旧密钥
echo "Removing old key..."
ssh -i "$HOME/.ssh/id_ed25519_new" "$SERVER" \
"sed -i '$(grep -c . $OLD_KEY)d' ~/.ssh/authorized_keys"
# 4. 本地替换
mv "$HOME/.ssh/id_ed25519" "$HOME/.ssh/id_ed25519.old"
mv "$HOME/.ssh/id_ed25519.pub" "$HOME/.ssh/id_ed25519.pub.old"
mv "$HOME/.ssh/id_ed25519_new" "$HOME/.ssh/id_ed25519"
mv "$HOME/.ssh/id_ed25519_new.pub" "$HOME/.ssh/id_ed25519.pub"
echo "✅ Key rotation complete"
else
echo "❌ New key verification failed. Old key still in place."
exit 1
fi
清理过期/不再使用的密钥
# 在服务器上查看 authorized_keys
cat ~/.ssh/authorized_keys
# 列出每个密钥的指纹和注释
while IFS= read -r line; do
echo "$line" | ssh-keygen -lf -
done < ~/.ssh/authorized_keys
# 删除特定用户/机器的公钥
# 假设注释为 user@old-laptop
sed -i '/user@old-laptop/d' ~/.ssh/authorized_keys
4.5 SSH 证书认证
为什么需要证书?
传统密钥管理的痛点:
| 痛点 | 说明 |
|---|---|
| 密钥分发 | 每台服务器都要部署公钥(N×M 问题) |
| 主机信任 | 用户需要手动验证每台服务器的指纹 |
| 密钥撤销 | 没有集中的撤销机制 |
| 短期访问 | 难以实现临时/一次性访问 |
SSH 证书通过引入证书授权机构 (CA) 解决了这些问题:
证书认证工作原理
┌─────────────┐ ┌───────────────┐ ┌─────────────┐
│ 用户 │ │ CA │ │ 服务器 │
│ │ │ (签署证书) │ │ │
│ 1. 生成密钥 │ │ │ │ │
│ ──────────────> │ │ │ │
│ │ │ 2. 签发证书 │ │ │
│ <────────────────│ │ │ │
│ │ │ │ │ │
│ 3. 提交证书 │ │ │ │ │
│ ─────────────────────────────────────> │ │
│ │ │ │ │ 4. 验证证书 │
│ │ │ │ │ (用 CA 公钥)│
│ │ │ │ │ │
│ <════════════════════════════════════════════ 允许登录 │
└─────────────┘ └───────────────┘ └─────────────┘
创建 SSH CA
用户证书 CA
# 1. 生成 CA 密钥对
ssh-keygen -t ed25519 -f ~/.ssh/ca_user_key -C "User CA"
# 设置强密码!
# 保护 CA 私钥
chmod 600 ~/.ssh/ca_user_key
主机证书 CA
# 生成主机 CA 密钥对
ssh-keygen -t ed25519 -f ~/.ssh/ca_host_key -C "Host CA"
chmod 600 ~/.ssh/ca_host_key
签发用户证书
# 用户提交公钥,CA 签发证书
# 1. 用户生成密钥(如果没有)
ssh-keygen -t ed25519 -f ~/.ssh/id_work
# 2. CA 签发证书
ssh-keygen -s ~/.ssh/ca_user_key \
-I "admin-cert-2025" \
-n "admin,root" \
-V "+52w" \
-z 1 \
~/.ssh/id_work.pub
参数说明:
| 参数 | 说明 |
|---|---|
-s | CA 私钥路径 |
-I | 证书标识(用于审计) |
-n | 允许的用户名(principal) |
-V | 有效期(+52w = 52周) |
-z | 序列号(用于追踪) |
# 生成的证书文件
# ~/.ssh/id_work-cert.pub
# 查看证书内容
ssh-keygen -Lf ~/.ssh/id_work-cert.pub
输出示例:
/home/user/.ssh/id_work-cert.pub:
Type: ssh-ed25519-cert-v01@openssh.com user certificate
Public key: ED25519-CERT SHA256:xxxxx
Signing CA: ED25519 SHA256:yyyyy (using ssh-ed25519)
Key ID: "admin-cert-2025"
Serial: 1
Valid: from 2025-01-01T00:00:00 to 2025-12-31T23:59:59
Principals:
admin
root
Critical Options: (none)
Extensions:
permit-X11-forwarding
permit-agent-forwarding
permit-port-forwarding
permit-pty
permit-user-rc
签发主机证书
# 在服务器上执行
# 1. 生成主机密钥(如果还没有)
sudo ssh-keygen -t ed25519 -f /etc/ssh/ssh_host_ed25519_key -N ""
# 2. 将主机公钥发给 CA 签署
sudo scp /etc/ssh/ssh_host_ed25519_key.pub ca-admin@ca-server:/tmp/
# 3. CA 签发主机证书
ssh-keygen -s ~/.ssh/ca_host_key \
-I "web01-host-cert" \
-h \
-n "web01.example.com,192.168.1.100,10.0.0.100" \
-V "+52w" \
/tmp/ssh_host_ed25519_key.pub
# 4. 将证书发回服务器
sudo scp ca-admin@ca-server:/tmp/ssh_host_ed25519_key-cert.pub /etc/ssh/
# 5. 配置 sshd 使用证书
# /etc/ssh/sshd_config
HostCertificate /etc/ssh/ssh_host_ed25519_key-cert.pub
配置服务器信任 CA
# /etc/ssh/sshd_config
# 信任用户 CA(用于认证用户)
TrustedUserCAKeys /etc/ssh/ca_user_key.pub
# 将用户 CA 公钥部署到服务器
sudo cp ~/.ssh/ca_user_key.pub /etc/ssh/ca_user_key.pub
sudo chmod 644 /etc/ssh/ca_user_key.pub
sudo chown root:root /etc/ssh/ca_user_key.pub
配置客户端信任主机 CA
# ~/.ssh/known_hosts
# 添加主机 CA 公钥(信任由该 CA 签署的所有主机)
@cert-authority *.example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIxxxxx
SSH 证书的优势总结
| 特性 | 传统密钥 | SSH 证书 |
|---|---|---|
| 密钥分发 | N×M 部署 | 只需信任 CA |
| 主机验证 | 手动确认指纹 | 自动信任 CA 签署的主机 |
| 过期机制 | 无 | 支持时间限制 |
| 集中管理 | 困难 | CA 集中签发/撤销 |
| 审计追踪 | 有限 | 证书 ID 可追踪 |
| 短期访问 | 实现复杂 | 签发短周期证书 |
4.6 使用场景
场景一:企业统一身份管理
# 部署方案:
# 1. 创建公司 CA
# 2. HR 系统离职时自动停止签发证书
# 3. 证书有效期设为 8 小时(一天的工作时间)
# 4. 员工每天通过 SSO 申请新证书
# 自动化签发脚本
#!/bin/bash
# get-cert.sh - 通过 SSO 获取日证书
TOKEN=$(sso-authenticate)
ssh-keygen -t ed25519 -f ~/.ssh/id_daily -N "" -C "$(whoami)-daily"
curl -H "Authorization: Bearer $TOKEN" \
-F "pubkey=@$HOME/.ssh/id_daily.pub" \
-o ~/.ssh/id_daily-cert.pub \
https://ca.internal/sign-user
echo "Certificate issued. Valid for 8 hours."
场景二:临时运维访问
# 打包工单系统签发 2 小时临时证书
ssh-keygen -s /etc/ssh/ca_ops_key \
-I "ticket-12345-$(whoami)" \
-n "$TARGET_USER" \
-V "+2h" \
-O "force-command=/usr/local/bin/ops-script.sh" \
-O "no-port-forwarding" \
~/.ssh/id_temp.pub
场景三:大规模集群管理
# 100 台服务器,传统方式需要部署 100 次公钥
# 证书方式:只需在每台服务器上部署 CA 公钥(一次)
# 批量部署 CA 公钥
ansible all -m copy -a \
"src=ca_user_key.pub dest=/etc/ssh/ca_user_key.pub mode=0644"
ansible all -m lineinfile -a \
"path=/etc/ssh/sshd_config line='TrustedUserCAKeys /etc/ssh/ca_user_key.pub'"
ansible all -m systemd -a "name=sshd state=reloaded"
扩展阅读
下一章: 第5章 密码认证与多因素认证 → 学习密码认证、PAM 集成和多因素认证配置。