用 SSH 密钥签名 Git 提交

摘要

为什么要签名

提交里的作者名和邮箱来自本机的 user.name、user.email。Git 只把这两项写进记录,不会核对此人是否拥有这个邮箱。别人把配置改成你的名字再推送,历史里看起来就是你写的。

签名用私钥对这一次提交做校验。校验范围包括目录树、父提交、作者和说明。其中任何一项被改过,校验都会失败。GitHub 同时满足这三件事时,提交页面显示 Verified:签名能通过、公钥已加到该账号的 Signing Key、提交邮箱是该账号已验证的邮箱。

这样别人能区分两件事:邮箱栏写成了你,以及这次提交是由持有对应私钥的人生成的,生成之后内容没有被改过。仓库还可以要求 main 上的提交必须带这个标记,见 用 Rulesets 保护 GitHub 的 main 分支。

签名写进提交

user.signingkey 指向公钥文件。commit.gpgsign 打开后,之后每一次 git commit 都会签名。已经存在的提交不会被补签。这三项都写进 ~/.gitconfig,新开终端仍然生效。

1
2
3
4
# gpg.format 改成 ssh 之后,签名不再走 GPG
git config --global gpg.format ssh
git config --global user.signingkey ~/.ssh/id_ed25519.pub
git config --global commit.gpgsign true

只想给某一次提交签名时,不要设置 commit.gpgsign,在那次提交上加 -S:

1
git commit -S -m "只签这一次"

标签一并签名时再执行下面这条。它同样写入 ~/.gitconfig。

1
git config --global tag.gpgSign true

提交用的邮箱来自 user.email,并且要和 GitHub 账号里已验证的邮箱一致。查看和修改配置见 Git安装和常用命令(最新) 第 15 节。

把公钥加到 GitHub

同一把公钥如果已经用来登录,还要再加一次,类型选 Signing Key。登录和签名在 GitHub 上是两条记录。

网页入口:头像 → Settings → SSH and GPG keys → New SSH key,Key type 选 Signing Key,Key 里粘贴 ~/.ssh/id_ed25519.pub 的整行。

当前 gh 活动账号也可以这样加。--type 不写时默认是 authentication,签名必须写明 signing。

1
gh ssh-key add ~/.ssh/id_ed25519.pub --type signing --title "laptop signing"

公司账号用另一把公钥时,先 gh auth switch 到那个账号,再执行一次,--title 和公钥路径换成公司的。本机则把 user.signingkey 写进 ~/.gitconfig-work,让 ~/work/ 下的仓库用那把公钥。目录切换见 同一台电脑使用两个 GitHub 账号。

本机先确认签名

GitHub 上的 Verified 依赖网站上的公钥和已验证邮箱。本机用 allowed_signers 做自己的校验,两处互不影响。

1
2
3
4
5
6
7
mkdir -p ~/.config/git

# 邮箱必须等于 user.email。namespaces="git" 表示这行只给 Git 签名校验用
# >> 追加到文件,不会清掉已有的行
printf '%s namespaces="git" %s\n' "$(git config --global user.email)" "$(cat ~/.ssh/id_ed25519.pub)" >> ~/.config/git/allowed_signers

git config --global gpg.ssh.allowedSignersFile ~/.config/git/allowed_signers

在临时仓库里做一次空提交。这个目录可以事后整个删掉。

1
2
3
4
mkdir -p /tmp/sign-test && cd /tmp/sign-test
git init
git commit --allow-empty -m "sign test"
git log -1 --show-signature

输出里有 Good "git" signature 时,本机校验通过。报错时先看这三条实际读到的值,以及 allowed_signers 第一列邮箱是否就是这次提交的邮箱:

1
git config --show-origin --get-regexp 'gpg.format|user.signingkey|commit.gpgsign'

推到 GitHub 之后

把签过名的提交推上去,打开该提交页面。显示 Verified 时,公钥、签名和已验证邮箱三处都对上了。

本机已经是 Good "git" signature,网页仍是 Unverified 时,按这个顺序查:GitHub 上这把公钥的类型是不是 Signing Key,提交邮箱是不是该账号已验证的邮箱,gh 是否把公钥加到了另一个账号。

停止自动签名

下面这条关掉以后的自动签名,已经签过的提交保持原样。

1
git config --global --unset commit.gpgsign

连签名密钥一起去掉时,再执行:

1
2
git config --global --unset gpg.format
git config --global --unset user.signingkey

~/.config/git/allowed_signers 只影响本机校验,删掉对应行即可。GitHub 上的 Signing Key 在 SSH and GPG keys 页面删除。