Daily Build

给 GitHub commit 配置 SSH 签名

git 的「作者」字段是完全自由填写的——我可以把 user.email 设成 linus@kernel.org,commit 出来的记录就显示是 Linus Torvalds 写的。GitHub 也不会拦:它只看 email,commit 推上去后会显示对应账号的头像。

Commit signature 就是用密码学手段证明「这个 commit 真的是我写的」。用一把私钥签名,GitHub 用对应的公钥验证,通过后页面上会显示一个绿色 Verified 徽章。

这事在多人协作的仓库里很重要——任何人都能伪装成 maintainer 推送看似人畜无害的代码。即使是个人项目,签名也是账号「被 push 历史」的可信度证明:哪天有人黑进你的 GitHub 账号偷塞了一个未签名的 commit,验证记录能让旁人一眼看出真伪。

配置一次就一劳永逸,下面记录完整步骤。

选 SSH 不选 GPG

历史上 git 用 GPG 签名,配置麻烦、工具难用、密钥管理一团糟。2022 年起 git 支持用 SSH key 签名,强烈推荐这条路。理由:

配置步骤

1. 生成 SSH key

ssh-keygen -t ed25519 -C "<id>+<username>@users.noreply.github.com"

-t ed25519 选 Ed25519 算法。比传统 RSA 短得多(公钥就一行)、生成快、安全等级更高。系统的 OpenSSH < 6.5(2014 年前的老机器)才需要回退用 -t rsa -b 4096

-C 是 comment,纯标识用途,不参与加密——填什么都行,但用 GitHub 给的 noreply 邮箱(<id>+<username>@users.noreply.github.com)有几个好处:

实际的 noreply 邮箱在 GitHub 网页的 Settings → Emails 里能看到,形如 12345678+yourname@users.noreply.github.com,下面所有地方出现的占位符都换成你自己的那个。

一路回车走完默认(保存到 ~/.ssh/id_ed25519)。

2. 告诉 git 用这把 key 签名

git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true

三条命令分别做的事:

3. 配置本地的 allowed_signers

git 在本地验证签名时需要一个 allowed_signers 文件,告诉它「这把 public key 对应的是谁」:

mkdir -p ~/.config/git
echo "<id>+<username>@users.noreply.github.com $(cat ~/.ssh/id_ed25519.pub)" > ~/.config/git/allowed_signers
git config --global gpg.ssh.allowedSignersFile ~/.config/git/allowed_signers

这一步严格说不必要——少了它 commit 还是能签名、GitHub 还是能验证。它只影响本地git log --show-signature 命令能不能 verify 自己的签名。配上未来不会困惑。

4. 把公钥加到 GitHub(作为 Signing Key)

IMPORTANT

GitHub 区分两种 key:Authentication Key(用于 push/pull 认证)和 Signing Key(用于 commit 签名验证)。同一把公钥要分别添加两次,作为两种用途。

操作:

cat ~/.ssh/id_ed25519.pub

复制输出的公钥内容,浏览器打开 GitHub → Settings → SSH and GPG keys → New SSH keyKey type 下拉选 Signing Key(默认是 Authentication Key——别选错),粘贴公钥,保存。

如果之前已经把这把 key 加过 Authentication 用途,Signing 这块需要单独再加一次。

5. 验证

随便找一个 git 仓库测一下:

echo "test" >> README.md
git commit -am "test signed commit"
git log --show-signature -1

看到类似这行就说明本地验证通过:

Good "git" signature for <id>+<username>@users.noreply.github.com with ED25519 key SHA256:...

push 上去后 GitHub 上这条 commit 会显示 Verified 绿章。

邮箱一致性(最容易踩的坑)

GitHub 验证 commit 签名时检查三件事必须一致:

  1. commit 的 author emailgit config user.email
  2. GitHub 账号绑定并验证过的 emailSettings → Emails
  3. allowed_signers 里写的 email

如果启用了 GitHub 的 email privacy(隐藏真实邮箱),就用代理邮箱 <id>+<username>@users.noreply.github.com 贯穿三处。先确认 git config --global user.email 设的是 noreply 邮箱:

git config --global user.email
# 输出应该是 <id>+<username>@users.noreply.github.com
# 如果不是,改:
git config --global user.email "<id>+<username>@users.noreply.github.com"

三处不一致的话,commit 在 GitHub 上会显示 Unverified——绿章变成黄色感叹号。

排错

配错了的时候按以下顺序排查,绝大部分问题都能定位。

症状一:commit 在 GitHub 上显示 Unverified

最常见。git log --show-signature -1 看本地:

症状二:commit 时报「error: gpg failed to sign the data」

git 用错了签名工具。检查 git config --global gpg.format,输出必须是 ssh。如果是空的或 openpgp,重设:

git config --global gpg.format ssh

症状三:git log --show-signature 报「key is not in allowed_signers file」

allowed_signers 文件没建好或 git 找不到它。检查:

cat ~/.config/git/allowed_signers   # 看内容是不是 "<email> ssh-ed25519 AAAA..."
git config --global gpg.ssh.allowedSignersFile   # 看 git 是否指向这个文件

allowed_signers 文件的格式是「邮箱 + 空格 + 完整公钥」,缺一不可。注意公钥要带 ssh-ed25519 算法前缀。

症状四:仓库里有些 commit 显示 Verified、有些 Unverified

混合状态正常——Unverified 通常是配置签名之前的老 commit、或临时 --no-gpg-sign 跳过签名的 commit、或别人 push 进来的 commit。不用管历史,关注新 commit 即可。

换电脑了怎么办

签名 key 是绑机器的(私钥不应该跨机器复制)。换电脑后:

  1. 新机器上重跑上面的步骤 1-3,生成新 key + 配 git
  2. 新公钥作为 Signing Key 添加到 GitHub(顺便也加 Authentication Key,省得 push 不了)
  3. 老电脑那把 key 不用删——GitHub 一个账号可以挂多把 signing key,互不冲突

一些细节

commit 时要不要输密码?

如果 SSH key 设了 passphrase,每次 commit 要输入。可以用 ssh-agent 缓存解锁状态——ssh-add ~/.ssh/id_ed25519 一次,之后整个登录会话不用再输。如果 key 没设 passphrase,就完全无感。

历史 commit 怎么办?

不会回溯签名(除非 rebase 重写历史,不推荐)。新 commit 开始签名就行,老的就老的。

在 GitHub 网页上改文件、合并 PR 也算签名吗?

算。GitHub 用自己的 GPG key(commit 历史里看到的 author 是 web-flow)替你签——网页上点 Edit 改文件、合 PR、squash merge 之类的操作产生的 commit 都自动签名。所以不需要为「网页编辑会破坏签名链」担心。