GPG(GNU Privacy Guard)是 OpenPGP 标准的开源实现,广泛用于加密文件、签名代码提交、保护邮件通信等场景。本文整理了日常使用 GPG 的最佳实践,帮助你在安全与便利之间找到平衡点。
一、密钥生成
选择合适的算法与长度
gpg --full-generate-key
推荐选择:
- RSA 4096 —— 兼容性最好,适合大多数场景
- Ed25519 —— 现代椭圆曲线算法,密钥短、速度快、安全强度高(推荐)
避免使用 DSA 1024 或 RSA 1024,这些算法已被认为不安全。
设置合理的过期时间
为主密钥设置 1~2 年的有效期,到期前通过续期(extend)而非重新生成的方式延续使用。有效期可以有效降低密钥泄漏后的持续伤害。
# 修改密钥有效期
gpg --edit-key <KEY_ID>
gpg> expire
填写正确的 UID
UID 格式为 Name <email@example.com>,建议:
- 使用常用真实姓名(或昵称)
- 邮箱与实际使用的一致
- 如有多个邮箱,可以添加多个 UID
二、主密钥与子密钥分离
这是 GPG 安全使用中最重要的实践之一。
为什么要分离?
| 角色 | 用途 | 存放位置 |
|---|---|---|
| 主密钥(Primary Key) | 证明身份、签署子密钥 | 离线冷存储 |
| 签名子密钥(Sign Subkey) | 日常签署提交/文档 | 工作机或智能卡 |
| 加密子密钥(Encrypt Subkey) | 接收加密邮件/文件 | 工作机或智能卡 |
| 认证子密钥(Auth Subkey) | SSH 认证(可选) | 智能卡 |
主密钥一旦泄漏,整个信任链就会崩溃。将主密钥保存在离线环境中,日常使用仅依赖子密钥,可以极大降低风险。
生成子密钥
gpg --edit-key <KEY_ID>
gpg> addkey
# 选择类型(签名/加密/认证)和有效期
gpg> save
导出并删除主密钥(仅保留子密钥在工作机)
# 备份完整密钥(含主密钥)到安全介质
gpg --export-secret-keys --armor <KEY_ID> > full-secret-key-backup.asc
# 仅导出子密钥到工作机使用
gpg --export-secret-subkeys --armor <KEY_ID> > subkeys.asc
# 删除工作机上的私钥
gpg --delete-secret-key <KEY_ID>
# 重新导入仅含子密钥的版本
gpg --import subkeys.asc
执行后用 gpg --list-secret-keys 检查,主密钥行显示 sec# 表示主密钥已从工作机移除(# 表示存根)。
三、密钥备份与吊销证书
备份密钥
至少保存到两个独立的离线介质(U 盘、纸张打印等),存放于不同地点。
# 导出公钥
gpg --export --armor <KEY_ID> > public-key.asc
# 导出私钥(主密钥 + 子密钥,妥善保管)
gpg --export-secret-keys --armor <KEY_ID> > secret-key.asc
提前生成吊销证书
在密钥生成后立刻生成吊销证书,防止密钥泄漏后无法吊销。
gpg --output revoke-<KEY_ID>.asc --gen-revoke <KEY_ID>
将吊销证书与私钥一起妥善保存(但存放位置要分开)。一旦密钥泄漏,立即使用吊销证书:
gpg --import revoke-<KEY_ID>.asc
gpg --keyserver hkps://keys.openpgp.org --send-keys <KEY_ID>
四、密钥发布与密钥服务器
上传公钥
# 上传到主流密钥服务器
gpg --keyserver hkps://keys.openpgp.org --send-keys <KEY_ID>
# 也可上传到 Ubuntu 密钥服务器
gpg --keyserver hkps://keyserver.ubuntu.com --send-keys <KEY_ID>
推荐使用 keys.openpgp.org,它支持邮件验证,防止垃圾密钥污染。
导出指纹供他人验证
gpg --fingerprint <KEY_ID>
在个人主页、GitHub Profile 或名片上公布指纹,便于他人验证你的公钥真实性。
五、与 Git 集成签名提交
配置 Git 使用 GPG 签名
# 开启 commit 签名
git config --global commit.gpgsign true
# 指定使用的密钥(如有多个密钥)
git config --global user.signingkey <KEY_ID>
强制指定子密钥(重要)
如果希望 Git 使用特定子密钥而非主密钥,需要在 Key ID 后面加上叹号 !:
git config --global user.signingkey <SUBKEY_ID>!
不加叹号时,GPG 会自动选择”合适”的密钥,可能并非你预期的那一个。
详细说明可参考:Git Signoff 指定 Subkey 的配置方法
跳过 GPG 签名的临时办法
在 CI/CD 等自动化环境中,如果没有 GPG Agent 可用,可临时跳过签名:
git commit --no-gpg-sign -m "提交消息"
六、智能卡 / 硬件密钥
将 GPG 私钥存储在硬件设备(如 YubiKey、CanoKey)中,可以防止私钥被直接导出,是目前最安全的使用方式。
将子密钥移入智能卡
gpg --edit-key <KEY_ID>
gpg> key 1 # 选中第一个子密钥
gpg> keytocard
# 按照提示选择存储槽(签名/加密/认证)
gpg> save
移入后子密钥本体从本地删除,只保留存根(stubs),使用时需要插入智能卡。
低成本替代方案
如果不想购买商业智能卡,可以使用 JavaCard + SmartPGP 方案自制 OpenPGP 卡,详见:基于SmartPGP低成本DIY OpenPGP卡
七、GPG Agent 配置
GPG Agent 负责缓存密码短语(passphrase),合理配置可以在安全与便利之间取得平衡。
配置缓存时间
编辑 ~/.gnupg/gpg-agent.conf:
# 密码短语缓存时间(秒)
default-cache-ttl 3600
max-cache-ttl 86400
重启 Agent
gpgconf --kill gpg-agent
gpgconf --launch gpg-agent
使用 SSH 认证
将 GPG 认证子密钥用于 SSH 登录:
# 在 gpg-agent.conf 中启用 SSH 支持
echo "enable-ssh-support" >> ~/.gnupg/gpg-agent.conf
# 将 GPG_AUTH_KEY_GRIP 添加到 sshcontrol
gpg --with-keygrip --list-secret-keys
# 将对应的 keygrip 写入 sshcontrol
echo "<KEYGRIP>" >> ~/.gnupg/sshcontrol
在 shell 配置文件中设置 SSH_AUTH_SOCK:
export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)
八、日常安全注意事项
设置强密码短语:私钥必须加密保存,密码短语建议 20 字符以上,混合大小写、数字、符号。
定期检查密钥状态:
gpg --list-keys gpg --list-secret-keys定期从密钥服务器刷新:获取他人密钥的吊销/更新状态:
gpg --refresh-keys不要在不可信环境中使用私钥:公用电脑、共享服务器上尽量避免导入私钥。
验证他人公钥的指纹:不要盲目信任从网络下载的公钥,通过带外方式(当面、电话、个人主页)核对指纹。
子密钥即将过期时提前续期:避免密钥突然过期导致验证失败。
九、常用命令速查
# 生成密钥
gpg --full-generate-key
# 列出密钥
gpg --list-keys # 公钥
gpg --list-secret-keys --keyid-format LONG # 私钥(含长格式 Key ID)
# 导入/导出
gpg --import key.asc
gpg --export --armor <KEY_ID> > pub.asc
gpg --export-secret-keys --armor <KEY_ID> > sec.asc
# 加密与解密
gpg --encrypt --recipient <KEY_ID> file.txt
gpg --decrypt file.txt.gpg
# 签名与验证
gpg --sign file.txt # 生成二进制签名文件
gpg --clearsign file.txt # 生成可读的签名文本
gpg --detach-sign file.txt # 生成分离签名
gpg --verify file.txt.sig file.txt
# 编辑密钥(添加子密钥、修改有效期等)
gpg --edit-key <KEY_ID>
# 吊销并上传
gpg --import revoke.asc
gpg --keyserver hkps://keys.openpgp.org --send-keys <KEY_ID>
# 重启 GPG Agent
gpgconf --kill gpg-agent
–
十、密钥过期处理
密钥过期后并不等于密钥作废,处理方式取决于你想继续使用还是彻底更换。
过期后的影响
| 操作 | 过期后的行为 |
|---|---|
| 用过期密钥加密 | GPG 会警告并拒绝(需强制指定才能加密) |
| 用过期密钥签名 | GPG 会报错拒绝 |
| 验证过期密钥签名的历史签名 | 仍然有效,历史签名不受影响 |
| 解密用过期加密密钥加密的数据 | 仍然可以解密,过期只限制新操作 |
关键认知:过期的含义是”不再建议使用”,而非”历史数据无效”。历史签名和加密文件依然可以正常使用。
方案一:续期(推荐)
续期是最常见的处理方式,即延长现有密钥的有效期,无需更换密钥。
前提:需要取出离线保存的主密钥。
# 1. 导入主密钥(从离线备份)
gpg --import full-secret-key-backup.asc
# 2. 编辑密钥,修改有效期
gpg --edit-key <KEY_ID>
# 续期主密钥
gpg> expire
Key is valid for? (0) # 输入新的有效期,如 1y 表示1年
# 续期子密钥(需逐个选中)
gpg> key 1 # 选中第一个子密钥
gpg> expire
gpg> key 1 # 取消选中
gpg> key 2 # 选中第二个子密钥
gpg> expire
gpg> save
# 3. 将续期后的公钥上传到密钥服务器
gpg --keyserver hkps://keys.openpgp.org --send-keys <KEY_ID>
# 4. 重新导出公钥分发给合作方(如果不使用密钥服务器)
gpg --export --armor <KEY_ID> > public-key-renewed.asc
# 5. 完成后,再次从工作机删除主密钥(保持离线原则)
gpg --delete-secret-key <KEY_ID>
gpg --import subkeys.asc # 重新导入仅含子密钥的版本
续期后,他人只需刷新你的公钥即可看到新有效期:
# 他人刷新密钥
gpg --refresh-keys
# 或指定服务器
gpg --keyserver hkps://keys.openpgp.org --recv-keys <KEY_ID>
方案二:添加新子密钥替换旧子密钥
如果仅子密钥过期,可以保留旧子密钥(用于解密历史数据),再添加新子密钥投入使用:
gpg --edit-key <KEY_ID>
gpg> addkey # 添加新的签名子密钥或加密子密钥
gpg> save
旧的加密子密钥过期后仍保留在密钥环中,GPG 依然可以用它解密之前接收的文件,只是不再接受用它加密新数据。
方案三:生成全新密钥(不推荐轻易使用)
仅在以下情况考虑重新生成:
- 主密钥私钥已丢失,无法续期
- 密钥算法过时,需要更换算法(如从 RSA 迁移到 Ed25519)
- 密钥已泄漏,需要完全作废
生成新密钥后需要:
- 用旧密钥的吊销证书吊销旧密钥,并上传到密钥服务器
- 将新公钥分发给所有合作方并重新建立信任
子密钥在智能卡上的续期
如果子密钥已移入智能卡,续期流程略有不同:
# 1. 导入主密钥(智能卡只存储子密钥,主密钥仍需从备份导入)
gpg --import full-secret-key-backup.asc
# 2. 正常执行 expire 续期
gpg --edit-key <KEY_ID>
gpg> key 1
gpg> expire
gpg> save
# 3. 智能卡上的子密钥有效期已通过主密钥续期,无需重新写卡
# (智能卡存储的是私钥本体,有效期元数据在公钥中)
续期前的检查清单
- 确认你还持有主密钥备份
- 确认吊销证书已安全保存
- 续期后重新导出并分发最新公钥
- 续期完成后重新将主密钥从工作机删除
- 更新 GitHub/GitLab 等平台上的 GPG 公钥(如有必要)
总结
GPG 的安全性高度依赖于正确的使用方式。核心原则是:
- 主密钥离线保存,日常只使用子密钥
- 提前备份私钥和吊销证书,并存放在不同介质
- 有效期不要设置太长,定期续期比一次性设置永久更安全
- 使用智能卡进一步保护私钥不被导出
遵循这些实践,GPG 能够成为你数字身份和数据安全的可靠基础。