---
url: https://eoht.net/notes/gpg.md
description: 生成 GPG 密钥并配置 Git OpenPGP 签名，上传公钥到 GitHub，验证提交与标签签名，并排查私钥和终端口令输入问题。
---

# 使用 GPG 签名 Git Commit {#sign-git-commits-with-gpg}

本文使用 GnuPG 的 OpenPGP 签名方式，终端示例适用于 macOS / Linux 的 Bash 或 zsh。签名用于核对提交是否由对应密钥签发，不代表代码本身安全。

## GPG 安装 {#install-gpg}

macOS 已安装 Homebrew 时：

```bash
brew install gnupg
gpg --version
```

Linux 使用所属发行版的软件包安装 GnuPG。无需为了 Git 签名额外安装 Keybase。

### 常用命令 {#common-gpg-commands}

```bash
gpg --list-keys --keyid-format LONG
gpg --list-secret-keys --keyid-format LONG
gpg --fingerprint
```

前两项分别列出公钥和可用的私钥记录，完整指纹用于准确识别密钥。为 GitHub 配置签名不需要发布到公共 keyserver。

### 生成 GPG key {#generate-gpg-key}

已有可用签名私钥时可以复用，无需重复生成。创建新密钥：

```bash
gpg --full-generate-key
```

#### 具体步骤如下 {#key-generation-steps}

1. 按本机 GnuPG 的选项选择支持签名的密钥类型和有效期。
2. 填入用于提交的姓名、邮箱；希望在 GitHub 显示验证状态时，邮箱需与账户已验证的提交邮箱匹配。
3. 设置私钥口令，并完成生成流程。
4. 保存私钥备份与生成时提示的吊销证书，分开妥善保管；不要上传到仓库或公开聊天。

算法菜单和输出随 GnuPG 版本变化，以实际提示为准。公钥可以分享，私钥与口令不能当作公钥粘贴到平台。

### 查看 GPG key {#view-gpg-keys}

```bash
gpg --list-secret-keys --keyid-format LONG
gpg --fingerprint
```

#### 示例 {#key-list-example}

输出字段可按下表理解，不要复制他人的密钥 ID 作为自己的配置：

| 字段 | 含义 |
| --- | --- |
| `pub` | 主公钥 |
| `sub` | 公共子密钥，不是私钥 |
| `sec` | 主私钥记录 |
| `ssb` | 私有子密钥记录 |
| `uid` | 用户身份，如姓名与邮箱 |
| `[S]` / `[C]` / `[E]` | 签名 / 认证 / 加密用途 |

### 密钥管理 {#gpg-key-management}

下面用变量 `GPG_KEY_FINGERPRINT` 表示刚确认的完整指纹。先替换示例值；有公钥而无对应可用私钥时，无法创建签名。

```bash
GPG_KEY_FINGERPRINT='替换为自己的完整密钥指纹'
```

#### 导出公钥 {#export-public-key}

```bash
gpg --armor --export "$GPG_KEY_FINGERPRINT"
```

#### GPG key 格式 {#gpg-key-format}

导出的 ASCII 公钥包含以下边界，复制时包括首尾两行：

```text
-----BEGIN PGP PUBLIC KEY BLOCK-----
...
-----END PGP PUBLIC KEY BLOCK-----
```

### 删除 GPG key {#delete-gpg-key}

删除密钥与撤销已经公开的密钥不是同一件事。仅在确认不再需要、备份可恢复并核对完整指纹后，才删除本地记录；存在私钥时先删除私钥：

```bash
gpg --delete-secret-keys "$GPG_KEY_FINGERPRINT"
gpg --delete-keys "$GPG_KEY_FINGERPRINT"
```

这不是完成签名配置的必做步骤，删除私钥后将无法再用它签名。

## GitHub 配置 GPG {#configure-gpg-on-github}

### 将 GPG 公钥添加到账户 {#add-gpg-public-key-to-github}

进入 GitHub 个人账户的 `Settings → SSH and GPG keys → New GPG key`，添加导出的公钥。密钥属于账户设置，不是把私钥提交到代码仓库。

### 本地代码仓库启用 GPG Sign {#enable-gpg-signing-locally}

确认本机存在可用私钥后，选择全局或单仓库配置。若先前使用 SSH 签名，必须同时将 `gpg.format` 改为 `openpgp`。

#### 全局设置 {#global-gpg-settings}

```bash
git config --global gpg.format openpgp
git config --global user.signingkey "$GPG_KEY_FINGERPRINT"
git config --global commit.gpgsign true
```

#### 指定仓库设置，需要进入代码目录 {#repository-specific-gpg-settings}

```bash
git config --local gpg.format openpgp
git config --local user.signingkey "$GPG_KEY_FINGERPRINT"
git config --local commit.gpgsign true
```

局部配置覆盖全局配置。检查 `git config user.email`，使提交邮箱符合所选密钥和平台账户要求。

### 重启 gpg-agent {#restart-gpg-agent}

修改口令输入设置后，可结束当前 agent；下次需要时 GnuPG 会重新启动它：

```bash
gpgconf --kill gpg-agent
```

参数使用两个 ASCII 连字符 `--`。

### 检查配置结果 {#configuration-example}

```bash
git config --show-origin --get gpg.format
git config --show-origin --get user.signingkey
git config --show-origin --get commit.gpgsign
command -v gpg
```

检查设置来自预期的全局或仓库配置文件，指纹与本机私钥匹配；不要依赖旧教程里带具体 Cellar 版本号的路径。

## Git 使用 {#use-gpg-with-git}

### 提交 {#sign-commit}

先按正常流程暂存并审阅需要提交的改动，再创建签名提交：

```bash
git diff --cached
git commit -S -m "Describe the change"
git verify-commit HEAD
```

若已开启 `commit.gpgsign`，可以省略 `-S`。本地验证成功表示签名可被当前密钥环验证；GitHub 的 `Verified` 还取决于公钥是否添加到账户、邮箱匹配和平台规则，不能由一次 `push` 保证。

### 提交 tag 时签名 {#sign-tag}

以下标签名应替换为项目实际要发布且尚不存在的名称：

```bash
git tag -s v1.0.0 -m "Release v1.0.0"
git verify-tag v1.0.0
```

### 查看日志 {#view-signed-commit-log}

```bash
git log --show-signature -1
```

签名有效性与本地密钥信任级别是不同概念；出现身份信任提示时，应核对指纹及其来源，不能仅凭姓名判断。

## 常见问题 {#common-issues}

出现 `gpg failed to sign the data` 时，先检查私钥是否存在、是否过期，以及 Git 选择的格式、密钥和 GPG 程序是否正确。需要口令但无法弹出输入界面时，再检查终端与 pinentry。

## 解决办法 {#solutions}

交互式终端可加载当前 TTY：

```bash
export GPG_TTY=$(tty)
```

需要持久设置时，将上面这一行按原样加入自己的 shell 配置文件，例如 `~/.zshrc` 或 `~/.bashrc`。不要在写入文件时先展开 `$(tty)`，否则会把某次会话的终端路径写死。图形界面 Git 客户端和无交互 CI 还需要各自适用的口令输入方案。

### 如果没有解决，继续排查 {#additional-troubleshooting-command}

```bash
gpgconf --check-programs
gpgconf --kill gpg-agent
```

可在私有测试目录创建一份无敏感内容的测试文件，再执行：

```bash
printf 'GPG signing check\n' > gpg-sign-test.txt
gpg --armor --local-user "$GPG_KEY_FINGERPRINT" --detach-sign gpg-sign-test.txt
gpg --verify gpg-sign-test.txt.asc gpg-sign-test.txt
```

这能把 GPG 本身的问题与 Git 配置问题分开；输出文件已存在时先处理旧文件，避免误覆盖或重复确认。

参考：[GitHub 配置签名密钥](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key)、[GitHub 签名验证](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification)、[GnuPG gpgconf](https://gnupg.org/documentation/manuals/gnupg/Invoking-gpgconf.html)。
