Python OpenPGP实现PGPy:纯Python加密签名与密钥管理实战指南
1. 项目概述为什么我们需要一个纯Python的OpenPGP实现如果你在Python项目里处理过加密、签名或者密钥管理大概率绕不开GnuPGGPG这个命令行工具。通过subprocess调用gpg命令确实能完成任务但体验嘛……就像用遥控器操作一台老式录像机步骤繁琐错误处理麻烦还得依赖系统环境。尤其是在需要自动化、集成到Web服务或者处理大量密钥操作的场景下这种“外包”方式就显得力不从心了。这时候PGPy的价值就凸显出来了。它是一个用纯Python实现的OpenPGP库目标不是替代GnuPG而是在Python生态内部为开发者提供一个原生、可控、可编程的OpenPGP操作接口。简单说它让你能用Python代码直接“说”OpenPGP协议的语言生成、解析、加密、签名那些.asc或.gpg文件背后的数据包而无需启动外部进程。我最初接触它是因为一个需要批量验证数千个PGP签名的数据流水线项目。用python-gnupg包装器调用GPG进程管理和日志解析成了性能瓶颈和调试噩梦。换成PGPy后所有逻辑都在Python内存里完成速度提升明显依赖干净调试也直观多了。当然它并非万能钥匙其设计定位和功能边界非常清晰专注于OpenPGP标准RFC 4880等中数据包Packet的解析与构建以及基于这些数据包的核心密码学操作如RSA/ECDSA签名、AES加密等。这意味着像密钥环管理、与keyserver交互等高级功能它可能不如GPG完善但它给了你一把深入OpenPGP内部结构的螺丝刀。从技术栈来看PGPy属于“协议实现层”。它底层依赖cryptography或pycryptodome这类通用密码学库来处理具体的加密算法自己则负责将OpenPGP复杂的报文结构——公钥包、签名包、加密会话密钥包、对称加密数据包等——组装或拆解成Python对象。这种设计让它既保持了纯Python的轻便性又能利用成熟的底层密码学实现保障安全。2. 核心设计思路在易用性与标准遵从性之间找平衡OpenPGP标准是个庞然大物历史包袱重可选算法和格式变体多。一个库要实现它无非两种路径一是追求大而全完全复刻GPG的所有行为二是抓住核心提供一个足够好用且符合标准的子集。PGPy明显选择了后者并在设计上做了几个关键取舍。2.1 对象模型设计直观映射OpenPGP实体PGPy的核心是几个主要的类它们直接对应OpenPGP世界里的实体PGPKey: 代表一个OpenPGP密钥包含主密钥用于签名和可能的多个子密钥用于加密。一个PGPKey对象可以同时装载公钥和私钥材料。PGPMessage: 代表一条OpenPGP消息可能是加密的、签名的、压缩的或纯文本的。它内部是由多个PGPPacket对象组成的序列。PGPSignature: 代表一个独立的签名包。PGPPacket: 所有OpenPGP数据包的基类具体的包类型如PublicKeyPacket,SignaturePacket,SymmetricallyEncryptedDataPacket继承自它。这种设计的好处是直观。比如你从一个.asc文件加载一个密钥得到的就是一个PGPKey对象你可以通过key.subkeys访问它的子密钥通过key.userids访问绑定的用户ID。想加密就对一个PGPMessage对象调用.encrypt()方法传入收件人的PGPKey。整个流程符合你对OpenPGP操作的心理模型。from pgpy import PGPKey, PGPMessage # 加载收件人公钥 pub_key, _ PGPKey.from_file(recipient_public.asc) # 创建一条文本消息 message PGPMessage.new(这是一条秘密信息。) # 加密 encrypted_message message.encrypt(pub_key) # 加密后的数据可以直接导出为ASCII码或二进制 ascii_armored str(encrypted_message)2.2 算法支持务实的选择OpenPGP标准定义了几十种算法。PGPy没有全盘支持而是聚焦于目前最常用、最安全的组合非对称加密/签名: RSA (2048位及以上) DSA 以及现代的椭圆曲线算法 EdDSA (Ed25519) 和 ECDSA (NIST P-256, P-384, P-521)。对于新项目强烈推荐使用Ed25519进行签名它比传统的RSA更安全、更快、签名更短。对称加密: AES (128, 192, 256位) Camellia。AES-256是当前事实上的标准。哈希算法: SHA-256, SHA-384, SHA-512, SHA-224。SHA-1已被废弃不应再使用。压缩算法: ZLIB, ZIP, BZip2。通常使用ZLIB。这个支持列表覆盖了99%的现代用例。如果你遇到一个使用古老算法如IDEA或非常小众算法的PGP数据PGPy可能会解析失败。但这在某种程度上是一种“特性”它促使你使用更安全的算法。实操心得算法协商的“坑”OpenPGP在加密时会从收件人密钥的首选算法列表和加密者本地支持的算法列表中取交集选一个来用。PGPy在创建密钥时会设置一个默认的算法偏好顺序。有时加密失败报错“无法找到兼容的加密算法”很可能是因为两端支持的算法没有交集。你需要检查并可能调整密钥的算法偏好。例如一个只支持RSA-4096和AES-256的密钥无法被一个只支持Ed25519和Camellia的库加密。通常生成密钥时使用库的默认设置就能保证最好的兼容性。2.3 “纯Python”的利与弊“纯Python”意味着除了标准库和cryptography这样的纯Python尽管其底层是C依赖外没有其他二进制扩展。这带来了巨大优势可移植性: 在任何有Python的地方都能运行Windows、Linux、macOS甚至一些嵌入式环境无需编译本地库。可审计性: 代码清晰容易阅读和审计安全。易于集成: 直接pip install pgpy即可依赖管理简单。但代价是性能。纯Python实现的密码学操作尤其是大量数据的对称加密解密速度上无法与GPG这种高度优化的C原生程序相比。对于偶尔的单个文件操作这根本不是问题。但对于需要实时加密解密海量数据流的应用这就可能成为瓶颈。我的经验是PGPy的典型使用场景是“管理性”和“协议性”操作而非“数据管道”操作。例如密钥生成与转换签名验证验证通常比生成快解析消息结构并提取元数据如发件人Key ID封装/解封装加密会话实际的批量数据加密可用更快的专用库处理3. 从安装到“Hello World”快速上手指南3.1 环境准备与安装PGPy的安装极其简单。它需要Python 3.7及以上版本。强烈建议在虚拟环境中操作。# 创建并激活虚拟环境以venv为例 python -m venv pgp-env source pgp-env/bin/activate # Linux/macOS # pgp-env\Scripts\activate # Windows # 安装PGPy pip install pgpy安装过程会自动拉取核心依赖cryptography。如果你想使用Camellia算法可能需要额外安装camellia但cryptography通常已包含。安装完成后可以在Python中导入验证import pgpy print(pgpy.__version__)3.2 生成你的第一对PGP密钥让我们跳过理论直接生成一对属于你自己的Ed25519签名主密钥 CV25519加密子密钥。这是目前最推荐的新密钥类型组合。from pgpy import PGPKey, PGPUID from pgpy.constants import PubKeyAlgorithm, KeyFlags, HashAlgorithm, SymmetricKeyAlgorithm, CompressionAlgorithm # 1. 创建一个空密钥对象主密钥 key PGPKey.new(PubKeyAlgorithm.EdDSA, 256) # EdDSA with Ed25519 curve # 2. 创建一个用户ID对象你的身份信息 uid PGPUID.new(张三 zhangsanexample.com) # 3. 为主密钥添加用户ID。这一步会生成一个自签名将用户ID绑定到主密钥。 # 我们需要指定这个主密钥的用途签名和哈希算法等参数。 key.add_uid(uid, usage{KeyFlags.Sign, KeyFlags.Certify}, # 主密钥用于签名和认证证明子密钥 hashes[HashAlgorithm.SHA256], ciphers[SymmetricKeyAlgorithm.AES256], compression[CompressionAlgorithm.ZLIB]) # 4. 现在添加一个用于加密的子密钥使用Curve25519 subkey PGPKey.new(PubKeyAlgorithm.ECDH, 256) # ECDH with Curve25519 key.add_subkey(subkey, usage{KeyFlags.EncryptCommunications, KeyFlags.EncryptStorage}, hashes[HashAlgorithm.SHA256]) # 此时key对象包含了主密钥和子密钥但私钥部分还在内存中。 print(f密钥指纹: {key.fingerprint}) print(f用户ID: {key.userids}) print(f子密钥数量: {len(key.subkeys)})运行这段代码你会在内存中得到一个完整的密钥对。但注意此时私钥是未加密的。在生产环境中必须为私钥设置密码。3.3 为私钥添加密码保护OpenPGP允许为私钥单独加密。PGPy中你需要先“锁定”私钥。# 假设key是上一步生成的包含私钥的PGPKey对象 passphrase MySuperStrongPassphrase123! # 请务必使用强密码 # 使用密码加密私钥 key.protect(passphrase, SymmetricKeyAlgorithm.AES256, HashAlgorithm.SHA256) # 现在key中的私钥材料已被加密。任何使用私钥的操作如签名、解密都需要先解锁。 try: key.sign(some data) # 这会失败因为私钥被锁定了 except Exception as e: print(f签名失败: {e}) # 解锁私钥以使用 with key.unlock(passphrase): # 在这个上下文管理器内私钥是解密的可以正常使用 signature key.sign(some data) print(签名成功) # 退出上下文后私钥在内存中会再次被清除或标记为锁定。重要安全警告key.protect()只加密了PGPKey对象中存储的私钥数据。在Python中由于内存管理机制私钥的明文副本可能在内存中残留。with key.unlock(passphrase):模式能一定程度上控制解密状态的生命周期但无法绝对保证内存清零。对于处理极高敏感密钥的应用需要评估此风险。PGPy文档也明确指出了这一点。3.4 密钥的导出与导入生成密钥后你需要导出才能分发或备份。# 导出公钥ASCII码格式 public_key_ascii str(key.pubkey) # .pubkey属性获取只包含公钥的副本 with open(zhangsan_public.asc, w) as f: f.write(public_key_ascii) # 导出私钥包含加密的私钥ASCII码格式 private_key_ascii str(key) # 直接转换整个key对象会包含私钥 with open(zhangsan_private.asc, w) as f: f.write(private_key_ascii) # 从文件导入密钥 imported_pub_key, _ PGPKey.from_file(zhangsan_public.asc) imported_priv_key, _ PGPKey.from_file(zhangsan_private.asc) # 导入后如果需要使用私钥操作同样需要解锁 # imported_priv_key.unlock(passphrase)现在你已经完成了PGPy最核心的循环生成、保护、导出、导入密钥。接下来我们用它来做点实际的事情。4. 核心操作实战加密、解密、签名与验证4.1 加密与解密保护消息内容假设Alice想给Bob发送一条加密消息。Alice有Bob的公钥文件bob_public.asc。from pgpy import PGPKey, PGPMessage # Alice的操作加密 # 1. 加载Bob的公钥 bob_pub_key, _ PGPKey.from_file(bob_public.asc) # 通常使用第一个可用于加密的子密钥 encryption_key bob_pub_key.subkeys[0] # 2. 创建消息对象 plaintext Bob明天下午3点老地方见。这是项目密码xY7#9!pL message PGPMessage.new(plaintext) # 3. 加密 encrypted_message message.encrypt(encryption_key) print(--- 加密后的消息 (ASCII Armored) ---) print(str(encrypted_message)) print(--- 结束 ---) # Alice可以将str(encrypted_message)通过邮件或其他方式发送给Bob。Bob收到加密的ASCII码文本后用自己的私钥解密。# Bob的操作解密 # 1. 加载Bob自己的私钥已加密保护 bob_priv_key, _ PGPKey.from_file(bob_private.asc) # 2. 从接收的文本加载加密消息 received_ciphertext -----BEGIN PGP MESSAGE----- ... (实际收到的ASCII码文本) ... -----END PGP MESSAGE----- encrypted_msg_obj PGPMessage.from_blob(received_ciphertext) # 3. 解锁私钥并解密 passphrase Bob的私钥密码 with bob_priv_key.unlock(passphrase): # decrypt方法返回解密后的PGPMessage对象 decrypted_msg_obj bob_priv_key.decrypt(encrypted_msg_obj) # .message属性获取明文内容 print(f解密出的消息: {decrypted_msg_obj.message})关键点解析encrypt方法默认会同时进行“数字信封”操作生成一个随机的会话密钥如AES-256密钥用于加密实际消息然后用收件人的公钥加密这个会话密钥一并打包。输出的encrypted_message是一个PGPMessage对象str()将其转换为可读的ASCII码格式Armored便于文本传输。也可以用.__bytes__()获取二进制格式。解密时PGPy会自动在消息包中找到被收件人公钥加密的会话密钥包用对应的私钥解密出会话密钥再去解密数据包。4.2 签名与验证确保消息来源与完整性签名用于证明消息确实来自声称的发送者且未被篡改。场景一分离式签名Detached Signature常用于软件发布。签名单独作为一个文件如.sig与原始文件如.tar.gz一起分发。from pgpy import PGPKey, PGPMessage from pgpy.constants import SignatureType # 发送者Alice生成签名 alice_priv_key, _ PGPKey.from_file(alice_private.asc) data_to_sign b这是非常重要的软件发布包v1.2.3的二进制数据... # 创建针对二进制数据的消息对象 message PGPMessage.new(data_to_sign, fileTrue) # fileTrue表示这是文件数据 passphrase Alice的私钥密码 with alice_priv_key.unlock(passphrase): # 生成分离式签名 signature alice_priv_key.sign(message, detachTrue) # 导出签名 signature_armored str(signature) with open(software-v1.2.3.tar.gz.sig, w) as f: f.write(signature_armored) print(分离式签名已生成。) # 接收者Bob验证签名 alice_pub_key, _ PGPKey.from_file(alice_public.asc) # 重新加载原始数据 with open(software-v1.2.3.tar.gz, rb) as f: original_data f.read() original_message PGPMessage.new(original_data, fileTrue) # 加载签名文件 with open(software-v1.2.3.tar.gz.sig, r) as f: signature_blob f.read() signature_obj PGPMessage.from_blob(signature_blob) # 注意分离签名本身也是一个PGPMessage # 验证 try: # 使用公钥验证签名和消息 verification alice_pub_key.verify(original_message, signature_obj) print(f验证成功签名由密钥 {verification.signer} 在 {verification.signed_at} 创建。) except pgpy.errors.PGPError as e: print(f验证失败: {e})场景二内联签名Clearsign签名和明文消息混合在一起形成一段以-----BEGIN PGP SIGNED MESSAGE-----开头的文本人类可直接阅读明文部分。这在邮件通信中很常见。# 发送者生成内联签名 cleartext 各位同事 下周一下午2点在301会议室召开项目评审会请准时参加。 祝好 Alice message PGPMessage.new(cleartext, cleartextTrue) # cleartextTrue是关键 with alice_priv_key.unlock(passphrase): signed_message alice_priv_key.sign(message) # 不指定detachTrue默认是内联签名 print(--- 内联签名消息 ---) print(str(signed_message)) print(--- 结束 ---) # 接收者验证内联签名 received_signed_text str(signed_message) # 模拟接收到的文本 signed_msg_obj PGPMessage.from_blob(received_signed_text) # 对于内联签名消息直接验证其本身即可 try: # 注意这里是对signed_msg_obj进行验证它会自动分离出签名和明文进行校验 verification alice_pub_key.verify(signed_msg_obj) print(f内联签名验证成功明文内容\n{verification.message}) except pgpy.errors.PGPError as e: print(f内联签名验证失败: {e})实操心得fileTrue和cleartextTrue的区别这是新手最容易混淆的地方。PGPMessage.new(data, fileTrue): 用于处理二进制数据如图片、压缩包、可执行文件。签名/验证时会对原始字节进行哈希。在生成分离签名时通常使用此模式。PGPMessage.new(data, cleartextTrue): 用于处理文本数据并且希望生成人类可读的内联签名格式。库会自动对文本进行规范化处理如统一换行符然后再哈希签名。即使你只是对一段文本做分离签名如果用了cleartextTrue验证方也必须用同样的模式否则哈希对不上。安全起见对文本文件进行分离签名时我通常也使用fileTrue模式将其作为二进制流处理避免换行符等平台差异问题。5. 深入解析处理复杂消息与密钥管理5.1 解析复杂的OpenPGP消息流一个OpenPGP消息可能包含多个数据包压缩包、加密会话密钥包、对称加密数据包、签名包等。PGPy允许你深入这些层级。encrypted_message PGPMessage.from_blob(encrypted_armored_text) print(f消息包含 {len(encrypted_message)} 个数据包。) for i, packet in enumerate(encrypted_message): print(f\n数据包 {i}: 类型{type(packet).__name__}) if isinstance(packet, pgpy.packet.SymmetricKeyEncryptedSessionKeyPacket): print(f - 对称加密的会话密钥包 (算法: {packet.symmetric_algorithm.name})) elif isinstance(packet, pgpy.packet.SymmetricallyEncryptedDataPacket): print(f - 对称加密数据包) # 解密后里面可能还嵌套着压缩包或字面数据包 elif isinstance(packet, pgpy.packet.CompressedDataPacket): print(f - 压缩数据包 (算法: {packet.algorithm.name})) # 解压后可以得到内部的消息 elif isinstance(packet, pgpy.packet.LiteralDataPacket): print(f - 字面数据包) print(f 文件名: {packet.filename}) print(f 时间戳: {packet.date}) # packet.data 是实际的明文数据bytes elif isinstance(packet, pgpy.packet.SignaturePacket): print(f - 签名包) print(f 签名类型: {packet.sig_type}) print(f 签名者Key ID: {packet.signer})这种解析能力在调试、编写中间件或分析未知PGP消息结构时非常有用。5.2 密钥检查与操作管理密钥时经常需要查看其属性或进行修改。key, _ PGPKey.from_file(some_key.asc) # 1. 基本信息 print(f指纹: {key.fingerprint}) print(fKey ID (长): {key.keyid}) print(fKey ID (短): {key.keyid.short}) # 通常显示8位短ID print(f算法: {key._key.algorithm}) # 主密钥算法 print(f创建时间: {key.created}) # 2. 用户ID和签名 for uid in key.userids: print(f\n用户ID: {uid.name}) # 每个用户ID可能有多个自签名用于绑定不同的功能 for certification in uid.signatures: print(f 签名类型: {certification.sig_type}) print(f 哈希算法: {certification.hash_algorithm}) print(f 创建时间: {certification.created}) # 3. 子密钥 for subkey in key.subkeys: print(f\n子密钥 Key ID: {subkey.keyid}) print(f 算法: {subkey._key.algorithm}) print(f 用途: {subkey.flags}) # 查看是用于加密还是签名 print(f 是否已过期: {subkey.is_expired}) if subkey.expires_at: print(f 过期时间: {subkey.expires_at}) # 4. 修改密钥属性例如延长过期时间 from datetime import datetime, timedelta # 假设我们要将主密钥和所有子密钥的过期时间延长1年 new_expiry datetime.now() timedelta(days365) # 注意修改密钥属性需要私钥和密码因为需要生成新的自签名 with key.unlock(passphrase): # 更新主密钥的过期时间会影响所有用户ID和子密钥的绑定签名不完全是需要具体更新 # 更常见的操作是更新特定子密钥的过期时间 for subkey in key.subkeys: # 创建一个新的子密钥绑定签名设置新的过期时间 # 这里需要更底层的操作通常建议使用专门的密钥编辑工具或重新生成。 pass # 对于复杂的密钥编辑如添加用户ID、撤销子密钥PGPy的API可能不够直接有时需要手动构建签名包。5.3 与其他工具的互操作性PGPy生成的密钥和消息能否被GnuPG、OpenPGP.js等其他实现识别答案是绝大多数情况下可以但需要注意细节。兼容性检查清单算法: 使用双方都支持的通用算法如RSA-2048/4096, Ed25519, ECDH with Curve25519, AES-256。格式:PGPy导出的ASCII码格式是标准的“Armored”格式GPG可以直接gpg --import。哈希算法: 在签名时确保使用安全的哈希算法如SHA-256。PGPy默认设置通常是合理的。压缩: 有些旧系统可能不支持BZip2压缩使用ZLIB兼容性最好。密钥过期时间: 如果密钥设置了过期时间确保接收方系统时钟正确。测试互操作性# 用PGPy生成的公钥文件在GPG中导入 gpg --import zhangsan_public.asc # 用GPG加密一个文件然后用PGPy解密需要先将GPG的收件人公钥导入PGPy # 反之亦然用PGPy加密用GPG解密。如果遇到问题一个很好的调试方法是使用pgpdump一个Python包来解析PGP数据包对比两者生成的数据包结构差异。pip install pgpdump pgpdump your_message.asc6. 常见问题、故障排查与性能优化6.1 常见错误与解决方案错误信息/现象可能原因解决方案pgpy.errors.PGPError: No suitable encryption key found1. 收件人密钥没有标记为用于加密的子密钥。2. 加密者支持的算法与收件人密钥首选的算法不匹配。3. 尝试用签名密钥去加密。1. 检查收件人密钥的subkeys.flags确保包含EncryptCommunications或EncryptStorage。2. 检查双方支持的算法列表。生成密钥时使用库默认设置可最大化兼容性。3. 加密必须使用加密子密钥usage包含Encrypt*。pgpy.errors.PGPError: Signature verification failed1. 签名确实无效消息被篡改。2. 验证使用的公钥不是真正的签名者。3. 签名格式不匹配如对二进制数据用了cleartext模式签名。4. 哈希算法不被支持。1. 确认消息在传输中未被修改。2. 确保导入的公钥指纹与签名中的签名者Key ID匹配。3. 确认签名和验证时使用的PGPMessage.new模式一致fileTrue或cleartextTrue。4. 尝试在签名时指定更通用的哈希算法如HashAlgorithm.SHA256。TypeError: a bytes-like object is required...在需要字节数据的地方传入了字符串。对文本数据确保在需要字节时使用.encode(utf-8)。创建PGPMessage时根据场景选择new(text, cleartextTrue)或new(text.encode(utf-8), fileTrue)。解密失败密码正确1. 私钥保护使用的加密算法或哈希算法不被当前环境支持。2. 私钥文件损坏或不完整。3. 尝试解密的密文不是用此私钥对应的公钥加密的。1. 使用AES256和SHA256这种最通用的组合保护私钥。2. 重新导出/导入密钥试试。3. 用pgpdump检查加密消息的收件人Key ID确认是否匹配。导入密钥时报错或警告1. 密钥包含PGPy不支持的算法或特性。2. ASCII码格式损坏如多余的换行。1. 尝试用GPG等工具转换密钥删除不支持的子密钥或用户属性。2. 确保导入的是完整的、未被截断的ASCII码块。性能慢处理大文件时内存占用高PGPy是纯Python实现加解密大文件时需将整个文件读入内存。对于超大文件考虑1. 使用流式处理如果应用支持。PGPy的底层包解析是流式的但高层API可能一次性加载。2. 对于纯加密/解密可考虑使用更底层的cryptography库结合OpenPGP的会话密钥或使用GPG进行批量数据处理用PGPy仅管理密钥和会话。6.2 性能优化实践密钥缓存频繁使用的密钥如用于验证签名的公钥不要在每次操作时都从文件读取和解析。可以在应用启动时加载到内存的字典中。避免重复的ASCII码编解码str(key)和from_blob()涉及Base64编解码。在内部流水线中如果不需要人类可读格式尽量使用二进制bytes(key)和from_blob(bytes_data)传递PGPMessage或PGPKey对象。选择性解析如果你只关心消息的元数据如发件人、时间而不需要解密内容可以尝试只解析消息的前几个数据包。这需要更底层的使用pgpy.packet模块。并发处理由于GIL的存在纯Python的密码学运算难以通过多线程提速。对于需要处理大量独立消息的任务可以使用multiprocessing模块利用多核。6.3 安全最佳实践私钥保护始终使用强密码protect()你的私钥。即使如此也要意识到Python内存中明文私钥残留的风险。对于服务器端应用考虑使用硬件安全模块HSM或专门的密钥管理服务KMSPGPy可能不适合作为核心私钥存储解决方案。算法选择弃用弱算法RSA2048, DSA, SHA-1。优先使用Ed25519签名和X25519加密。密钥有效期为密钥设置合理的过期时间并建立轮换机制。验证签名时检查更多信息不要只验证签名是否通过。还要检查签名时间是否合理防止重放攻击。签名者Key ID是否在你信任的列表中。签名使用的哈希算法是否足够强至少SHA-256。谨慎处理输入如同所有密码学库不要将不受信任的数据直接喂给PGPy解析以防潜在的格式解析漏洞或压缩炸弹虽然PGPy本身有部分防护。在我自己的项目中PGPy已经成为了处理OpenPGP相关自动化任务的可靠工具。它最大的优势在于将OpenPGP这个复杂的协议“翻译”成了Python开发者熟悉的面向对象接口让集成变得清晰可控。虽然它在极端性能场景和某些边缘特性上无法与GPG媲美但对于绝大多数需要以编程方式生成、解析、加密、签名PGP数据的应用来说它提供了恰到好处的抽象和足够强大的功能。下次当你的Python脚本需要和那个古老的.asc或.gpg世界对话时不妨先试试PGPy它可能会让你省去不少折腾外部命令的麻烦。