GPG 加密与签名实战:给备份和发布产物做完整性校验、免密自动化与密钥撤销

为什么你的备份和发布产物需要一个签名

个人站长每天和文件打交道:数据库导出、网站打包、证书私钥、配置文件备份,这些东西要么被传到对象存储,要么被同步到另一台机器。它们在路上安全吗?存下来之后别人能不能篡改?如果你从来没想过这个问题,那说明你的运维链路里少了一环——完整性校验。

很多人第一次意识到这个问题,是在某次恢复备份的时候。文件从对象存储下载回来,大小看着对,解压却报错,或者内容和你记忆中的完全不一样。你没法判断这是传输损坏、存储静默出错,还是文件在某个环节被人动过。没有签名,你就只能靠「祈祷」。

GPG(GNU Privacy Guard)解决的正是这件事。它是 OpenPGP 标准的开源实现,能同时做两件事:加密(内容只有指定接收方能解开)和签名(任何人都能验证内容有没有被改过、是不是出自你手)。对个人站长来说,签名比加密更常用——因为你要保护的核心是「可信」和「可验证」。

GPG 密钥是怎么工作的

在动手之前,花两分钟理解原理,后面的每一步都会变得顺理成章。GPG 用的是非对称加密:你手里有一对密钥,公钥和私钥。

  • 公钥可以随便给任何人。别人用它来给你加密(只有你能解开),或者用它来验证你的签名(确认是你签的)。
  • 私钥必须保密。只有它能解密别人发给你的内容,也只有它能生成你的签名。

签名的工作方式值得一提。GPG 并不是把整个文件加密一遍当签名,那样太慢。它的做法是先对文件算一个哈希(摘要),再用你的私钥对摘要签名。验证方拿到文件后自己算一遍哈希,再用你的公钥解开签名,两个摘要一致就说明文件没被改动。这也意味着签名可以「附在文件之外」——既有一体的 .asc 文件,也有分离式签名,后者对备份场景特别方便。

生成一对属于你的密钥

先确认 GPG 装好了。主流发行版基本都自带,没有的话装一下:

gpg --version
apt install -y gnupg    # 未安装时

生成密钥用交互式的 --full-generate-key,它会把每一步问清楚,比全参数命令行更不容易出错:

gpg --full-generate-key

会依次问你几个问题,逐条解释:

  • 密钥类型:选默认的 (1) RSA and RSA,或者更现代的 (9) ECC (sign and encrypt)。ECC 密钥更短、更快,推荐选了椭圆曲线的 Curve 25519。
  • 密钥长度:RSA 选 4096,ECC 无此项。4096 位的 RSA 至今仍是稳妥默认值。
  • 有效期:建议设一个,比如 2y(两年)。设有效期不是不信任,而是提醒自己定期轮换;过期后还能续期,不必重建。
  • 姓名与邮箱:填一个你能记住的标识。邮箱最好真实,这样别人导入公钥时能对上号。
  • 密码(Passphrase):给私钥本身再加一道锁。这个密码越强越好,因为一旦私钥文件泄露,没有它别人也用不了。

生成过程会调用系统的熵源,如果是在刚启动的 VPS 上,可能会提示「需要更多随机字节」。这时候随便敲敲键盘、动动鼠标就能帮着凑熵,或者装个 haveged 服务。完成后查看密钥列表:

gpg --list-keys
gpg --list-secret-keys --keyid-format LONG

输出里 sec 那行后面跟着的十六进制串就是密钥 ID,例如 rsa4096/1A2B3C4D5E6F7A8B,后面会经常用到它。

加密与解密:把敏感文件锁起来

最经典的场景是:你要把一份含数据库密码的配置文件备份到对象存储,不希望存明文。用你自己的公钥加密就行:

# 加密(--armor 产生可读的 ASCII 文本,便于粘贴和邮件发送)
gpg --encrypt --armor --recipient 你的邮箱@example.com config.tar.gz

# 生成的 config.tar.gz.asc 就是密文

# 解密(需要你的私钥和密码)
gpg --decrypt config.tar.gz.asc > config.tar.gz

如果只是临时备忘,也可以做对称加密,用一条口令就够了,不需要密钥对:

gpg --symmetric --cipher-algo AES256 secrets.txt
# 会提示输入两遍口令,生成 secrets.txt.gpg

对称加密的优点是简单、不需要任何密钥管理;缺点也很明显——口令本身必须通过另一条安全渠道传递,一旦口令泄露,密文就等于明文。所以对长期备份,还是推荐用非对称加密:公钥可以光明正大地写在脚本里,哪怕脚本泄露也拿不到私钥。

签名:让篡改无处遁形

签名是本站场景里最实用的功能。发布一个网站打包产物时,顺手签个名,别人(以及你自己以后)就能验证它没被动过手脚。用 --detach-sign 生成分离式签名:

gpg --detach-sign --armor site-backup-20261001.tar.gz
# 生成 site-backup-20261001.tar.gz.asc

验证时把原始文件和签名放在一起,让 GPG 自己去配:

gpg --verify site-backup-20261001.tar.gz.asc site-backup-20261001.tar.gz

输出里出现 Good signature 就说明文件完好且确实由对应私钥签发。如果被改过哪怕一个字节,就会变成 BAD signature。还有一种把签名和内容打包在一起的「明文签名」,适合签一份文本公告:

gpg --clearsign release-notes.txt
# 生成 release-notes.txt.asc,内容可读、签名附加在末尾

验证分离式签名时有一点要注意:--verify 的第一个参数是签名文件,第二个才是被签名的文件,顺序写反会报错。如果签名文件没有后缀提示、GPG 无法自动识别被签文件,就显式把两个参数都写上。

把签名接进备份流水线

签名本身不难,难的是「每天自动做,且能自动验」。最好的做法是把签名和验证都写进备份脚本,每次备份之后立刻签名,每次恢复之前先验证。下面是一段可以直接用的脚本骨架:

#!/bin/bash
set -euo pipefail

KEY_ID="1A2B3C4D5E6F7A8B"
STAMP=$(date +%Y%m%d-%H%M)
BACKUP="/backup/site-${STAMP}.tar.gz"

# 1. 打包
tar -czf "$BACKUP" -C /var/www/html .

# 2. 签名(--batch 让它不弹交互,密码由 agent 缓存提供)
gpg --batch --yes --local-user "$KEY_ID" \
    --detach-sign --armor "$BACKUP"

# 3. 自校验:签名生成后立刻验一遍,避免签出坏包
gpg --verify "${BACKUP}.asc" "$BACKUP"

# 4. 生成校验和,双保险
sha256sum "$BACKUP" > "${BACKUP}.sha256"

echo "OK: $BACKUP"
echo "OK: ${BACKUP}.asc"
echo "OK: ${BACKUP}.sha256"

恢复的时候,顺序要反过来:先 --verify 确认签名,再比对 sha256,两个都过了才解压。这一套下来,任何环节的损坏或篡改都会被拦在恢复之前,不会等到解压报错才发现。

让脚本免密码:gpg-agent 与密码缓存

自动化里最大的障碍是「签名要输密码,而 cron 里没人能输」。解决办法是 gpg-agent,它可以把私钥密码缓存在内存里一段时间。配置在 ~/.gnupg/gpg-agent.conf:

default-cache-ttl 28800
max-cache-ttl 86400

改完重启 agent 并预热缓存:

gpgconf --kill gpg-agent
gpgconf --launch gpg-agent
echo "test" | gpg --local-user "$KEY_ID" --clearsign > /dev/null   # 手动输一次密码

缓存期内,cron 脚本里的 gpg --detach-sign 就能免密执行。但这里有个务实的权衡:密码缓存在内存里,等于这段时间内本机被攻破就等于私钥被拿走。如果你对安全性要求更高,可以考虑用一台专门的签名机,或者接受「每次备份手动跑」的成本。

另一个必须知道的细节是 --batch 和 --pinentry-mode loopback 的组合。在无 tty 的 cron 环境下,默认的 pinentry 会因为没有终端而失败,所以生产脚本里通常要写成:

gpg --batch --yes --pinentry-mode loopback --passphrase-file /root/.gpgpass \
    --local-user "$KEY_ID" --detach-sign --armor "$BACKUP"

把密码写在文件里当然不理想,所以那个文件必须 chmod 600,且只对 root 可读。更稳妥的方案是把它交给 systemd 的 LoadCredential 或者一个受保护的 secret 目录。

公钥的分发与信任

签名要能被别人验证,前提是别人拿到你的公钥。导出与导入都很简单:

# 导出公钥(不含私钥,可以安全公开)
gpg --armor --export 你的邮箱@example.com > mykey.asc

# 别人导入你的公钥
gpg --import mykey.asc

# 也可以从密钥服务器拉取
gpg --keyserver keys.openpgp.org --recv-keys 1A2B3C4D5E6F7A8B

这里要泼一盆冷水:导入公钥不等于信任公钥。攻击者完全可以生成一个姓名邮箱都和你一模一样的密钥四处散播。GPG 的应对机制是信任网(Web of Trust),你要么手工核对指纹并签名确认,要么至少换一条独立渠道核验密钥指纹。查看指纹命令是:

gpg --fingerprint 你的邮箱@example.com

把指纹公布在你网站的一个固定页面上,别人核对时以这个为准,就能有效防止公钥被冒名替换。对个人站长来说,这一小步几乎是零成本的,但它让整套签名体系从「看起来安全」变成了「真的安全」。

密钥备份与撤销:别让自己被锁在门外

私钥一旦丢失,之前所有用公钥加密的备份就永远解不开了;私钥一旦泄露,别人就能冒充你签名。所以两件事必须做:备份和撤销证书。

备份私钥用 --export-secret-keys,务必加密后再存:

gpg --armor --export-secret-keys 你的邮箱@example.com > private.asc
# 推荐再包一层对称加密,离线存到两块不同的介质上
gpg --symmetric --cipher-algo AES256 private.asc

撤销证书(Revocation Certificate)是私钥丢失或泄露时的「作废声明」。它应该在生成密钥后立刻创建并离线保存:

gpg --gen-revoke 你的邮箱@example.com > revoke.asc

真要撤销时,把 revoke.asc 导入并发布到密钥服务器,别人就会知道这个密钥不能再用了。很多人把撤销证书和私钥存在同一台机器上,这是没有意义的——机器丢了,撤销证书也一起丢了,等于没有。

小结

GPG 对个人站长不是花架子,它补上的是数据链路里最容易被忽视的一环:可验证性。备份加密,防止对象存储里的文件被任何人翻看;发布产物签名,让每次恢复都能确认内容没被篡改;密钥备份和撤销证书,让整套体系在意外发生时依然可控。这三件事加起来,可能就是一次事故和一次虚惊之间的差别。

入门路径很清楚:先生成一对 4096 位 RSA 或 Curve 25519 的密钥,再掌握加密、签名、验证三条命令,最后把它们接进备份脚本,并在恢复前强制验证。做完这些,你的运维链路就从「我猜应该没问题」升级成了「我能证明它没问题」。

Last modification:October 1st, 2026 at 01:30 pm

Leave a Comment