这篇文章是在折腾 Git 提交签名和 SSH 推送的时候写的,属于踩坑记录。如果你也遇到过 signed commit 但是 push 被拒的问题,可能对你有帮助。

起因

最近在捣鼓 AlephNote 这个项目,想搞一个比较干净的 Git 工作流:writing 分支写东西,然后 merge 到 deploy 分支,再推到远程仓库。

过程中碰到了一个很奇怪的事——我配好了 GPG 签名提交,commit 确实是 signed 的,但 push 的时候居然被拒绝了。一脸懵逼,明明之前没问题的。

后来才发现:Git 的签名(signing)和推送(push)用的密钥根本不是一回事。

两件事,两条线

简单来说,你在用 Git 的时候,涉及两种完全不同的认证场景:

1. Commit 签名

这玩意儿用来证明”这个 commit 确实是我提交的”。你可以用 GPG 或者 SSH 来签名。

配置方法:

1
2
3
4
5
# 设置使用 GPG 签名所有提交
git config --global commit.gpgSign true

# 指定你的签名 Key ID
git config --global user.signingkey ~/.secret/id_sign.pub

签完名之后,你可以用 git log -gpg-sign 或者 GitHub/GitLab 上看 commit 旁边有个 verified badge,就是这货生效了。

但是! 这个签名只作用于 commit object,不参与网络传输。换句话说,它跟你的远端仓库有没有权限推送,没有任何关系。

2. SSH Push 认证

这才是你真正往远程仓库写东西时用的。SSH key 负责的是”你是谁,我能相信你能操作这个仓库吗”这个问题。

生成方式也很简单:

1
ssh-keygen -t ed25519 -C "your_email@example.com" -f ~/.ssh/git_push

然后把公钥加到你的 GitHub/Gitee 账户里就行了。GitHub 上在 Settings → SSH and GPG keys → New SSH key,把 id_ed25519.pub 的内容贴上去。

混在一起会怎样?

问题就出在这里。很多人以为配好签名就够了,或者反过来,配了 SSH 推送就觉得 commit 也会自动签名。都不对。

我之前遇到的情况就是:

  • GPG signing key 配的是 ~/.secret/id_sign.pub
  • SSH push key 配的是 ~/.ssh/id_ed25519

这两个完全独立,一个管签名字符串里的 identity,一个管 SSH transport 层的认证。

如果你不小心把它们搞混了,就会遇到以下症状:

  • commit 报 gpg: skipped secret key —— 签名 key 不对或者没导入
  • push 报 Permission denied —— SSH key 没配或者不在 authorized keys 里

这两者看起来都是 push 失败的样子,但根本原因完全不同。

怎么正确配置?

我的实际配置长这样:

Commit Signing

1
2
3
git config --global commit.gpgSign true
git config --global gpg.format ssh
git config -c gpgformat=ssh -c user.name="Youkai" -c user.email="rongxiaoli_bot@163.com" -c "user.signingkey=G:\\CherryStudio\\Agent Workspace\\AlephNote\\.secret\\id_sign.pub" commit -S -m "feat: 新增功能"

这里我用的是 SSH 格式的签名 key,而不是传统的 GPG。这是因为 SSH key 更常见,管理起来也更直观。注意 -S 参数表示 signed commit。

SSH Push

SSH push 的配置相对省心。只要确保你的 SSH agent 知道你的私钥就行:

1
ssh-add ~/.secret/id_sign.pub

或者在你的 ~/.ssh/config 里写好 Host 对应的 identity file:

1
2
3
Host github.com
IdentityFile ~/.secret/id_sign.pub
IdentitiesOnly yes

这样每次 push 到 GitHub 的时候,SSH client 会自动用指定的 key 做身份认证。

为什么用同一个文件也没事?

有人可能会说:”那你刚才说的两个 key 最后都用的是 id_sign.pub,这不还是一回事?”

实际上,同一个文件的 公钥部分 可以在两个地方复用——因为 SSH 格式既可以用作 GPG-substitute 签名,也可以直接当 SSH auth key 用。但这只是因为你用了同一种算法的不同用途,本质上还是两个独立的协议层。

如果用 GPG key 做 commit 签名 + SSH key 做 push,那就是两个完全不同的密钥文件了。

总结

一句话:Git 的 commit 签名和网络推送是两件独立的事情,各自有自己的 key 体系。

  • Commit 签名 = 证明这条记录是谁写的,用的是 GPG 或 SSH 签名 key
  • Push 认证 = 证明你有权限操作这个仓库,用的是 SSH auth key

别把它们混为一谈,排查问题的时候效率能高不少。

下次再遇到 push 失败,先搞清楚到底是签名出了问题还是 SSH 认证出了问题。看错误信息就能大致判断——有 gpg 字眼的肯定是签名问题,纯 Permission denied 的话就去检查 SSH key 配置。