bigywater.tech
Git · 72

SSH 签名提交与本地验证

用 SSH 密钥给提交签名,比 GPG 轻便得多。这篇看签名在 commit 对象里长什么样、怎样在本地验证、怎样配置“信任谁”,以及篡改、陌生人签名、密钥过期时各自会怎么显示。

gpgsig
commit 对象里放 SSH 签名的头,从 BEGIN SSH SIGNATURE 到 END SSH SIGNATURE
No principal matched
签名有效但公钥不在 allowed_signers 里时的提示
G / U / N
%G? 状态码:好且受信 / 好但不受信 / 没有签名

签名在哪

实测 · 本机执行输出
  $ git config --get-regexp "gpg|signing" | sed -E "s#/[^ ]*/_ssh#<ssh目录>#"
  gpg.format ssh
  user.signingkey <ssh目录>/alice.pub
  | tree <sha40>
  | author Alice <alice@example.com> 1767261600 +0000
  | committer Alice <alice@example.com> 1767261600 +0000
  | gpgsig -----BEGIN SSH SIGNATURE-----
  |  U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAgJrryP71ohXfy3ddKQtwME+9SS0
  |  Amw+2hmInLH/kW7lEAAAADZ2l0AAAAAAAAAAZzaGE1MTIAAABTAAAAC3NzaC1lZDI1NTE5
  |  AAAAQDBULC1lJV0+/lOKCuYx1W2nk5GX7rc7QdBoumEzwaaRyMCVGzivgKR2qOAggy7FiX
  |  AvzzKlCo8NT5ieI26NIww=
  |  -----END SSH SIGNATURE-----
  | 
  | 签名的提交
  (gpgsig 头:从 -----BEGIN SSH SIGNATURE----- 到 -----END SSH SIGNATURE-----,后续行以空格开头)

设置 gpg.format=ssh 和 user.signingkey(公钥文件),git commit -S 之后,commit 对象里多一个 gpgsig 头,里面是 SSH 签名的文本形式。签名是提交内容(去掉 gpgsig 头)的签名,命名空间是 git。

信任谁

实测 · 本机执行输出
  $ git log -1 --show-signature --format=%s 2>&1 | head -4 | sed -E "s/SHA256:[A-Za-z0-9+\/]+/SHA256:<指纹>/"
  error: gpg.ssh.allowedSignersFile needs to be configured and exist for ssh signature verification
  No signature
  签名的提交
  配置 gpg.ssh.allowedSignersFile 之后(文件内容:一行“主体 公钥类型 公钥”):
  $ git log -1 --show-signature --format=%s 2>&1 | head -4 | sed -E "s/SHA256:[A-Za-z0-9+\/]+/SHA256:<指纹>/"
  Good "git" signature for alice@example.com with ED25519 key SHA256:<指纹>
  签名的提交
  签名里的指纹与 ssh-keygen -l 给出的 alice 公钥指纹一致: True

git 需要 gpg.ssh.allowedSignersFile:每行“主体 [选项] 公钥”。没有它就无法验证(No signature)。公钥在白名单里才算 Good for 某主体;不在就是 No principal matched;白名单条目可带 valid-before 等选项。

手工验证

实测 · 本机执行输出
  ssh-keygen -Y verify: Good "git" signature for alice@example.com with ED25519 key SHA256:<指纹> | 退出码 0
  改一个字之后再验证: Could not verify signature.
Signature verification failed: incorrect signature | 退出码 255
  命名空间不是 git(-n other): Could not verify signature.
Couldn't verify signature: namespace does not match | 退出码 255

去掉 gpgsig 头的 commit 内容用 ssh-keygen -Y verify -n git 验证,结果与 git 一致;改一个字符、换命名空间都会失败。

篡改

实测 · 本机执行输出
  $ git verify-commit <篡改后的提交>
    Could not verify signature.
    Signature verification failed: incorrect signature
  退出码 1

签名覆盖提交的全部内容,保留原来的 gpgsig 头而改动说明得到的新对象,git verify-commit 报 incorrect signature。

陌生人与过期

实测 · 本机执行输出
  $ git log -1 --show-signature --format=%s 2>&1 | head -4 | sed -E "s/SHA256:[A-Za-z0-9+\/]+/SHA256:<指纹>/"
  Good "git" signature with ED25519 key SHA256:<指纹>
  No principal matched.
  陌生人签名的提交
  $ git log -1 --show-signature --format=%s 2>&1 | head -4 | sed -E "s/SHA256:[A-Za-z0-9+\/]+/SHA256:<指纹>/"
  Good "git" signature with ED25519 key SHA256:<指纹>
  /work/_ssh/allowed_signers_expired:1: key has expired: verify time 2026-01-01T18:02:00 > valid-before 2020-01-01T00:00:00
  No principal matched.
  alice 再签一次

公钥不在白名单里:签名本身有效,git 显示 Good "git" signature with ED25519 key … 但接着 No principal matched;白名单条目带 valid-before 且已过期时同样不受信,并说明 key has expired。

状态码

实测 · 本机执行输出
  $ git log --format='%G? %GS  %s' | sed -E 's/^(.) /\1  /'
  N    没签名的提交
  G  alice@example.com  alice 再签一次
  U    陌生人签名的提交
  G  alice@example.com  签名的提交
  $ git tag -v v1 2>&1 | head -3 | sed -E "s/SHA256:[A-Za-z0-9+\/]+/SHA256:<指纹>/"; git cat-file -p v1 | grep -c "BEGIN SSH SIGNATURE" | sed "s/^ */标签对象里的签名块个数: /"
  Good "git" signature for alice@example.com with ED25519 key SHA256:<指纹>
  object d0edab54455df698c71810dacfe3631435b083d0
  type commit
  标签对象里的签名块个数: 1

%G?:G 好签名且受信,U 好签名但不受信,B 坏签名,N 没有签名,E 无法检查。标签用 git tag -s 签名,git tag -v 验证。

分步演示

SSH 签名
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / git 2.54.0(Apple Git-157);每个实验在临时目录里新建仓库,HOME 和全局配置指向实验专用的空目录(不读用户自己的 git 配置),提交的作者与时间固定以便复现;需要网络协议的实验用本机的 file:// 或 git daemon 回环端口(9800 以上),不碰任何远程仓库;计时用 time.perf_counter,数字每次运行会有小幅抖动。密钥用 ssh-keygen 生成 ed25519(无口令),保存在实验目录里反复使用以便指纹稳定;所有验证都是本机离线完成;输出里指纹做了替换;验证时间显示的 “2026-01-01” 来自固定的提交时间
    • 没有验证:GitHub 等平台对 SSH 签名的 “Verified” 显示;ssh-agent 与硬件密钥(FIDO);RSA/ECDSA 密钥;allowed_signers 里的 cert-authority、namespaces 选项;吊销列表;git log --show-signature 在合并提交上的行为;GPG 签名与 SSH 签名的混用
    京ICP备2026067502号-1京公网安备11010502063487号