首先,我们了解PGP 密钥对。其次,也是最重要的,我们了解使用 BouncyCastle PGP 实现对文件进行加密和解密。
对于软件应用程序而言,安全性至关重要,因为加密和解密敏感数据和个人数据是基本要求。所有这些加密 API 都作为 JCA/JCE 的一部分包含在 JDK 中,其他 API 则来自第三方库,例如BouncyCastle。
在本教程中,我们将了解 PGP 的基础知识以及如何生成 PGP 密钥对。此外,我们还将学习使用 BouncyCastle API 在 Java 中进行 PGP 加密和解密。
使用 BouncyCastle 的 PGP 加密
PGP(Pretty Good Privacy)加密是一种保密数据的方法,目前只有少数 OpenPGP Java 实现可用,例如 BouncyCastle、IPWorks、OpenPGP 和 OpenKeychain API。如今,当我们谈论 PGP 时,我们几乎总是提到 OpenPGP。
PGP 使用两个密钥:
- 收件人的公钥用于加密消息。
- 收件人的私钥用于解密消息。
如果 A 希望向 B 发送加密消息,则 A 使用 B 的公钥通过 BouncyCastle PGP 加密该消息并将其发送给 B。之后,B 使用其私钥解密并阅读该消息。
BouncyCastle 是一个实现 PGP 加密的 Java 库。
项目设置和依赖项
在开始加密和解密过程之前,让我们使用必要的依赖项设置我们的 Java 项目并创建我们稍后需要的 PGP 密钥对。
BouncyCastle 的 Maven 依赖
首先,让我们创建一个简单的 Java Maven项目并添加 BouncyCastle 依赖项。
我们将添加bcprov-jdk15on,它包含 JCE 提供程序和适用于 JDK 1.5 及更高版本的 BouncyCastle 加密 API 的轻量级 API。此外,我们还将添加bcpg-jdk15on,它是用于处理 OpenPGP 协议的 BouncyCastle Java API,包含适用于 JDK 1.5 及更高版本的 OpenPGP API:
<dependency> |
安装 GPG 工具
我们将使用GnuPG (GPG)工具生成 ASCII ( .asc ) 格式的 PGP 密钥对。
如果我们还没有在系统上安装 GPG,那么首先安装它:
$ sudo apt install gnupg
生成 PGP 密钥对
在我们进行加密和解密之前,让我们首先创建一个 PGP 密钥对。
首先,我们将运行命令来生成密钥对:
$ gpg --full-generate-key
接下来我们需要按照提示选择密钥类型、密钥大小以及有效期。
例如,我们选择 RSA 作为密钥类型、2048 作为密钥大小以及有效期为2 年。
接下来,我们输入我们的姓名和电子邮件地址:
Real name: baeldung |
我们需要设置密码来保护密钥,并确保其强大且唯一。使用密码对于 PGP 加密并非严格强制要求,但出于安全原因,强烈建议使用密码。生成 PGP 密钥对时,我们可以选择设置密码来保护我们的私钥,从而增加额外的安全层。
如果攻击者掌握了我们的私钥,设置强密码可确保攻击者在不知道密码的情况下无法使用它。
根据 GPG 工具的提示,我们来创建密码。在本例中,我们选择baeldung作为密码。
以 ASCII 格式导出密钥
最后,一旦生成密钥,我们使用以下命令以 ASCII 格式将其导出:
$ gpg --armor --export <our_email_address> > public_key.asc |
这将创建一个名为public_key.asc的文件,其中包含 ASCII 格式的公钥。
以同样的方式,我们将导出私钥:
$ gpg --armor --export-secret-key <our_email_address> > private_key.asc |
现在我们得到了一个 ASCII 格式的 PGP 密钥对,由一个公钥public_key.asc和一个私钥private_key.asc组成。
PGP 加密
在我们的示例中,我们将有一个包含纯文本消息的文件。我们将使用公共 PGP 密钥加密此文件,并创建一个包含加密消息的文件。
我们参考了 BouncyCastle 示例来进行PGP 实现。
首先,让我们创建一个简单的 Java 类并添加一个encrypt()方法:
public static void encryptFile(String outputFileName, String inputFileName, String pubKeyFileName, boolean armor, boolean withIntegrityCheck) |
这里,outputFileName是输出文件的名称,该文件将包含加密格式的消息。
另外,inputFileName是包含纯文本消息的输入文件的名称,publicKeyFileName是公钥文件名的名称。
在这里,如果armor设置为true,我们将使用ArmoredOutputStream,它使用类似于Base64 的编码,以便将二进制不可打印字节转换为文本友好的内容。
此外,withIntegrityCheck指定生成的加密数据是否受完整性包的保护。
接下来,我们将打开输出文件的流:
OutputStream out = new BufferedOutputStream(new FileOutputStream(outputFileName)); |
现在,让我们读取公钥:
InputStream publicKeyInputStream = new BufferedInputStream(new FileInputStream(pubKeyFileName)); |
接下来,我们将使用PGPPublicKeyRingCollection类来管理和使用 PGP 应用程序中的公钥环,从而允许我们加载、搜索和使用公钥进行加密。
PGP 中的公钥环是一组公钥,每个公钥都与一个用户 ID(例如电子邮件地址)相关联。公钥环中可以包含许多公钥,从而使用户可以拥有多个身份或密钥对。
我们将打开一个密钥环文件并加载第一个适合加密的可用密钥:
PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(publicKeyInputStream), new JcaKeyFingerprintCalculator()); |
接下来我们压缩这个文件并得到一个字节数组:
ByteArrayOutputStream bOut = new ByteArrayOutputStream(); |
此外,我们将创建一个 BouncyCastle PGPEncryptDataGenerator类,用于流出和写入数据:
PGPDataEncryptorBuilder encryptorBuilder = new JcePGPDataEncryptorBuilder(PGPEncryptedData.CAST5).setProvider("BC") |
最后,让我们运行程序,看看我们的输出文件是否以我们的文件名创建,以及内容是否如下所示:
-----BEGIN PGP MESSAGE----- |
PGP 解密
作为解密的一部分,我们将使用收件人的私钥解密上一步中创建的文件。
首先,我们将创建一个decrypt()方法:
public static void decryptFile(String encryptedInputFileName, String privateKeyFileName, char[] passphrase, String defaultFileName) |
这里,参数inputFileName是需要解密的文件名。
接下来,privateKeyFileName是私钥的文件名,passphrase是在生成密钥对时选择的秘密密码。
此外,defaultFileName是解密文件的默认名称。
让我们在输入文件和私钥文件上打开一个输入流:
InputStream in = new BufferedInputStream(new FileInputStream(inputFileName)); |
然后,让我们创建一个解密流,并将 BouncyCastle 的PGPObjectFactory用于OutputStream:
JcaPGPObjectFactory pgpF = new JcaPGPObjectFactory(in); |
此外,我们将使用PGPSecretKeyRingCollection来加载、查找和利用密钥进行解密。接下来,我们将从文件中加载密钥:
Iterator it = enc.getEncryptedDataObjects(); |
现在,一旦我们获得了私钥,我们将使用集合中的私钥来解密加密的数据或消息:
InputStream clear = pbe.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider("BC") |
最后,我们将使用PGPPublicKeyEncryptedData的isIntegrityProtected()和verify()方法来验证数据包的完整性:
if (pbe.isIntegrityProtected() && pbe.verify()) { |
之后,让我们运行程序来查看输出文件是否以我们的文件名创建,以及内容是否是纯文本:
//In our example, decrypted file name is defaultFileName and the msg is: |