本节摘要:PGP 在 1991 年把"对称快、非对称解决分发"的混合加密格局带给普通人,也把密码学推上了与出口管制对簿公堂的舞台;三十年后,同样的信封结构支撑着文件加密工具与全盘磁盘加密。本节拆解数字信封的组装方式、信任网与证书路线的分野,并给出当下工具选型的务实建议。
TLS 保护的是"传输途中",但邮件的历史包袱让端到端保密很难搭便车:邮件会在多台服务器间转发、被服务器存档索引,发件与收件之间隔着不设防的明文地带。1991 年,菲尔·齐默曼写出 Pretty Good Privacy(PGP),让两个普通人在没有任何基础设施的情况下互发保密邮件——这直接踩中了当时的敏感神经:强加密在美国出口管制下被视为军火,司法部以"未经许可出口军火"对齐默曼立案调查,直到 1996 年撤诉。这段"密码战争"史在 7.4 节还会回响,它确立了一件今天看来理所当然的事:个人使用强加密合法且正当。
PGP 的技术遗产是一副数字信封,组装次序与第 4 章的结论严丝合缝:
收件人反向拆封:私钥解出会话密钥,对称解出正文。多收件人场景只是多贴几层封口,正文密文只存一份——这正是 PGP、OpenPGP(其开放标准)、S/MIME 以及现代文件加密工具共用的骨架。
PGP 与 6.2 节的 WebPKI 走了截然不同的信任路线。PGP 没有中心 CA,采用信任网(Web of Trust):用户之间互相签署对方的公钥,信任沿着签名链传递;公钥真伪靠指纹(公钥的 SHA-1 或 SHA-256 摘要)当面或带外核对。它浪漫而彻底地去中心化,代价是可用性——密钥管理、指纹核对、密钥丢失后的吊销传播,每一步都在劝退普通用户;学者对 PGP 易用性的著名研究标题就叫"为什么 Johnny 还不会加密"。现实中,邮件端到端加密始终是极客的圈子,即时通讯领域改由 Signal 协议(双棘轮密钥轮换)接棒,把密钥管理全部自动化后才真正普及了端到端。
企业邮件的答案是 S/MIME:沿 X.509 路线,证书由公司内部 CA 或公共 CA 签发,信任模型与 WebPKI 同构。两种路线的取舍见下表:
| 维度 | PGP / OpenPGP | S/MIME |
|---|---|---|
| 信任模型 | 信任网,用户互签 | CA 层级,同 WebPKI |
| 公钥核对 | 指纹带外比对 | 验证书链 |
| 部署成本 | 零基础设施,自管理 | 需要证书签发体系 |
| 典型场景 | 个人、开源社区 | 企业邮件与合规审计 |
| 易用性 | 密钥管理全靠自觉 | 与既有 PKI 体系融合 |
文件与磁盘加密走的则是信封结构的第三种落地。文件加密:老牌的 GPG 既可对称口令加密也可公钥信封;新工具 age 把这套模式精简到两条命令,内部就是 X25519 信封加 ChaCha20-Poly1305。全盘磁盘加密(BitLocker、FileVault、LUKS)同样是一个信封:数据用 AES-XTS 加密,而解锁数据密钥的"信封钥匙"由口令派生密钥或可信平台模块(TPM)保管——开机输入的那次口令,换来整块硬盘的静态安全。下面用标准库把口令信封完整复刻一遍:
import hashlib, os, hmac def envelope_encrypt(data: bytes, password: str) -> bytes: salt, nonce, dek = os.urandom(16), os.urandom(12), os.urandom(32) kek = hashlib.scrypt(password.encode(), salt=salt, n=2**15, r=8, p=1, dklen=32) stream = bytearray() counter = 0 # 以 kek+nonce 造流,演示信封结构 while len(stream) < len(data): stream += hashlib.sha256(kek + nonce + counter.to_bytes(8, "big")).digest() counter += 1 wrapped = bytes(a ^ b for a, b in zip(data, stream)) # DEK 之外:数据用会话密钥流加密 tag = hmac.new(kek, salt + wrapped, hashlib.sha256).digest() # 完整性标签 return salt + nonce + tag + wrapped def envelope_decrypt(blob: bytes, password: str) -> bytes: salt, nonce, tag, wrapped = blob[:16], blob[16:28], blob[28:60], blob[60:] kek = hashlib.scrypt(password.encode(), salt=salt, n=2**15, r=8, p=1, dklen=32) if not hmac.compare_digest(hmac.new(kek, salt + wrapped, hashlib.sha256).digest(), tag): raise ValueError("口令错误或数据被篡改") # 先验标签,再解数据 stream, counter = bytearray(), 0 while len(stream) < len(wrapped): stream += hashlib.sha256(kek + nonce + counter.to_bytes(8, "big")).digest() counter += 1 return bytes(a ^ b for a, b in zip(wrapped, stream)) blob = envelope_encrypt("绝密会议纪要".encode(), "我的口令") print(envelope_decrypt(blob, "我的口令").decode())
⚠️ 常见坑:把加密压缩包(如某些压缩工具的旧默认)当成安全文件加密——历史版本多用弱密钥派生且无认证标签。文件加密请用 age 或 GPG 这类专用工具,它们内部替你把信封、AEAD 与密钥派生全部做对。
从传输到静态存储,密码学的"防御性应用"还有一块最年轻的大陆。下一节看区块链如何把哈希与签名从"保护通信"改造成"构建共识"。