Skip to content

Mac 上的 GitHub SSH Key:多账号时为什么总是认错人 ​

工作仓库 push 时报「Repository not found」,可密钥明明已经加到工作账号上了。用 ssh -G 打印出最终配置,就能看到原因:写在前面的 Host * 让工作仓库也带上了个人密钥,而且排在第一位。GitHub 按个人账号完成了认证,自然找不到工作仓库。

本文先把 SSH 认证链路讲清楚,再给出单账号和多账号的配置方法。文中命令在 macOS 27、OpenSSH 10.3p1、Git 2.55 上验证。实验使用临时目录下的测试密钥,没有改动真实的 ~/.ssh。

一、先说结论 ​

  • GitHub 只看公钥。ssh-keygen -C 后面的邮箱只是写在公钥末尾的注释,不参与认证;提交记录里显示的作者由 git config user.email 决定,两者互不相关。
  • 给私钥设置口令,再交给钥匙串保管。--apple-use-keychain 和 UseKeychain 保存的是口令,不是密钥本身,所以口令为空时它们什么也不做。
  • 配置写成 Host github.com,不要写成 Host *。IdentityFile 会累加:Host * 里的密钥会被带到每一台主机,多账号时顺序一错就认错人。
  • 多账号用 Host 别名,每个别名只给一个密钥,加上 IdentitiesOnly yes。
  • 排查先用 ssh -G <host> 看最终生效的配置,再用 ssh -vT 看实际尝试了哪些密钥。

二、认证链路 ​

git pushgit@github.com:…~/.ssh/config按 Host 匹配配置ssh-agent持有解锁后的私钥GitHub用公钥找到账号macOS 钥匙串保存私钥的口令决定用哪个私钥再检查该账号对仓库的权限口令为空时,钥匙串无事可做;--apple-use-keychain 与 UseKeychain 的作用对象是口令,不是密钥本身
图 1 · GitHub 只根据公钥判断你是哪个账号;生成密钥时 -C 后面的邮箱只是注释,不参与认证

一次 git push 走 SSH 时,大致经过这几步:

  1. Git 把远程地址 git@github.com:owner/repo.git 交给 ssh;
  2. ssh 读取 ~/.ssh/config,按 Host 匹配出要用的主机名、用户和私钥;
  3. 私钥如果有口令,由 ssh-agent 持有解锁后的私钥,macOS 上口令可以存进钥匙串;
  4. GitHub 收到签名后,根据公钥找到它属于哪个账号,再检查这个账号有没有仓库权限。

第 4 步是理解多账号问题的关键:一把公钥只能添加到一个 GitHub 账号上。连上之后你是谁,完全取决于 ssh 先递过去的是哪把私钥。

三、单账号配置 ​

3.1 生成密钥 ​

bash
ssh-keygen -t ed25519 -C "macbook-2026"
  • -t ed25519:GitHub 文档推荐的算法。实测注释为 macbook-2026 时,Ed25519 公钥 94 字节,4096 位 RSA 公钥 738 字节。只有目标系统不支持 Ed25519 时,才退回 -t rsa -b 4096。
  • -C:注释,写成能让你认出这把密钥的名字就行,比如设备名。GitHub 的密钥列表里显示的是你添加时填的 Title,不是这个注释。
  • 口令建议设置。私钥文件一旦泄露(备份、同步盘、误提交),没有口令就等于直接交出了账号权限。

生成后,~/.ssh/ 下会多出两个文件:id_ed25519(私钥,权限 600)和 id_ed25519.pub(公钥)。

3.2 配置 ~/.ssh/config ​

ssh-config
Host github.com
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_ed25519
  IdentitiesOnly yes
  AddKeysToAgent yes
  UseKeychain yes
配置项作用
IdentityFile这台主机使用的私钥
IdentitiesOnly yes只用这里指定的密钥,不去尝试 agent 里的其他密钥
AddKeysToAgent yes第一次使用时自动加进 ssh-agent,之后不用反复输入口令
UseKeychain yesmacOS 专有:从钥匙串读取口令,输入过的口令也会存进去

然后把口令存进钥匙串,重启后也不用再输入:

bash
ssh-add --apple-use-keychain ~/.ssh/id_ed25519

man ssh-add 对这个参数的说明是:添加密钥时,同时把口令保存到用户的钥匙串。所以它解决的是「每次开机都要输口令」,而不是「密钥会丢失」。老教程里的 -K 是它的旧写法。

3.3 添加公钥并验证 ​

bash
pbcopy < ~/.ssh/id_ed25519.pub     # 复制公钥,粘贴到 GitHub → Settings → SSH and GPG keys
ssh -T git@github.com

第一次连接会提示确认 GitHub 的主机指纹,要和 GitHub 文档公布的指纹逐字比对后再输入 yes,这一步用来防止中间人攻击。认证成功会看到 Hi <用户名>! You've successfully authenticated,这里的用户名就是 GitHub 认定的账号,排查多账号问题时很有用。

已有的 HTTPS 仓库,改一下远程地址即可:

bash
git remote set-url origin git@github.com:owner/repo.git

四、多账号:个人与工作分开 ​

4.1 问题复现 ​

很多教程会给出这样的配置:

ssh-config
Host *
  IdentityFile ~/.ssh/id_ed25519_personal

Host github-work
  HostName github.com
  IdentityFile ~/.ssh/id_ed25519_work

看起来 github-work 用的是工作密钥。用 ssh -G 打印最终配置,结果是:

text
$ ssh -G github-work | grep identityfile
identityfile ~/.ssh/id_ed25519_personal
identityfile ~/.ssh/id_ed25519_work
错误写法:Host * 在前Host github-work1. id_ed25519_personal2. id_ed25519_work认证为个人账号Repository not found正确写法:每个别名只给一个密钥,并设置 IdentitiesOnly yesHost github-workid_ed25519_work认证为工作账号用 ssh -G github-work 可以打印出最终生效的配置,先看这个再排查
图 2 · IdentityFile 会累加而不是覆盖:写在前面的 Host * 让工作仓库也先尝试个人密钥,GitHub 按个人账号认证后找不到仓库

ssh_config 的规则是:大多数配置项取第一次匹配到的值,IdentityFile 例外,它会累加。 所以两个密钥都在列表里,个人密钥还排在前面。个人密钥是有效的,GitHub 用它完成认证,把你识别成个人账号,再去找工作仓库,结果是「Repository not found」或者权限不足。这个报错很容易让人以为是仓库地址写错了。

Host * 的另一个副作用是,个人密钥会被递给所有主机,包括公司内网的 GitLab 和任何你 SSH 过去的服务器。

4.2 正确的配置 ​

每个账号一个别名,每个别名只有一个密钥:

ssh-config
Host github.com
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_ed25519_personal
  IdentitiesOnly yes
  AddKeysToAgent yes
  UseKeychain yes

Host github-work
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_ed25519_work
  IdentitiesOnly yes
  AddKeysToAgent yes
  UseKeychain yes

实测 ssh -G github-work 只剩一个 identityfile ~/.ssh/id_ed25519_work,hostname 解析为 github.com。工作仓库的远程地址要写成别名:git@github-work:acme/order-service.git。

IdentitiesOnly yes 也很重要:没有它,即使配置里只写了一个密钥,ssh 仍然可能尝试 agent 里已经加载的其他密钥,只要其中一把属于别的账号,就又回到了认错人的问题。

4.3 按目录自动切换 ​

每次 clone 都要记得改成别名,迟早会忘。可以让 Git 按目录自动处理:工作项目统一放在 ~/code/work/ 下。

~/.gitconfig:

ini
[user]
  name = Your Name
  email = me@personal.example

[includeIf "gitdir:~/code/work/"]
  path = ~/.gitconfig-work

~/.gitconfig-work:

ini
[user]
  email = me@work.example

[url "git@github-work:"]
  insteadOf = git@github.com:

实测效果(Git 2.55):

位置user.email实际连接的 Host
~/code/work/order-serviceme@work.examplegithub-work
~/code/personal/blogme@personal.examplegithub.com
在 ~/code/work/ 下执行 git clone git@github.com:acme/payment.git—github-work

这样提交作者和认证身份都跟着目录走。注意两点:gitdir: 末尾的 / 表示匹配这个目录下的所有仓库;在 ~/code/work/ 下但还不是 Git 仓库的目录里,条件不成立,读到的仍然是个人配置。

五、排查顺序 ​

  1. ssh -G <host>:看最终生效的 hostname、user、identityfile。多账号问题大多在这一步就能看出来。
  2. ssh -T git@<host>:看 GitHub 把你认成了哪个账号。
  3. ssh -vT git@<host>:看实际按什么顺序尝试了哪些密钥,哪一把被接受。
  4. ssh-add -l:看 agent 里当前加载了哪些密钥。
  5. git remote -v 与 git config --show-origin user.email:确认仓库用的是哪个地址、哪份配置。

六、常见误区 ​

  • 「-C 要填 GitHub 账号的邮箱」:它只是注释。
  • 「--apple-use-keychain 让 Mac 记住密钥」:它记住的是口令,口令为空时不起作用。
  • 「Host * 写一个默认密钥最省事」:IdentityFile 会累加,多账号时会认错人,还会把密钥递给不相关的主机。
  • 「Repository not found 就是地址写错了」:先用 ssh -T 确认 GitHub 认出来的是哪个账号。
  • 「第一次连接的指纹提示直接 yes」:应该和官方公布的指纹比对。

小结 ​

SSH 认证只认公钥,所以多账号问题归根结底是「ssh 先递出了哪把私钥」。配置上的原则就三条:按主机写配置,不写 Host *;每个别名只配一个密钥并打开 IdentitiesOnly;用 includeIf 让目录决定身份。私钥配上口令,再交给钥匙串保管,安全和便利可以兼得。


配套实验

参考资料

文章以 CC BY-NC-SA 4.0 授权 · 代码片段以 MIT 授权