3432 字
17 分钟
GPG 安全密钥管理最佳实践

在长期使用 GPG 的场景中,直接使用主密钥进行签名或加密是强烈不推荐的,主密钥一旦参与日常使用,其私钥暴露风险将显著增加。而一旦主密钥私钥泄露整个身份验证的信任链将会崩塌,密钥持有人只能选择吊销主密钥并发布吊销声明,随后重新生成全新的密钥对。

这不仅意味着历史签名的信任基础失效,还需要重新分发公钥、重新建立他人对新密钥的信任关系,过程漫长且成本极高。因此,将主密钥严格限定为身份认证与子密钥管理用途,并通过子密钥承担日常签名与加密职责,是保障 GPG 身份长期稳定与安全的关键设计原则。

TIP

参考:
https://gnupg.org/documentation/manuals/gnupg26/gpg.1.html
https://gnupg.org/documentation/manuals/gnupg-2.0/OpenPGP-Key-Management.html

一、密钥结构设计(推荐)#

类型用途是否日常使用
主密钥(Ed25519)身份 + 子密钥管理否(离线)
签名子密钥(Ed25519)文件 / Git / 发布签名
加密子密钥(Curve25519 / cv25519)文件 / 邮件加密
认证子密钥(可选)SSH 登录可选

二、生成主密钥(仅用于认证)#

Terminal window
gpg --full-generate-key --expert

交互选择建议#

  1. 密钥类型 11 → ECC(自定义)

默认输出内容可能如下:

Possible actions for this ECC key: Sign Certify Authenticate
Current allowed actions: Sign Certify
(S) Toggle the sign capability
(A) Toggle the authenticate capability
(Q) Finished
Your selection?

当前开启的能力是签名和认证,输入 S 取消签名能力,输入 Q 完成自定义
GPG 要求主密钥至少具备 Certify 能力,因此最终主密钥能力为 [C],这是 OpenPGP 的设计约束,无法生成完全“无能力”的主密钥

  1. 椭圆曲线 1 → Ed25519(Curve25519 曲线族,用于签发/认证)
    在 gpg 的交互界面中,Curve 25519 同时用于 Ed25519(签名/认证)与 cv25519(加密),具体用途由所选密钥能力决定

  2. 有效期

    • 建议:5y10y
    • 主密钥不建议永久有效
  3. 填写用户名与邮箱

  4. 设置高强度密码

生成完成后查看:

Terminal window
gpg --list-secret-keys --keyid-format=LONG

示例输出结构:

sec ed25519/AAAAAAAAAAAAAAAA 2025-01-01 [C]
uid 用户名 <email@example.com>
CAUTION

此时只有主密钥没有子密钥,主密钥仅具备身份认证(Certify)与子密钥管理能力,不要直接使用它进行日常操作

三、添加子密钥(核心步骤)#

3.1 进入密钥编辑模式#

Terminal window
gpg --edit-key AAAAAAAAAAAAAAAA

3.2 添加签名子密钥#

gpg> addkey

选择:

  • 类型:10 → ECC(仅用于签名)
  • 曲线:1 → Ed25519
  • 有效期:建议 1y2y

完成后会看到:

ssb ed25519/BBBBBBBBBBBBBBBB [S]

3.3 添加加密子密钥#

gpg> addkey

选择:

  • 类型:12 → ECC(加密)
  • 曲线:1 → Curve25519(cv25519)
  • 有效期:建议 1y2y

完成后会看到:

ssb cv25519/CCCCCCCCCCCCCCCC [E]

3.4 保存退出#

gpg> save

四、密钥隔离与备份(强烈建议)#

在执行主密钥隔离前,确保你已经创建了用于日常使用的子密钥(签名/加密等),并完成必要导出与备份

4.1 导出公钥(对外发布/同步)#

Terminal window
gpg --armor --export AAAAAAAAAAAAAAAA > public.key

public.key 用于发布到团队成员、公钥服务器或同步到其他设备。

4.2 导出子密钥私钥(仅用于日常设备)#

Terminal window
gpg --armor --export-secret-subkeys AAAAAAAAAAAAAAAA > subkeys.key

此命令会导出所有子密钥私钥,但主密钥私钥会被替换为占位符(stub),因此导入此子密钥只能用于在日常设备使用签名/加密能力,无法执行 revkey、adduid 等需要主密钥的操作。

4.3 备份主密钥私钥 + 所有子密钥私钥(仅一次,离线保存)#

Terminal window
gpg --armor --export-secret-keys AAAAAAAAAAAAAAAA > master-full.key

master-full.key 进行离线备份(建议多份):

  • 存入加密 U 盘
  • 离线保存(例如保险箱/异地)
  • 如需纸质备份,可转换为 QR 打印保存
  • 不再导入到日常设备

在完成 master-full.key 备份后,建议在隔离环境中(如临时虚拟机)执行以下操作,验证备份完整性:

Terminal window
gpg --import master-full.key
echo "test" | gpg --encrypt --recipient email@example.com > test.gpg
gpg --decrypt test.gpg # 应能成功解密

4.4 在日常设备删除主密钥私钥,仅保留子密钥#

Terminal window
gpg --delete-secret-key AAAAAAAAAAAAAAAA
TIP

删除后,你的日常设备将只保留子密钥能力(用于签名/加密),主密钥私钥不再在线暴露
即使日常设备被入侵,攻击者也难以获得主密钥私钥,从而无法伪造你的长期身份
如需在新设备启用子密钥能力,导入子密钥私钥即可:

Terminal window
gpg --import subkeys.key
CAUTION

加密子密钥私钥一旦丢失将无法恢复

这里的丢失通常发生在密钥迁移、重装系统、清理密钥环或误删密钥时:例如仅备份公钥、备份主密钥私钥后生成子密钥但未备份、仅导出子密钥用于日常使用后却未保留完整离线备份,或在子密钥私钥已从本地密钥环中删除后才执行导出操作。上述操作均会导致备份中不包含加密子密钥私钥,进而无法解密历史数据。

在 OpenPGP / GnuPG 的密钥体系中,子密钥私钥是独立存在的密钥材料,并非由主密钥派生生成。主密钥仅用于身份认证及子密钥的签发与吊销,不具备恢复或替代子密钥私钥的能力

因此,是否能够恢复历史加密数据,不取决于是否单独保留了主密钥,而完全取决于备份中是否包含用于加密的子密钥私钥;一旦加密子密钥私钥在备份或迁移过程中丢失或未被备份,即使主密钥私钥仍然完整存在,所有使用该子密钥加密的数据也将永久无法解密。

在执行主密钥隔离、删除主密钥私钥或轮换子密钥之前,必须确认加密子密钥私钥已被完整备份,并至少保留一份包含主密钥及全部子密钥私钥的离线备份;建议在新设备或新环境中验证该备份能成功解密历史数据后,再进行密钥删除或吊销操作

五、加密与解密(使用加密子密钥)#

在采用主密钥隔离的实践中,加密与解密操作由加密子密钥完成,主密钥不参与具体的数据加解密过程

NOTE

关于 GPG 的密钥选择规则说明(加密)

  • 发送方在执行加密操作时,如果接收方的公钥中仅存在一个可用于加密的子密钥,GPG 会自动使用该子密钥进行加密
  • 发送方在执行加密操作时,如果接收方的公钥中存在多个可用于加密的子密钥,GPG 会根据密钥有效性、未吊销状态及算法匹配情况自动选择合适的加密子密钥(通常优先选择仍在有效期内的子密钥)

5.1 加密文件#

使用接收方邮箱为 receiver@example.com的公钥对文件进行加密:

Terminal window
gpg --encrypt --recipient receiver@example.com file.txt

也可以使用接收方的公钥 ID 指定加密目标:

Terminal window
gpg --encrypt --recipient DDDDDDDDDDDDDDDD file.txt

加密完成后生成文件:

file.txt.gpg

5.2 解密文件#

解密他人使用你的公钥加密的文件:

Terminal window
gpg --decrypt file.txt.gpg > file.txt
TIP

GPG 会自动在本地密钥环中匹配对应的加密子密钥私钥
按提示输入密码,解密成功后,文件内容恢复为原始数据

六、数字签名(使用签名子密钥)#

在采用主密钥隔离的最佳实践方案下,签名和验签操作由签名子密钥完成,主密钥不参与具体的签名与验签操作

NOTE

关于 GPG 的密钥选择规则说明(签名)

  • 发送方在执行签名操作时,如果本地密钥环中仅存在一个可用于签名的子密钥,GPG 会自动使用该子密钥进行签名
  • 发送方在执行签名操作时,如果本地密钥环中存在多个可用于签名的子密钥,GPG 会根据密钥有效性、未吊销状态及用途标记等条件自动选择合适的签名子密钥(通常优先选择仍在有效期内的子密钥)
  • 如果对 发送方(本地) 使用的签名密钥存在特殊要求,可在 ~/.gnupg/gpg.conf 中通过 default-key AAAAAAAAAAAAAAAA 指定签名时默认使用的私钥(仅影响签名,不影响加密),若采用主密钥隔离方法,推荐显式指定具有签名能力的 [S] 子密钥 ID

6.1 签名文件#

Terminal window
# 生成一个包含原文件内容的二进制签名文件,内容不可直接阅读(file.txt.gpg)
gpg --sign file.txt
# 生成一个独立的签名文件(file.txt.sig),不修改原文件
gpg --detach-sign file.txt
# 生成一个可读文本文件,原文内容保持明文,并内嵌 ASCII 签名(file.txt.asc)
gpg --clearsign file.txt

6.2 验证签名#

Terminal window
# 验证二进制签名(不输出原始内容)
gpg --verify file.txt.gpg
# 解包签名文件获取源文件
gpg --decrypt file.txt.gpg > file.txt
# 验证分离签名
gpg --verify file.txt.sig file.txt
# 验证 clearsign 文件
gpg --verify file.txt.asc
NOTE

对于 gpg --sign 生成的签名封装文件,gpg --verify 仅用于校验签名,不会输出原始内容
如需恢复原文件,应使用 gpg --decrypt 进行解包

TIP

在实际使用中,签名与加密可以组合使用,例如在向他人发送文件时同时保证机密性与来源可信性:

Terminal window
gpg --sign --encrypt --recipient receiver@example.com file.txt

使用 gpg --decrypt 进行解包时,gpg 会自动尝试验证内嵌的签名,并在输出中提示签名是否有效

七、子密钥吊销与轮换(核心优势)#

NOTE
  • 该操作需要主密钥私钥仍然存在并可用
  • 子密钥吊销一旦执行即不可撤销
  • 子密钥吊销不会影响主密钥,身份信任关系仍然有效,可通过重新生成子密钥完成轮换
  • 完成操作后建议重新导出并发布公钥,以确保其他用户可以及时获取子密钥的吊销信息

7.1 进入密钥编辑模式#

Terminal window
gpg --edit-key AAAAAAAAAAAAAAAA

7.2 选择子密钥并吊销#

选择子密钥编号,编号按生成顺序依次编号,输入 revkey 进行吊销

gpg> key 1
gpg> revkey

7.3 保存退出#

gpg> save

7.4 生成新子密钥#

生成方式与 **三、添加子密钥(核心步骤)**章节内容一致

八、修改用户信息(姓名 / 邮箱)#

NOTE
  • 该操作涉及 UID 的新增或吊销,需要主密钥私钥仍然存在并可用
  • 完成操作后建议重新导出并发布公钥,以确保其他用户可以及时获取新的用户信息

gpg密钥的指纹一旦生成后不可更改,但针对用户信息可以通过新增 UID 并吊销或删除旧 UID 的方式实现用户信息的更新

8.1 进入密钥编辑模式#

Terminal window
gpg --edit-key AAAAAAAAAAAAAAAA

8.2 添加新 UID#

按照提示输入姓名、邮箱等信息

gpg> adduid

8.3 吊销旧 UID#

选择 UID 编号,编号按生成顺序依次编号,输入 revuid 进行吊销,向公钥中写入 此 UID 已失效 的声明

gpg> uid 1
gpg> revuid

8.4 删除旧 UID#

选择 UID 编号,编号按生成顺序依次编号,输入 deluid 进行删除,仅在本地密钥中删除该 UID
该方式本身不构成对外可验证的身份吊销;如果该 UID 曾被分发或使用,应优先使用 revuid 吊销,再视需要使用 deluid 进行本地清理,否则该身份在对外发布的公钥中永远不会包含任何可验证的失效声明

gpg> uid 1
gpg> deluid

8.5 保存退出#

gpg> save

九、修改密钥密码(主密钥或子密钥)#

NOTE
  • 该操作仅涉及本地私钥的保护密码修改,不依赖主密钥的认证能力
  • 在仅保留子密钥私钥的环境中同样可以执行

9.1 进入密钥编辑模式#

Terminal window
gpg --edit-key AAAAAAAAAAAAAAAA

9.2 修改密码#

该操作会修改整个密钥对象的保护密码,主密钥私钥与其所有子密钥私钥统一使用同一个密码
修改密码不会更换或重新生成任何密钥材料,也不会影响已生成的签名或加密数据

gpg> passwd

9.3 保存退出#

gpg> save

十、生成吊销证书(必须)#

NOTE
  • 主密钥吊销证书必须在主密钥私钥仍然存在且受控时提前生成,并离线安全保存
  • 一旦主密钥丢失或主密钥私钥泄露,应立即发布事先生成的吊销证书,以对外声明该密钥已被永久停用
  • 吊销证书本身不包含私钥,但具有与主密钥等效的吊销效力,应妥善保护,避免误用
  • 吊销证书一旦发布无法撤回,且会立即使整个密钥(包括所有子密钥)失效。因此务必离线保存,切勿提前上传

执行时会提示选择吊销原因(如“密钥已泄露”、“不再使用”等),建议选择具体原因并填写说明,这有助于他人理解吊销情况

Terminal window
gpg --output revoke.asc --gen-revoke AAAAAAAAAAAAAAAA

十一、使用原则总结#

  • 主密钥: 离线保存,仅用于签发 / 吊销
  • 子密钥: 日常使用,优先迁移至硬件
  • stub key: 跨设备使用的唯一本地材料
  • 硬件密钥: 私钥不可导出,安全边界明确
GPG 安全密钥管理最佳实践
https://blog.33065432.xyz/posts/security-gpg-recommended-usage/
✍️作者
m4passion
📅发布于
2025-09-09
©️许可协议
CC BY-NC-SA 4.0

商业用途必须事先获得作者授权;
非商业用途可以使用,但必须注明出处,
并且若有改编需采用相同许可协议发布。