用PGP轻松实现OpenPGP
OpenPGP有难以使用的声誉。这对Java生态系统来说尤其如此。这就是PGPainless出现的原因。
说到加密,Bouncy Castle可能是Java生态系统中最受欢迎的库。它有很好的算法支持,并被积极维护。
然而,当你试图用它来为你的应用程序添加OpenPGP支持时,你很快就会发现自己在StackOverflow的帖子中挖掘出了大量难以阅读的源代码。甚至为了创建一个简单的加密、签名的消息,你应该包裹至少三个不同的OutputStreams,这些OutputStreams是从不同的Factories中获得的,每个初始化的参数都很模糊,如缓冲区大小和Provider类。
OpenPGP协议有一个机制,通过收件人公钥上的字段来显示算法支持。你应该自己提取这些字段吗?你怎么能知道哪些算法是安全的?
这正是我四年前发现自己所处的情况。Bouncy Castle OpenPGP API强大而有力,但不幸的是,它非常低级,需要你自己完成繁重的工作。如果你熟悉OpenPGP协议,这还算可以,但你可能不是专家,只是想完成工作。把所有对安全至关重要的设置工作留给API的消费者是一个坏主意。
这就是为什么我决定创建PGPainless。最初,我在寻找其他的替代品,偶然发现了一个叫做bouncy-gpg的库,但我很快就发现它不适合我的需要,于是决定走自己的路。然而,bouncy-gpg在很大程度上影响了PGPainless的开发,尤其是在早期阶段。
PGPainless在内部使用了Bouncy Castle,但通过使用构建器模式隐藏了大部分的复杂性,但抽象程度比Bouncy Castle更高。它能让你尽快完成工作,如果你没有提供具体的加密算法,它就会为你选择安全的默认值。
特别是当涉及到签名验证时,StackOverflow上的许多帖子(甚至是Bouncy Castle自己的例子!!)只是告诉你如何检查一个签名的正确性。他们并没有讨论检查签名的有效性。例如,一个签名可能在密码学上是正确的,但由于签名密钥被撤销而无效。有一整套的检查需要执行,以检查一个签名在某个参考时间是否真的有效。PGPainless为你执行这些检查。
现在我们来看看一些例子,好吗?
让我们从每个用户可能想做的第一件事开始;生成一个新的OpenPGP密钥。
Java
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing()
.modernKeyRing("Romeo <romeo@montague.lit>", "p4ssw0rd");
结果是一个受密码保护的OpenPGP密钥,它利用了现代EdDSA和XDH子密钥。该API抽象了所有烦人的东西,例如对钥匙使用哪种算法,设置哪些算法偏好(哈希算法、对称算法和压缩算法),当然还有使用绑定签名将钥匙和用户-ID绑定在一起。同时,有一个更灵活的APIPGPainless.buildKeyRing() ,允许用户根据自己的喜好改变所有这些参数。
现在让我们来签署和加密一条消息。
Java
// 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时需要做的事情相比,这是一个巨大的改进。
验证和解密是以类似的方式进行的。如果你在家里跟着做,请注意我们需要朱丽叶的密匙来解密信息,需要罗密欧的证书来验证签名。
// 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。
如果你正在为Java生态系统开发一个OpenPGP库,请考虑用它来创建一个sop-java的实现,因为这不仅可以让你的库被插入到刚好使用sop-java的项目中,还可以让你的实现被插入到Sequoia-PGP的OpenPGP互操作性测试套件中,因此你可以从大量的测试向量中受益,从而发现错误和互操作问题。
最后,这是一个自由软件项目,意味着所有的代码都在Apache 2.0许可下可用。开发是在GitHub和Codeberg上进行的。如果你觉得这个项目有用,请考虑传播这个消息并作出贡献。