持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第25天,点击查看活动详情
使用 PGPainless 轻松实现 OpenPGP
OpenPGP 以难以使用而著称,对于 Java 生态系统尤其如此。本文将说明通过使用 PGPainless 轻松实现 。
谈到加密,Bouncy Castle 可能是 Java 生态系统中最受欢迎的包。它具有强大的算法并持续维护中。
但是,当你尝试使用它为应用程序添加 OpenPGP 支持时,其中包含难以阅读、理解的源代码。即使要创建一个简单的加密、签名消息,也应该包装至少三个从不同途径获得的不同 OutputStream,每个都使用大量模糊参数进行初始化,例如缓冲区大小和 Provider 类。
OpenPGP 协议具有通过接收者公钥上的字段来支持信令算法的机制。你怎么知道哪些算法是安全的?
Bouncy Castle OpenPGP API 强大毋庸置疑,但不幸的是需要你自己完成繁重的工作。如果你熟悉 OpenPGP 协议,那是可以接受的,但如果你不是专家,只是想完成工作。将所有对安全至关重要的设置工作留给 API 的使用者是不妥的。
PGPainless 在内部使用了 Bouncy Castle,但通过使用 builder 模式隐藏了大部分复杂性,但在比 Bouncy Castle 更高的抽象级别上。它可以让你尽快完成工作,并且,如果你不提供加密算法等细节,它会为你选择安全可靠的默认值。
特别是在签名验证方面,甚至是 Bouncy Castle 团队自己的示例也只是展示了如何检查签名的正确性。他们没有讨论检查签名的有效性。例如,签名可能在加密上是正确的,但由于签名密钥被撤销而无效。需要执行一整套检查来检查签名在某个参考时间是否真的有效。PGPainless 会为你执行这些检查。
现在让我们看看一些例子:
生成一个新的 OpenPGP 密钥:
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing()
.modernKeyRing("Romeo <romeo@montague.lit>", "p4ssw0rd");
结果是使用现代 EdDSA 和 XDH 子密钥的受密码保护的 OpenPGP 密钥。API 抽象了所有烦人的东西,例如哪些算法用于密钥,哪些算法偏好(散列、对称和压缩算法)要设置,当然,使用绑定签名将密钥和用户 ID 绑定在一起. 同时,还有一个更灵活的 API PGPainless.buildKeyRing(),允许用户根据自己的喜好更改所有这些参数。
现在让我们对消息进行签名和加密:
// We generated our key in the previous example
PGPSecretKeyRing secretKey = ...;
SecretKeyRingProtector protector = SecretKeyRingProtector
.unlockAnyKeyWith(Passphrase.fromPassword("p4ssw0rd"));
// Extract our own public key certificate
PGPPublicKeyRing certificate = PGPainless.extractCertificate(secretKey);
// Read Juliets public key certificate from somewhere
PGPPublicKeyRing julietsCertificate = PGPainless.readKeyRing()
.publicKeyRing(julietsCertInputStream);
// The message we want to encrypt and sign
InputStream plaintextIn = ...;
// The destination to where we want to write the ciphertext
OutputStream ciphertextOut = ...; // e.g. new ByteArrayOutputStream();
// Set up the stream
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
.onOutputStream(ciphertextOut)
.withOptions(ProducerOptions.signAndEncrypt(
new EncryptionOptions()
.addRecipient(certificate)
.addRecipient(julietsCertificate),
new SigningOptions()
.addInlineSignature(protector, secretKey)
)
);
// Encrypt and sign
Streams.pipeAll(plaintextIn, encryptionStream);
encryptionStream.close();
// Information about the encryption (algorithms, detached signatures etc.)
EncryptionResult result = encryptionStream.getResult();
这仍然不是单线,但与直接使用 Bouncy Castle 时需要做的工作相比,已经有了巨大的改进。
验证和解密以类似的方式完成。如果你在家里跟着,请注意我们需要 Juliet 的密钥来解密消息和 Romeo 的证书来验证签名。
// Set up the stream
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
.onInputStream(encryptedInputStream)
.withOptions(new ConsumerOptions()
.addDecryptionKey(julietsSecretKey, secretKeyProtector)
.addVerificationCert(romeosCertificate)
);
// Decrypt and verify the data
Streams.pipeAll(decryptionStream, outputStream);
decryptionStream.close();
// Result contains information like signature status etc.
OpenPgpMetadata metadata = decryptionStream.getResult();
在内部,PGPainless 解析加密的消息,设置所有不同的包装流,根据健全的策略检查使用的算法,解密数据,进行完整性检查,并通过评估 Romeo 的证书来验证签名的正确性和有效性。
不过,API 的功能还不止于此,例如使用密码进行对称加密、在其他用户的证书上创建签名、修改密钥(添加用户 ID 和子密钥、更改密码、撤销和过期密钥和用户 ID... )等等。
还有一件事我想说明。 无状态 OpenPGP 协议是标准化 OpenPGP 命令行界面的草案。该草案定义了常见的操作,例如生成密钥、加密/解密消息、创建和验证签名等。
PGPainless 提供了此 API 的编程适配以及CLI 应用程序。这意味着如果应用程序需要执行基本的 OpenPGP 操作,可以简单地依赖一个名为[sop-java]的模块,它定义了程序化 SOP 接口。该接口可以由任何 OpenPGP 库实现,但 PGPainless 通过模块pgpainless-sop提供通过自身来实现。这样你就可以从超级简单易用的 OpenPGP API 中受益,而不会被锁定使用 PGPainless。