Git 的签名和推送密钥不是一回事
这篇文章是在折腾 Git 提交签名和 SSH 推送的时候写的,属于踩坑记录。如果你也遇到过 signed commit 但是 push 被拒的问题,可能对你有帮助。
起因
最近在捣鼓一个项目,想搞一个比较干净的 Git 工作流:writing 分支写东西,然后 merge 到 deploy 分支,再推到远程仓库。
过程中碰到了一个很奇怪的事——我配好了签名提交,commit 确实是 signed 的,但 push 的时候居然被拒绝了。一脸懵逼,明明之前没问题的。
后来才发现:Git 的签名(signing)和推送(push)用的密钥根本不是一回事。
两件事,两条线
简单来说,你在用 Git 的时候,涉及两种完全不同的认证场景:
1. Commit 签名
这玩意儿用来证明”这个 commit 确实是我提交的”。你可以用 GPG 或者 SSH 来签名。
签名只作用于 commit object,不参与网络传输。换句话说,它跟你的远端仓库有没有权限推送,没有任何关系。
关键点:签名配置指向的是公钥文件(.pub)。
2. SSH Push 认证
这才是你真正往远程仓库写东西时用的。SSH key 负责的是”你是谁,我能相信你能操作这个仓库吗”这个问题。
生成方式也很简单:
1 | ssh-keygen -t ed25519 -C "your_email@example.com" -f ~/.ssh/id_ed25519 |
然后把公钥加到你的 GitHub/Gitee 账户里就行了。GitHub 上在 Settings → SSH and GPG keys → New SSH key,把 id_ed25519.pub 的内容贴上去。
关键点:SSH 认证配置指向的是私钥文件(不带 .pub)。
混在一起会怎样?
问题就出在这里。很多人以为配好签名就够了,或者反过来,配了 SSH 推送就觉得 commit 也会自动签名。都不对。
这两个环节完全独立,一个管签名字符串里的 identity,一个管 SSH transport 层的认证。
如果你不小心把它们搞混了,就会遇到以下症状:
- commit 报
gpg: skipped secret key—— 签名 key 不对或者没导入 - push 报
Permission denied—— SSH key 没配或者不在 authorized keys 里
这两者看起来都是 push 失败的样子,但根本原因完全不同。
常见配置误区
网上很多教程(包括我最初的写法)都有这几个坑,先排掉:
误区 1:ssh-add 加载公钥
1 | ❌ 错:ssh-add 不接受 .pub 公钥 |
ssh-add 只能加载私钥(且是未加密或已解锁的私钥),加载 .pub 文件会直接报错。
误区 2:IdentityFile 指向公钥
1 | # ❌ 错:IdentityFile 必须是私钥 |
SSH 认证时 IdentityFile 必须指向私钥。指向公钥文件,SSH client 根本找不到对应的私钥做签名握手,push 必被拒。
误区 3:配置键写错
1 | ❌ 错:gpgformat 少了一个点号,Git 会静默忽略 |
误区 4:以为”公钥部分可以复用”
有人会问:”同一个文件的公钥能不能既签名又认证?”答案是不能混用,因为两条线指向的文件类型根本不同:
| 用途 | 指向的文件 | 原因 |
|---|---|---|
user.signingkey(签名) |
公钥 id_sign.pub |
SSH 签名格式需要公钥来验证 |
IdentityFile(推送认证) |
私钥 id_auth |
SSH 握手需要私钥做签名 |
签名指向公钥、认证指向私钥,这是两个不同性质的密钥文件,不存在”复用”。
怎么正确配置?
我的实际配置长这样,假设有两个独立的 key:
1 | ~/.secret/id_sign ← 私钥(签名) |
路径中的
~表示你的用户主目录(Windows 下通常是C:\Users\你的用户名\)。请把示例路径替换成你自己存放密钥的实际位置。
Commit Signing(仓库内配置,不动全局)
仓库本地配置(--local)的优先级高于全局配置,所以完全可以只在这个仓库里独立设置签名 key,不影响其他仓库,也不怕被全局覆盖。
1 | cd /path/to/your/repo |
之后在这个仓库里普通的 git commit 就会自动用 id_sign 签名,验证方式:
1 | git log --show-signature -1 |
SSH Push(私钥走 SSH 层)
推送认证走的是 SSH 层,与 git config 无关。在 ~/.ssh/config 里指定私钥:
1 | Host github.com |
如果不想改 SSH config,也可以用环境变量临时指定:
1 | GIT_SSH_COMMAND='ssh -i ~/.secret/id_auth' git push |
验证
1 | 1. 确认签名配置在仓库内生效 |
总结
一句话:Git 的 commit 签名和网络推送是两件独立的事情,各自有自己的 key 体系。
- Commit 签名 = 证明这条记录是谁写的,用的是 GPG 或 SSH 公钥,在仓库内用
git config --local配置 - Push 认证 = 证明你有权限操作这个仓库,用的是 SSH 私钥,在
~/.ssh/config里配置
别把它们混为一谈,排查问题的时候效率能高不少。
下次再遇到 push 失败,先搞清楚到底是签名出了问题还是 SSH 认证出了问题。看错误信息就能大致判断——有 gpg 字眼的肯定是签名问题,纯 Permission denied 的话就去检查 SSH key 配置。