暗色模式

GPG 加密与签名:从密钥生成到文件加密与验签

技术教程
2026-09-02
7
0
本文要点
  • GPG(GnuPG)是 OpenPGP 标准的开源实现:公钥加密 + 数字签名一体,密钥以「密钥环」形式长期管理,与 openssl 面向证书/SSL 的定位不同
  • gpg --quick-gen-key 一键生成密钥对:ed25519 主密钥([SC] 签名+证书)自动搭配 cv25519 子密钥([E] 加密),生成的吊销证书必须备份
  • 无图形界面的服务器上解密必须加 --pinentry-mode loopback --passphrase '密码',否则报 Inappropriate ioctl for device——这是 headless 环境最常见的 GPG 报错
  • --encrypt --armor -r 对方邮箱 加密(输出纯文本格式,可直接复制传输),--decrypt 还原
  • --detach-sign 生成独立签名文件,gpg --verify 验签;文件被篡改后验签会报 BAD signature,数据完整性一目了然
  • 公钥通过 --export --armor 导出、对方 --import 导入;未核验信任的密钥加密时会报 There is no assurance this key belongs to the named user,需要 --always-trust 或先核验指纹
  • 全部命令在 Ubuntu 24.04(gpg 2.4.4)上真实执行验证,截图即真实输出

GPG 是什么:把「身份」和「加密」绑在一起的工具箱

上一篇文章《openssl 命令行》里我们用它生成证书、做对称加密。但 openssl 更适合「临场使用」:证书、握手、一次性加密。当你要长期、反复地和同一批人加密传输文件、证明「这个文件确实是我发的、没被改过」时,更顺手的工具是 GPG(GnuPG,GNU Privacy Guard)——OpenPGP 标准的开源实现。

GPG 的核心思路和 openssl 完全不同:

  • 一套长期密钥。GPG 维护一个「密钥环」(keyring),你的密钥对生成一次、用很多年,不需要每次重新生成。
  • 身份绑定。每个密钥对绑定一个 UID(姓名 + 邮箱),「加密给 demo@example.com」=「加密给那个人的公钥」,语义清晰。
  • 加密与签名一体。公钥加密文件、私钥签名文件,验签方只需拿到你的公钥即可,不需要你的私钥。
  • 密钥分发有一套信任体系。别人拿到你的公钥后要「核验并信任」,才能放心用你的公钥加密——这就是下文会遇到的"信任级别"问题的由来。

下面所有命令都在 Ubuntu 24.04(gpg 2.4.4)上实测执行。先看当前环境:

gpg --version | head -2

生成密钥对:一分钟搞定

交互式生成用 gpg --full-generate-key,会一步步问你算法、密钥长度、有效期、密码。但服务器上经常要无人值守,--quick-gen-key 配合三个参数就能全自动:

gpg --batch --pinentry-mode loopback --passphrase 'DemoPass123' --quick-gen-key 'Demo User <demo@example.com>'

生成 GPG 密钥对

首次运行会初始化密钥环目录(~/.gnupg/ 下的 pubring.kbxtrustdb.gpg),然后生成密钥。输出里最后一行值得特别留意:

gpg: revocation certificate stored as '/root/.gnupg/openpgp-revocs.d/DDEA2CDD134FB682BD421B49E59C130AC0166863.rev'

这行说的是:吊销证书(revocation certificate)已生成。如果私钥丢了/被偷了,靠这份 .rev 文件才能公告「这个密钥作废」,防止别人拿你的身份继续加密/签名。务必把这份吊销证书备份到安全的地方,密钥丢了它是唯一的后悔药。

查看密钥:指纹才是身份的凭据

gpg --list-keys 查看密钥环里已有的公钥:

gpg --list-keys --keyid-format long

查看密钥与完整指纹

输出结构拆开看:

  • pub ed25519/E59C130AC0166863 2026-09-01 [SC] [expires: 2029-08-31]:主密钥,ed25519 算法,用途标记 [SC] = Sign(签名)+ Certify(管理证书/信任)。有效期默认 3 年。
  • DDEA2CDD134FB682BD421B49E59C130AC0166863完整指纹,40 位十六进制。这才是公钥真正的身份标识——跟别人核验密钥时对照的是这串指纹,不是邮箱(邮箱可以伪造)。
  • uid [ultimate] Demo User <demo@example.com>:绑定的身份,[ultimate] 表示这是你自己的密钥(终极信任)。
  • sub cv25519/C90103441ED7DFD7 2026-09-01 [E]:子密钥,cv25519 算法,用途 [E] = Encrypt(加密)。GPG 默认把「加密」和「签名」职责拆开:签名用主密钥,加密用子密钥,这样日常加密泄露也不会连累签名身份。

输出的前四行 gpg: checking the trustdb 是 GPG 在检查信任数据库(首次使用会初始化),可以忽略。

加密与解密文件

加密用对方的邮箱(GPG 自动在密钥环里找到对应公钥),--armor 把二进制密文转成纯文本格式——直接贴在聊天软件里发也没问题:

echo 'GPG 加密测试内容:你好,Linux!' > secret.txt
gpg --encrypt --armor --recipient demo@example.com secret.txt

加密命令本身是静默成功的,对比一下加密前后的文件:43 字节的明文变成 366 字节的 ASCII 密文(secret.txt.asc)。--recipient 可简写为 -r

解密时注意一个坑。直接 gpg --decrypt secret.txt.asc 会失败:

gpg: encrypted with cv25519 key, ID C90103441ED7DFD7, created 2026-09-01
      "Demo User <demo@example.com>"
gpg: public key decryption failed: Inappropriate ioctl for device
gpg: decryption failed: Inappropriate ioctl for device

Inappropriate ioctl for device 的意思是:GPG 想弹出图形密码框(pinentry),但服务器上没有图形界面,也拿不到 tty。解法是告诉它用 loopback 模式、直接把密码写在命令行:

gpg --decrypt --pinentry-mode loopback --passphrase 'DemoPass123' secret.txt.asc

完整的「加密 → 解密」演示:

echo 'GPG 加密测试内容:你好,Linux!' > secret.txt && gpg --encrypt --armor --recipient demo@example.com secret.txt && gpg --decrypt --pinentry-mode loopback --passphrase 'DemoPass123' secret.txt.asc

加密与解密完整流程

输出里的 encrypted with cv25519 key, ID C90103441ED7DFD7 就是加密时用的那把子密钥 ID,明文原样解出。提醒一句:--passphrase 直接出现在命令行会把密码泄露给 ps 进程列表,生产脚本建议从文件读或配好 pinentry;本文为演示简洁直接写入。

签名与验签:证明文件「没被改过」

加密保护的是机密性(别人读不到),签名保护的是完整性(内容没被改)和真实性(确实是你的)。--detach-sign 生成一个独立的签名文件(不修改原文件):

gpg --detach-sign --pinentry-mode loopback --passphrase 'DemoPass123' secret.txt
gpg --verify secret.txt.sig secret.txt

验签输出是 Good signature from "Demo User <demo@example.com>" [ultimate],表示签名有效。现在模拟「文件在传输中被篡改」——往文件里追加一行:

echo '恶意篡改内容' >> secret.txt
gpg --verify secret.txt.sig secret.txt

输出立刻变成 BAD signature from "Demo User <demo@example.com>" [ultimate],并且验签过程还会打印签名时间和使用的密钥指纹。生产环境里,下载软件镜像后验签、审计日志完整性校验,用的都是这套机制。

公钥分发:导出给别人的正确姿势

要给别人加密发文件,对方得有你的公钥。导出成纯文本格式:

gpg --export --armor --output demo-pubkey.asc demo@example.com
cat demo-pubkey.asc

输出是 -----BEGIN PGP PUBLIC KEY BLOCK----- 开头的一坨 base64 文本,把它发对方即可。对方在自己的机器上导入(这里用 --homedir 模拟另一台机器 Bob 的独立密钥环):

mkdir -p /tmp/gpg-bob && chmod 700 /tmp/gpg-bob
gpg --homedir /tmp/gpg-bob --import demo-pubkey.asc
gpg --homedir /tmp/gpg-bob --list-keys

导入后 Bob 的密钥环里就有了这份公钥。注意 --list-keys 输出里 uid 的信任标记是 [ unknown]——这是信任级别:GPG 知道这把公钥存在,但「这个公钥真的属于 demo@example.com 吗?」未经核验。此时 Bob 直接用这把公钥加密会失败:

gpg: B899471593761DAD: There is no assurance this key belongs to the named user
gpg: cannot open '/dev/tty': No such device or address

There is no assurance... 是 GPG 的安全设计:不想让你把密文发给「可能不是本人」的密钥。两个解决路径:

  1. 正式做法:通过电话/面聊核对 40 位指纹,然后 gpg --sign-key demo@example.com 声明「我核验过这把密钥」——PGP 信任网就是这么建立的。
  2. 快捷做法(脚本/演示场景):加密时加 --always-trust,跳过信任检查:
gpg --homedir /tmp/gpg-bob --encrypt --armor --always-trust --recipient demo@example.com bob-msg.txt
gpg --decrypt --pinentry-mode loopback --passphrase 'DemoPass123' bob-msg.txt.asc

这样 Bob 用导入的公钥加密的密文,只有持有对应私钥的你才能解开——加密和解密的完整链路就通了。

小结

GPG 的工作流可以总结成四步:

  1. 生成gpg --batch --pinentry-mode loopback --passphrase '密码' --quick-gen-key '姓名 <邮箱>',备份吊销证书。
  2. 发布gpg --export --armor -o 文件.asc 邮箱 把公钥发给对方,对方 gpg --import 导入。
  3. 加密gpg --encrypt --armor -r 邮箱 文件;解密加 --pinentry-mode loopback --passphrase '密码'
  4. 签名/验签gpg --detach-sign 文件 + gpg --verify 文件.sig 文件,篡改会报 BAD signature。

和《openssl 命令行》对照着记:openssl 适合"一次性、证书场景"(HTTPS 证书、临时对称加密),gpg 适合"长期身份、日常文件加密与签名"(邮件加密、软件包签名、Git 提交签名)。两者都用公钥密码学,但工作模式完全不同。密钥的权限管理和文件权限体系也有交集,可参考《Linux 文件权限》给 ~/.gnupg 目录补上 700 权限。

参考资料

发表评论

暂无评论,快来抢沙发吧!