SSH 密钥管理
SSH 密钥管理
1. 密钥对原理回顾
SSH 密钥认证使用非对称加密:本机持有私钥,远端(GitHub / 服务器)保存公钥。
私钥 ~/.ssh/id_ed25519 ← 绝对不能泄露,只存本机
公钥 ~/.ssh/id_ed25519.pub ← 可以任意分发,上传到 GitHub / 服务器
认证流程(私钥从不离开本机):
Client Server
| ① 发起连接,声明用户名 |
|-------------------------------------> |
| |
| ② 找到对应公钥,生成随机挑战 R |
| 用公钥加密 R,发回 |
|<------------------------------------- |
| |
| ③ 用本地私钥解密 R |
| 计算 hash(R + session_id) 发回 |
|-------------------------------------> |
| |
| ④ 验证 hash 是否正确 |
|<---------- 登录成功 / 失败 ---------- |
2. 生成密钥对
2.1 基本命令
ssh-keygen -t ed25519 -C "your_email@example.com"
各参数含义:
| 参数 | 说明 |
|---|---|
-t ed25519 |
指定算法类型 |
-C "注释" |
附加注释,会写入公钥末尾,方便识别密钥归属 |
-f ~/.ssh/id_ed25519 |
指定保存路径(不指定则交互询问) |
-b 4096 |
密钥位数(RSA 专用,Ed25519 固定 256 bit 忽略此参数) |
-N "passphrase" |
直接指定私钥口令(空字符串表示无口令) |
2.2 算法选择
| 算法 | 密钥长度 | 安全性 | 速度 | 推荐程度 |
|---|---|---|---|---|
| Ed25519 | 256 bit | ★★★★★ | 最快 | 首选 |
| ECDSA | 256 bit | ★★★★ | 快 | 可用(部分老系统不支持) |
| RSA 4096 | 4096 bit | ★★★★ | 慢 | 兼容老系统时用 |
| RSA 2048 | 2048 bit | ★★★ | 较慢 | 不推荐新建 |
| DSA | 1024 bit | ★ | — | 已淘汰,禁用 |
结论: 现代系统(OpenSSH 6.5+,即 2014 年后)一律用 Ed25519。
2.3 交互过程详解
执行 ssh-keygen -t ed25519 -C "me@example.com" 后:
# 第一步:选择保存路径
Enter file in which to save the key (/home/user/.ssh/id_ed25519):
- 直接回车:使用默认路径
~/.ssh/id_ed25519 - 输入自定义路径:如
~/.ssh/github_work(多账号时必须自定义)
# 第二步:设置私钥口令(passphrase)
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
- 口令的作用:私钥文件本身会被加密,即使文件被盗,没有口令也无法使用
- 留空:无口令,使用方便但文件安全性低
- 建议:重要密钥(如 GitHub 个人账号)设置口令,配合 ssh-agent 使用
生成结果:
Your identification has been saved in /home/user/.ssh/id_ed25519
Your public key has been saved in /home/user/.ssh/id_ed25519.pub
The key fingerprint is:
SHA256:ABC123...xyz me@example.com
The key's randomart image is:
+--[ED25519 256]--+
| .o+o. |
...
+----[SHA256]-----+
2.4 生成的文件
~/.ssh/
├── id_ed25519 ← 私钥,权限必须 600
└── id_ed25519.pub ← 公钥,权限 644
私钥权限必须是 600,否则 SSH 会拒绝使用:
chmod 600 ~/.ssh/id_ed25519
# SSH 报错示例:
# Permissions 0644 for '/home/user/.ssh/id_ed25519' are too open.
# It is required that your private key files are NOT accessible by others.
查看公钥内容(上传到 GitHub 时复制这个):
cat ~/.ssh/id_ed25519.pub
# ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA... me@example.com
3. 本地多密钥管理
实际使用中通常需要多个密钥:个人 GitHub、公司 GitHub、各台服务器……
3.1 目录结构规划
~/.ssh/
├── config ← 核心配置文件,控制路由规则
├── known_hosts ← 已信任的服务器指纹(自动维护)
│
├── id_ed25519 ← 默认密钥(个人 / 通用)
├── id_ed25519.pub
│
├── github_work ← 公司 GitHub 账号
├── github_work.pub
│
├── gitlab_personal ← 个人 GitLab
├── gitlab_personal.pub
│
└── server_prod ← 生产服务器
server_prod.pub
3.2 生成多个密钥
# 个人 GitHub
ssh-keygen -t ed25519 -C "personal@gmail.com" -f ~/.ssh/github_personal
# 公司 GitHub
ssh-keygen -t ed25519 -C "work@company.com" -f ~/.ssh/github_work
# 生产服务器(无口令,方便自动化脚本)
ssh-keygen -t ed25519 -C "deploy-key" -f ~/.ssh/server_prod -N ""
3.3 ~/.ssh/config 配置文件
config 文件是多密钥管理的核心,为不同主机指定使用哪个密钥。
# ============ GitHub 个人账号 ============
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/github_personal
IdentitiesOnly yes # 只用指定的密钥,不尝试其他
# ============ GitHub 公司账号 ============
# 用不同的 Host 别名区分同一个域名的不同账号
Host github-work
HostName github.com
User git
IdentityFile ~/.ssh/github_work
IdentitiesOnly yes
# ============ GitLab ============
Host gitlab.com
HostName gitlab.com
User git
IdentityFile ~/.ssh/gitlab_personal
IdentitiesOnly yes
# ============ 生产服务器 ============
Host prod
HostName 192.168.1.100
User ubuntu
Port 22
IdentityFile ~/.ssh/server_prod
# ============ 全局默认 ============
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
AddKeysToAgent yes # 自动将私钥加入 ssh-agent
UseKeychain yes # macOS:口令保存到 Keychain
IdentitiesOnly yes 的作用:不加这行时,SSH 还会把 ssh-agent 中的所有密钥都尝试一遍,在多密钥场景下容易混乱,加上后只用 IdentityFile 指定的密钥。
3.4 多账号 clone / push
配置好 config 后,clone 公司仓库时把 github.com 替换为 config 中的别名:
# 个人 GitHub(Host 是 github.com,直接用)
git clone git@github.com:personal/repo.git
# 公司 GitHub(Host 别名是 github-work)
git clone git@github-work:company/repo.git
# ^^^^^^^^^^^^ 这里用 config 里的 Host 别名
# 已有仓库修改 remote
git remote set-url origin git@github-work:company/repo.git
3.5 clone 时临时指定密钥
不想改 config、或者临时用一次,有三种方式:
方式一:GIT_SSH_COMMAND 环境变量(推荐)
GIT_SSH_COMMAND="ssh -i ~/.ssh/github_work" git clone git@github.com:company/repo.git
只作用于当次命令,不影响全局。
方式二:-c core.sshCommand 内联参数
git clone -c "core.sshCommand=ssh -i ~/.ssh/github_work" git@github.com:company/repo.git
效果与方式一相同,区别是 -c 的写法更适合在脚本里组合其他 git 参数。
方式三:clone 后对该仓库持久生效
git clone git@github.com:company/repo.git
cd repo
# 只对这个仓库生效,写入 .git/config
git config core.sshCommand "ssh -i ~/.ssh/github_work -o IdentitiesOnly=yes"
# 之后 git pull / push 都自动用这个密钥
git pull
三种方式对比:
| 方式 | 作用范围 | 适用场景 |
|---|---|---|
~/.ssh/config |
按 Host 匹配,永久 | 多账号日常使用(首选) |
GIT_SSH_COMMAND |
单次命令 | 临时操作、脚本 CI |
git config core.sshCommand |
单个仓库,永久 | 仓库维度隔离 |
4. 密钥优先级
SSH 连接时会按照固定顺序选择密钥,理解这个顺序能避免很多"用了错误密钥"的问题。
4.1 选择顺序
命令行 -i 参数 (最高优先级)
↓
config 文件中的 IdentityFile
↓
ssh-agent 中的密钥(按加入顺序逐个尝试)
↓
默认密钥文件(按固定顺序)
↓
交互询问密码 (最低)
4.2 默认密钥文件顺序
当没有任何 config 指定时,SSH 按以下顺序查找默认密钥:
~/.ssh/id_ed25519 ← 第一个尝试(推荐算法,优先)
~/.ssh/id_ecdsa
~/.ssh/id_rsa
~/.ssh/id_dsa ← 最后(已淘汰算法)
4.3 -i 参数(临时指定)
# 临时使用指定密钥,不走 config
ssh -i ~/.ssh/server_prod ubuntu@192.168.1.100
# 等价于在 config 中对该 Host 配置了 IdentityFile
4.4 用 -v 观察密钥选择过程
ssh -v git@github.com 2>&1 | grep -E "(identity|offer|key)"
输出示例:
debug1: Will attempt key: /home/user/.ssh/github_personal ED25519 SHA256:xxx explicit
debug1: Offering public key: /home/user/.ssh/github_personal ED25519
debug1: Server accepts key: /home/user/.ssh/github_personal ED25519
看到 explicit 说明来自 config 指定;agent 说明来自 ssh-agent;default 说明是默认路径。
多加一个 -v 可以看更多细节:ssh -vv 或 ssh -vvv
4.5 IdentitiesOnly 的重要性
# 假设 ssh-agent 里有 5 个密钥,config 里指定了 IdentityFile
# 不加 IdentitiesOnly yes:会先尝试 agent 里所有密钥,再尝试指定的
# 加了 IdentitiesOnly yes:只尝试 IdentityFile 指定的密钥
服务器有最大认证尝试次数限制(通常 3-6 次),agent 里密钥太多会导致:
Received disconnect from ... : Too many authentication failures
此时加上 IdentitiesOnly yes 即可解决。
5. ssh-agent(密钥代理)
私钥设置了口令后,每次使用都要输入。ssh-agent 将解密后的私钥缓存在内存中。
5.1 基本使用
# 启动 agent(通常系统登录时自动启动)
eval "$(ssh-agent -s)"
# 添加私钥(会提示输入口令)
ssh-add ~/.ssh/github_personal
# Enter passphrase for /home/user/.ssh/github_personal: ****
# Identity added: /home/user/.ssh/github_personal (personal@gmail.com)
# 查看已缓存的密钥
ssh-add -l
# 256 SHA256:xxx personal@gmail.com (ED25519)
# 256 SHA256:yyy work@company.com (ED25519)
# 删除特定密钥
ssh-add -d ~/.ssh/github_personal
# 清空所有缓存
ssh-add -D
5.2 macOS 自动加载(推荐)
macOS 可以将口令保存在 Keychain,重启后自动加载:
# 添加时保存到 Keychain
ssh-add --apple-use-keychain ~/.ssh/github_personal
# ~/.ssh/config 中配置自动加载
Host *
UseKeychain yes
AddKeysToAgent yes
5.3 Linux 自动加载
# 方式一:写入 ~/.bashrc / ~/.zshrc
if [ -z "$SSH_AGENT_PID" ]; then
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/github_personal 2>/dev/null
fi
# 方式二:使用 systemd user service(更优雅)
# ~/.config/systemd/user/ssh-agent.service
[Unit]
Description=SSH key agent
[Service]
Type=simple
Environment=SSH_AUTH_SOCK=%t/ssh-agent.socket
ExecStart=/usr/bin/ssh-agent -D -a $SSH_AUTH_SOCK
[Install]
WantedBy=default.target
6. 公钥部署
6.1 部署到 GitHub / GitLab
- 复制公钥内容:
cat ~/.ssh/github_personal.pub
# 或 macOS 直接复制到剪贴板
cat ~/.ssh/github_personal.pub | pbcopy
-
GitHub:Settings → SSH and GPG keys → New SSH key → 粘贴公钥
-
验证连接:
ssh -T git@github.com
# Hi username! You've successfully authenticated, but GitHub does not provide shell access.
# 公司账号用别名验证
ssh -T git@github-work
# Hi work-username! You've successfully authenticated...
6.2 部署到 Linux 服务器
# 方式一:ssh-copy-id(推荐,自动处理权限)
ssh-copy-id -i ~/.ssh/server_prod.pub ubuntu@192.168.1.100
# 方式二:手动追加
cat ~/.ssh/server_prod.pub | ssh ubuntu@192.168.1.100 \
"mkdir -p ~/.ssh && chmod 700 ~/.ssh && \
cat >> ~/.ssh/authorized_keys && \
chmod 600 ~/.ssh/authorized_keys"
服务器端 ~/.ssh/authorized_keys 的格式:每行一个公钥,可以有多个。
# ~/.ssh/authorized_keys
ssh-ed25519 AAAA...abc deploy-key
ssh-ed25519 AAAA...def personal-laptop
ssh-ed25519 AAAA...ghi work-macbook
7. known_hosts
7.1 作用
known_hosts 记录了客户端曾经连接过的服务器的公钥指纹,用于防止中间人攻击。
SSH 的服务器公钥是自签发的,没有 CA 背书,客户端无法自动判断"这台服务器是不是真的"。known_hosts 的逻辑是:
第一次连接时你确认了这台服务器,之后每次连接都拿保存的指纹比对,一旦不匹配就报警。
7.2 服务器指纹是什么
服务器自己也有一对密钥对,叫 host key(主机密钥),在安装 SSH 服务时自动生成:
ls /etc/ssh/ssh_host_*
# /etc/ssh/ssh_host_ed25519_key ← 服务器私钥
# /etc/ssh/ssh_host_ed25519_key.pub ← 服务器公钥
# /etc/ssh/ssh_host_rsa_key
# /etc/ssh/ssh_host_rsa_key.pub
这对密钥和你的个人密钥作用不同:
| 个人密钥对 | 服务器 host key | |
|---|---|---|
| 作用 | 证明你是你 | 证明服务器是服务器 |
| 私钥位置 | ~/.ssh/id_ed25519 |
/etc/ssh/ssh_host_ed25519_key |
| 公钥给谁 | 上传到 GitHub / 服务器 | 连接时发给客户端 |
指纹就是服务器公钥的 SHA256 哈希值,公钥内容长、不易比对,哈希后得到一个短摘要便于人工核验:
# 在服务器本机执行
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
# 256 SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU root@server (ED25519)
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 这就是指纹
连接时的完整流程:
客户端 服务器
| ① 发起连接 |
|-------------------------------------> |
| ② 服务器把自己的公钥发过来 |
|<------------------------------------- |
| ③ 客户端计算公钥的 SHA256 |
| 与 known_hosts 里的记录比对 |
| · 有记录且一致 → 直接建立连接 |
| · 有记录但不一致 → 报警(可能被攻击)|
| · 没有记录(第一次)→ 展示指纹让你确认|
|-------------------------------------> |
本质上和下载软件后校验 sha256sum 是同一个道理——公钥内容只要变了一个字节,指纹就完全不同。
7.3 格式
cat ~/.ssh/known_hosts
每行一条记录,格式为:主机名/IP 算法 公钥
# 明文主机名
github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
192.168.1.100 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
# 开启 HashKnownHosts 后,主机名被哈希,无法直接看出连的是哪台机器
|1|abc123==|xyz456== ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
7.4 第一次连接的确认提示
第一次连接一台新服务器时:
The authenticity of host '192.168.1.100 (192.168.1.100)' can't be established.
ED25519 key fingerprint is SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU.
Are you sure you want to continue connecting (yes/no/[fingerprint])?
输入 yes 后,该服务器的指纹写入 ~/.ssh/known_hosts,下次不再提示。
如何验证指纹是否可信(防止第一次就被中间人):
# 在服务器本机执行,获取服务器自己的公钥指纹
ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
# 256 SHA256:+DiY3wvvV6T... root@server (ED25519)
# 与连接提示里的指纹比对,一致才输入 yes
7.5 服务器公钥变了:REMOTE HOST IDENTIFICATION HAS CHANGED
服务器重装系统、更换 IP 复用、或遭遇中间人时会报:
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Offending ECDSA key in ~/.ssh/known_hosts:5
错误里会告诉你冲突记录在第几行(上面是第 5 行)。
确认是服务器重装导致的(不是攻击),删除旧记录:
# 方式一:按主机名/IP 删除(推荐)
ssh-keygen -R 192.168.1.100
ssh-keygen -R myserver.com
# 方式二:手动删除,知道行号时更快
sed -i '5d' ~/.ssh/known_hosts
# 删除后重新连接,重新确认新指纹
ssh ubuntu@192.168.1.100
7.6 常用操作
# 查询某主机的记录是否存在
ssh-keygen -F github.com
# 存在则输出该行内容,不存在则无输出
# 查看文件共有多少条记录
wc -l ~/.ssh/known_hosts
# 删除某台主机的记录
ssh-keygen -R 10.0.0.50
# 查看服务器对外暴露的所有公钥类型
ssh-keyscan 192.168.1.100
ssh-keyscan -t ed25519 github.com # 只扫 ed25519 类型
ssh-keyscan 也可以用来批量预填充 known_hosts,省去第一次连接时逐台确认:
# 批量信任内网一批机器(在可信环境下使用)
ssh-keyscan 10.0.0.1 10.0.0.2 10.0.0.3 >> ~/.ssh/known_hosts
7.7 没有数量限制
known_hosts 是普通文本文件,无数量限制,连几千台服务器都没问题。
时间长了可以清理失效记录:
# 逐个删除已下线的机器
ssh-keygen -R 10.0.0.99
# 或直接清空(下次连接时重新确认)
> ~/.ssh/known_hosts
7.8 跳过 known_hosts 检查(内网批量场景)
对于完全可信的内网,可在 config 里关掉检查:
Host 10.0.0.*
StrictHostKeyChecking no # 新主机自动信任并记录,不提示
# StrictHostKeyChecking accept-new # 更保守:只接受新主机,拒绝指纹变化的主机
Host bastion.internal
StrictHostKeyChecking no
UserKnownHostsFile /dev/null # 不写入 known_hosts,连记录都不留(每次当新主机)
注意:
StrictHostKeyChecking no会绕过中间人检测,只在完全可信的内网使用,公网绝对不要开。
8. 常见问题排查
8.1 验证当前使用的是哪个密钥
# 测试 GitHub 连接,看详细日志
ssh -vT git@github.com 2>&1 | grep -E "(Offering|Accepted|identity)"
8.2 Permission denied (publickey)
# 逐步排查
ssh -v git@github.com
# 常见原因:
# 1. 公钥没有上传到对应账号
# → 去 GitHub Settings 确认 SSH keys 列表
# 2. config 里用了 github-work 别名但仓库 remote 还是 github.com
# → git remote -v 检查,改为正确别名
# 3. authorized_keys 权限不对(服务器场景)
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
# 4. Too many authentication failures
# → config 里加 IdentitiesOnly yes
8.3 查看密钥指纹(用于比对 GitHub 上的记录)
# 查看本地公钥指纹
ssh-keygen -lf ~/.ssh/github_personal.pub
# 256 SHA256:xxxx personal@gmail.com (ED25519)
# GitHub 上 Settings → SSH keys 里也能看到对应指纹,比对确认是否匹配
8.4 修改私钥口令
# 修改(或添加/删除)私钥的口令,不需要重新生成密钥对
ssh-keygen -p -f ~/.ssh/github_personal
# Enter old passphrase:
# Enter new passphrase (empty for no passphrase):
# Enter same passphrase again:
8.5 从私钥还原公钥
# 只有私钥时,可以重新导出公钥
ssh-keygen -y -f ~/.ssh/github_personal > ~/.ssh/github_personal.pub
9. 最佳实践总结
密钥生成:
- 统一用 Ed25519,
-C注释写明用途(如邮箱或描述) - 不同用途使用不同密钥,泄露隔离
- 重要密钥设置 passphrase,配合 ssh-agent
文件管理:
- 私钥权限必须
600,.ssh目录权限700 - 所有路由规则集中在
~/.ssh/config管理 - 同一域名多账号用不同
Host别名区分
config 关键配置:
# 多账号必须加,防止 agent 密钥干扰
IdentitiesOnly yes
# 防止长连接超时断开
ServerAliveInterval 60
# macOS 自动加载
UseKeychain yes
AddKeysToAgent yes
排查工具:
ssh -v查看密钥选择过程ssh-add -l查看 agent 已缓存密钥ssh-keygen -lf查看密钥指纹
xingliuhua