文章目录
- PGP
- OpenPGP
- GPG
- 工作原理
- 工作流程
- 用途
- 案例说明
- 过程
- 代码实现
- pom依赖
- PgpEncryptionUtil
- PgpDecryptionUtil
- CommonUtils
- PgpEncryptionTest
- 小结
PGP
PGP (Pretty Good Privacy) 是一种加密通信协议,用于保护电子邮件和文件的安全性和隐私。它通过使用加密、数字签名和压缩技术来确保数据的保密性、完整性和可验证性。
GP最初由麻省理工学院的Nick embrace和Eric Hughes开发,后来由Phil Zimmermann进一步发展。它使用公钥加密和私钥解密的机制,以确保只有信息的接收者才能解密和阅读邮件内容。
PGP的主要优点是它易于使用,并能在大多数流行的电子邮件客户端中集成。然而,随着更高级的加密技术和标准(如OpenPGP和GPG)的出现,PGP已经在一定程度上被这些新标准取代。
PGP不仅仅用于电子邮件,它也可以用于加密文件和数据。
-
加密和解密: PGP 使用对称加密和非对称加密相结合的方式来实现加密和解密。发送方使用接收方的公钥对消息进行加密,接收方使用自己的私钥对消息进行解密。此外,PGP 还支持对数据进行数字签名,以确保数据的完整性和验证发送方的身份。
-
密钥管理: PGP 使用密钥对来管理加密和解密过程。每个用户都有一个公钥和一个私钥。公钥用于加密消息,私钥用于解密消息。这些密钥对可以通过密钥服务器或密钥交换方式获取。
-
数字签名: PGP 允许用户使用自己的私钥对消息进行数字签名。接收方可以使用发送方的公钥验证签名,以确保消息的完整性和发送方的身份。
-
信任模型: PGP 使用基于信任的模型来验证密钥的真实性。用户可以通过直接交换密钥、使用信任链或通过信任服务器来建立信任。
-
开放标准: PGP 是一种开放标准,意味着任何人都可以实现和使用该协议,而不受限于特定的厂商或供应商。
总的来说,PGP 是一种强大的加密协议,用于保护通信内容的机密性和完整性,同时提供身份验证机制。它广泛用于电子邮件和文件加密,以确保用户的数据安全和隐私。
OpenPGP和GPG(GNU Privacy Guard)是PGP(Pretty Good Privacy)的开放标准和自由软件实现。 随着时间的推移,PGP的标准和实现逐渐演进,OpenPGP和GPG就是其中的两个重要发展。
OpenPGP
OpenPGP是一个开放标准,它定义了一种用于加密和数字签名数据的协议。这个标准允许不同的加密软件相互兼容,这意味着使用不同OpenPGP实现的用户可以安全地交换加密信息。OpenPGP标准是由RFC 4880定义的,它包括了公钥和私钥的生成、交换和验证方法,以及加密和签名的算法。
GPG
GPG是OpenPGP的一个流行实现,它是GNU项目的一部分,由GNU通用公共许可证(GPL)发布。GPG是一个命令行工具,可以在多种操作系统中运行,包括Linux、macOS和Windows。GPG提供了创建和验证数字签名、加密文件和电子邮件以及安全地交换密钥等功能。
GPG的核心组件包括:
- keyring:用于存储公钥和私钥。
- gpg:命令行工具,用于执行加密、解密、签名和验证等操作。
- gpgconf:用于配置GPG的命令行工具。
- gpg-agent:一个守护进程,用于提供密钥管理、加密和服务器功能。
GPG的使用场景包括:
- 安全地交换电子邮件和文件。
- 验证软件的完整性和来源。
- 保护个人隐私和商业机密。
工作原理
PGP(Pretty Good Privacy)涉及加密、数字签名和密钥管理等关键步骤
-
密钥生成: 用户生成一对公钥和私钥。公钥用于加密消息,私钥用于解密消息和生成数字签名。
-
加密: 发送方使用接收方的公钥来加密消息。这样,只有拥有相应私钥的接收方才能解密消息。
-
数字签名: 发送方使用自己的私钥对消息进行签名。接收方使用发送方的公钥验证签名,确保消息的完整性和发送方的身份。
-
密钥管理: 用户可以通过密钥服务器或直接交换密钥的方式来管理和共享公钥。
工作流程
-
密钥交换: 发送方和接收方需要交换公钥。这可以通过密钥服务器、直接交换或其他安全渠道完成。
-
加密消息:
- 发送方选择要发送的消息,并使用接收方的公钥对消息进行加密。
- 发送方可以选择使用对称加密算法来加密消息内容,然后再使用接收方的公钥来加密对称密钥,这样可以提高效率。
- 发送方发送加密后的消息给接收方。
-
解密消息:
- 接收方使用自己的私钥解密接收到的消息。
- 如果消息有数字签名,接收方使用发送方的公钥验证签名。
-
数字签名验证:
- 接收方使用发送方的公钥验证数字签名,确保消息的完整性和发送方的身份。
-
信任管理:
- 用户可以建立信任关系,以确保使用其他用户的公钥时其真实性。
- 信任关系可以通过直接交换密钥、信任链或信任服务器来建立。
总的来说,PGP的工作原理涉及加密、数字签名和密钥管理,通过这些步骤保证了消息的机密性、完整性和可验证性。
用途
PGP 本质上有三个主要用途:
- 发送和接收加密电子邮件。
- 验证向你发送消息的人的身份。
- 加密文件。
案例说明
假设Alice和Bob是两个使用PGP加密通信的用户。他们希望通过电子邮件进行安全通信,以保护其消息的机密性和完整性。
过程
-
密钥生成:
- Alice 和 Bob 分别生成一对公钥和私钥。
-
密钥交换:
- Alice 将她的公钥发送给 Bob,而 Bob 也将他的公钥发送给 Alice。这可以通过安全的电子邮件或其他安全通道完成。
-
加密消息:
- Alice 决定向 Bob 发送一封加密的电子邮件。
- Alice 使用 Bob 的公钥将邮件内容进行加密。
- Alice 还可以选择使用对称加密算法来加密邮件内容,然后再使用 Bob 的公钥来加密对称密钥,以提高效率。
-
解密消息:
- Bob 收到 Alice 发送的加密邮件后,使用自己的私钥解密邮件内容。
- 如果邮件有数字签名,Bob 使用 Alice 的公钥验证签名,确保邮件的完整性和 Alice 的身份。
-
数字签名验证:
- 如果 Alice 在邮件中添加了数字签名,Bob 使用 Alice 的公钥验证签名,以确保邮件的完整性和 Alice 的身份。
-
信任管理:
- Alice 和 Bob 可能通过直接交换公钥或使用信任服务器来建立信任关系,以确保对方公钥的真实性。
在这个案例中,Alice 和 Bob 使用PGP协议加密和解密他们之间的通信,同时还可以使用数字签名来确保消息的完整性和验证发送方的身份。通过这种方式,他们可以安全地交换信息,而不用担心被未经授权的第三方窃取或篡改。
代码实现
在Java中完全实现PGP协议需要使用第三方库,因为PGP是一个复杂的加密协议。常用的库之一是Bouncy Castle.
pom依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.artisan</groupId>
<artifactId>pgp-encryption</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>8</source>
<target>8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpg-jdk15on</artifactId>
<version>1.70</version>
<scope>compile</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk15on -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.70</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>
</project>
PgpEncryptionUtil
package com.artisan.pgpUtils;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Objects;
/**
* @author artisan
*/
@Getter
@Builder
@AllArgsConstructor
public class PgpEncryptionUtil {
/**
* 将Bouncy Castle添加到JVM中
*/
static {
// 将Bouncy Castle添加到JVM中
if (Objects.isNull(Security.getProvider(BouncyCastleProvider.PROVIDER_NAME))) {
Security.addProvider(new BouncyCastleProvider());
}
}
/**
* 压缩算法,默认为ZIP
*/
@Builder.Default
private int compressionAlgorithm = CompressionAlgorithmTags.ZIP;
/**
* 对称密钥算法,默认为AES-128
*/
@Builder.Default
private int symmetricKeyAlgorithm = SymmetricKeyAlgorithmTags.AES_128;
/**
* 是否启用ASCII Armor,默认为true
*/
@Builder.Default
private boolean armor = true;
/**
* 是否启用完整性检查,默认为true
*/
@Builder.Default
private boolean withIntegrityCheck = true;
/**
* 缓冲区大小,默认为65536字节
*/
@Builder.Default
private int bufferSize = 1 << 16;
/**
* 加密方法,将明文数据使用公钥进行加密
*
* @param encryptOut
* @param clearIn
* @param length
* @param publicKeyIn
* @throws IOException
* @throws PGPException
*/
public void encrypt(OutputStream encryptOut, InputStream clearIn, long length, InputStream publicKeyIn)
throws IOException, PGPException {
// 创建压缩数据生成器
PGPCompressedDataGenerator compressedDataGenerator =
new PGPCompressedDataGenerator(compressionAlgorithm);
// 创建PGP加密数据生成器
PGPEncryptedDataGenerator pgpEncryptedDataGenerator = new PGPEncryptedDataGenerator(
// 配置加密数据生成器
new JcePGPDataEncryptorBuilder(symmetricKeyAlgorithm)
.setWithIntegrityPacket(withIntegrityCheck)
.setSecureRandom(new SecureRandom())
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
);
// 添加公钥
pgpEncryptedDataGenerator.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(
CommonUtils.getPublicKey(publicKeyIn)));
// 如果启用ASCII Armor,则创建ArmoredOutputStream
if (armor) {
encryptOut = new ArmoredOutputStream(encryptOut);
}
// 打开加密输出流
OutputStream cipherOutStream = pgpEncryptedDataGenerator.open(encryptOut, new byte[bufferSize]);
// 将压缩数据生成器打开,并将明文数据以字面形式复制到加密输出流中
CommonUtils.copyAsLiteralData(compressedDataGenerator.open(cipherOutStream), clearIn, length, bufferSize);
// 依次关闭所有输出流
compressedDataGenerator.close();
cipherOutStream.close();
encryptOut.close();
}
/**
* 加密方法,返回加密后的字节数组
*
* @param clearData
* @param pubicKeyIn
* @return
* @throws PGPException
* @throws IOException
*/
public byte[] encrypt(byte[] clearData, InputStream pubicKeyIn) throws PGPException, IOException {
ByteArrayInputStream inputStream = new ByteArrayInputStream(clearData);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
encrypt(outputStream, inputStream, clearData.length, pubicKeyIn);
return outputStream.toByteArray();
}
// 加密方法,返回加密后的输入流
public InputStream encrypt(InputStream clearIn, long length, InputStream publicKeyIn)
throws IOException, PGPException {
File tempFile = File.createTempFile("pgp-", "-encrypted");
encrypt(Files.newOutputStream(tempFile.toPath()), clearIn, length, publicKeyIn);
return Files.newInputStream(tempFile.toPath());
}
/**
* 加密方法,将明文数据使用公钥字符串进行加密
*
* @param clearData
* @param publicKeyStr
* @return
* @throws PGPException
* @throws IOException
*/
public byte[] encrypt(byte[] clearData, String publicKeyStr) throws PGPException, IOException {
return encrypt(clearData, IOUtils.toInputStream(publicKeyStr, Charset.defaultCharset()));
}
/**
* 加密方法,返回加密后的输入流
*
* @param clearIn
* @param length
* @param publicKeyStr
* @return
* @throws IOException
* @throws PGPException
*/
public InputStream encrypt(InputStream clearIn, long length, String publicKeyStr) throws IOException, PGPException {
return encrypt(clearIn, length, IOUtils.toInputStream(publicKeyStr, Charset.defaultCharset()));
}
}
PgpDecryptionUtil
package com.artisan.pgpUtils;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.security.Security;
import java.util.Iterator;
import java.util.Objects;
/**
* @author artisan
*/
public class PgpDecryptionUtil {
static {
// 将Bouncy Castle添加到JVM中
if (Objects.isNull(Security.getProvider(BouncyCastleProvider.PROVIDER_NAME))) {
Security.addProvider(new BouncyCastleProvider());
}
}
/**
* 私钥密码
*/
private final char[] passCode;
/**
* 密钥环集合
*/
private final PGPSecretKeyRingCollection pgpSecretKeyRingCollection;
/**
* @param privateKeyIn
* @param passCode
* @throws IOException
* @throws PGPException
*/
public PgpDecryptionUtil(InputStream privateKeyIn, String passCode) throws IOException, PGPException {
this.passCode = passCode.toCharArray(); // 将密码转换为字符数组
// 从输入流中读取PGP密钥环集合
this.pgpSecretKeyRingCollection = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(privateKeyIn)
, new JcaKeyFingerprintCalculator());
}
/**
* @param privateKeyStr
* @param passCode
* @throws IOException
* @throws PGPException
*/
public PgpDecryptionUtil(String privateKeyStr, String passCode) throws IOException, PGPException {
// 将私钥字符串转换为输入流
this(IOUtils.toInputStream(privateKeyStr, Charset.defaultCharset()), passCode);
}
/**
* 查找指定ID的私钥
*
* @param keyID
* @return
* @throws PGPException
*/
private PGPPrivateKey findSecretKey(long keyID) throws PGPException {
// 从密钥环中获取指定ID的私钥
PGPSecretKey pgpSecretKey = pgpSecretKeyRingCollection.getSecretKey(keyID);
// 使用密码解密私钥
return pgpSecretKey == null ? null : pgpSecretKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder()
.setProvider(BouncyCastleProvider.PROVIDER_NAME).build(passCode));
}
/**
* 解密方法,将加密输入流解密为明文输出流
*
* @param encryptedIn
* @param clearOut
* @throws PGPException
* @throws IOException
*/
public void decrypt(InputStream encryptedIn, OutputStream clearOut)
throws PGPException, IOException {
// 去除ASCII Armor并返回底层的二进制加密流
encryptedIn = PGPUtil.getDecoderStream(encryptedIn);
JcaPGPObjectFactory pgpObjectFactory = new JcaPGPObjectFactory(encryptedIn);
Object obj = pgpObjectFactory.nextObject();
// 第一个对象可能是标记数据包
PGPEncryptedDataList pgpEncryptedDataList = (obj instanceof PGPEncryptedDataList)
? (PGPEncryptedDataList) obj : (PGPEncryptedDataList) pgpObjectFactory.nextObject();
PGPPrivateKey pgpPrivateKey = null;
PGPPublicKeyEncryptedData publicKeyEncryptedData = null;
Iterator<PGPEncryptedData> encryptedDataItr = pgpEncryptedDataList.getEncryptedDataObjects();
while (pgpPrivateKey == null && encryptedDataItr.hasNext()) {
publicKeyEncryptedData = (PGPPublicKeyEncryptedData) encryptedDataItr.next();
pgpPrivateKey = findSecretKey(publicKeyEncryptedData.getKeyID()); // 查找私钥
}
if (Objects.isNull(publicKeyEncryptedData)) {
throw new PGPException("无法生成PGPPublicKeyEncryptedData对象");
}
if (pgpPrivateKey == null) {
throw new PGPException("无法提取私钥");
}
CommonUtils.decrypt(clearOut, pgpPrivateKey, publicKeyEncryptedData); // 调用通用解密方法
}
/**
* 解密方法,将加密字节数组解密为明文字节数组
*
* @param encryptedBytes
* @return
* @throws PGPException
* @throws IOException
*/
public byte[] decrypt(byte[] encryptedBytes) throws PGPException, IOException {
ByteArrayInputStream encryptedIn = new ByteArrayInputStream(encryptedBytes);
ByteArrayOutputStream clearOut = new ByteArrayOutputStream();
// 调用解密方法
decrypt(encryptedIn, clearOut);
// 将解密后的明文字节数组返回
return clearOut.toByteArray();
}
}
CommonUtils
package com.artisan.pgpUtils;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Date;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Optional;
/**
* @author artisan
*/
public class CommonUtils {
/**
* 使用提供的私钥解密公钥加密数据,并将其写入输出流
*
* @param clearOut 要写入数据的输出流
* @param pgpPrivateKey 私钥实例
* @param publicKeyEncryptedData 公钥加密数据实例
* @throws IOException IO相关错误
* @throws PGPException PGP相关错误
*/
static void decrypt(OutputStream clearOut, PGPPrivateKey pgpPrivateKey, PGPPublicKeyEncryptedData publicKeyEncryptedData) throws IOException, PGPException {
PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder()
.setProvider(BouncyCastleProvider.PROVIDER_NAME).build(pgpPrivateKey);
InputStream decryptedCompressedIn = publicKeyEncryptedData.getDataStream(decryptorFactory);
JcaPGPObjectFactory decCompObjFac = new JcaPGPObjectFactory(decryptedCompressedIn);
PGPCompressedData pgpCompressedData = (PGPCompressedData) decCompObjFac.nextObject();
InputStream compressedDataStream = new BufferedInputStream(pgpCompressedData.getDataStream());
JcaPGPObjectFactory pgpCompObjFac = new JcaPGPObjectFactory(compressedDataStream);
Object message = pgpCompObjFac.nextObject();
if (message instanceof PGPLiteralData) {
PGPLiteralData pgpLiteralData = (PGPLiteralData) message;
InputStream decDataStream = pgpLiteralData.getInputStream();
IOUtils.copy(decDataStream, clearOut);
clearOut.close();
} else if (message instanceof PGPOnePassSignatureList) {
throw new PGPException("加密消息包含签名消息而不是文字数据");
} else {
throw new PGPException("消息不是简单加密文件 - 类型未知");
}
// 执行完整性检查
if (publicKeyEncryptedData.isIntegrityProtected()) {
if (!publicKeyEncryptedData.verify()) {
throw new PGPException("消息未通过完整性检查");
}
}
}
/**
* 将输入流中的数据复制到pgp文字数据,并写入提供的输出流
*
* @param outputStream 输出流,要写入其中的数据
* @param in 要读取数据的输入流
* @param length 要读取的数据长度
* @param bufferSize 缓冲区大小,因为使用缓冲区加速复制
* @throws IOException IO相关错误
*/
static void copyAsLiteralData(OutputStream outputStream, InputStream in, long length, int bufferSize) throws IOException {
PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
OutputStream pOut = lData.open(outputStream, PGPLiteralData.BINARY, PGPLiteralData.CONSOLE,
Date.from(LocalDateTime.now().toInstant(ZoneOffset.UTC)), new byte[bufferSize]);
byte[] buff = new byte[bufferSize];
try {
int len;
long totalBytesWritten = 0L;
while (totalBytesWritten <= length && (len = in.read(buff)) > 0) {
pOut.write(buff, 0, len);
totalBytesWritten += len;
}
pOut.close();
} finally {
// 清除缓冲区
Arrays.fill(buff, (byte) 0);
// 关闭输入流
in.close();
}
}
/**
* 从密钥输入流获取公钥
*
* @param keyInputStream 密钥输入流
* @return PGPPublicKey实例
* @throws IOException IO相关错误
* @throws PGPException PGP相关错误
*/
static PGPPublicKey getPublicKey(InputStream keyInputStream) throws IOException, PGPException {
PGPPublicKeyRingCollection pgpPublicKeyRings = new PGPPublicKeyRingCollection(
PGPUtil.getDecoderStream(keyInputStream), new JcaKeyFingerprintCalculator());
Iterator<PGPPublicKeyRing> keyRingIterator = pgpPublicKeyRings.getKeyRings();
while (keyRingIterator.hasNext()) {
PGPPublicKeyRing pgpPublicKeyRing = keyRingIterator.next();
Optional<PGPPublicKey> pgpPublicKey = extractPGPKeyFromRing(pgpPublicKeyRing);
if (pgpPublicKey.isPresent()) {
return pgpPublicKey.get();
}
}
throw new PGPException("无效的公钥");
}
private static Optional<PGPPublicKey> extractPGPKeyFromRing(PGPPublicKeyRing pgpPublicKeyRing) {
for (PGPPublicKey publicKey : pgpPublicKeyRing) {
if (publicKey.isEncryptionKey()) {
return Optional.of(publicKey);
}
}
return Optional.empty();
}
}
PgpEncryptionTest
package com.artisan.pgpUtils;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.openpgp.PGPException;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.Optional;
import static org.junit.Assert.assertEquals;
public class PgpEncryptionTest {
public static final TemporaryFolder tempFolder = new TemporaryFolder();
private PgpEncryptionUtil pgpEncryptionUtil = null;
private PgpDecryptionUtil pgpDecryptionUtil = null;
/**
* 加载资源文件的辅助方法
* @param resourcePath
* @return
*/
private static URL loadResource(String resourcePath) {
return Optional.ofNullable(PgpEncryptionTest.class.getResource(resourcePath))
.orElseThrow(() -> new IllegalArgumentException("Resource not found"));
}
private static final String passkey = "dummy";
private final URL privateKey = loadResource("/private.pgp");
private final URL publicKey = loadResource("/public.pgp");
private final URL testFile = loadResource("/Sample_CSV_5300kb.csv");
private static final String testString = "This text needs to be PGP encrypted";
/**
* 在测试类运行之前创建临时文件夹
* @throws IOException
*/
@BeforeClass
public static void construct() throws IOException {
tempFolder.delete();
tempFolder.create();
}
/**
* 在测试类运行之后清理临时文件夹
*/
@AfterClass
public static void destroy() {
tempFolder.delete();
}
/**
* 初始化方法,在每个测试方法运行之前执行
*/
@Before
public void init() {
// 初始化加密工具类
pgpEncryptionUtil = PgpEncryptionUtil.builder()
.armor(true)
.compressionAlgorithm(CompressionAlgorithmTags.ZIP)
.symmetricKeyAlgorithm(SymmetricKeyAlgorithmTags.AES_128)
.withIntegrityCheck(true)
.build();
try {
// 初始化解密工具类
pgpDecryptionUtil = new PgpDecryptionUtil(privateKey.openStream(), passkey);
} catch (IOException | PGPException e) {
throw new RuntimeException(e);
}
}
/**
* 测试字节数组加密
* @throws IOException
* @throws PGPException
*/
@Test
public void testByteEncryption() throws IOException, PGPException {
// 加密测试字节数组
byte[] encryptedBytes = pgpEncryptionUtil.encrypt(testString.getBytes(Charset.defaultCharset()),
publicKey.openStream());
// 解密生成的加密字节数组
byte[] decryptedBytes = pgpDecryptionUtil.decrypt(encryptedBytes);
// 将解密后的字节数组转换为字符串,并与原始测试字符串进行比较
assertEquals(testString, new String(decryptedBytes, Charset.defaultCharset()));
}
/**
* 测试文件加密
* @throws IOException
* @throws URISyntaxException
* @throws PGPException
*/
@Test
public void testFileEncryption() throws IOException, URISyntaxException, PGPException {
// 生成一个从测试文件生成的PGP加密临时文件
File encryptedFile = tempFolder.newFile();
File originalFile = new File(testFile.toURI());
try (OutputStream fos = Files.newOutputStream(encryptedFile.toPath())) {
pgpEncryptionUtil.encrypt(fos, Files.newInputStream(originalFile.toPath()), originalFile.length(),
publicKey.openStream());
}
// 解密生成的PGP加密临时文件,并写入另一个临时文件
File decryptedFile = tempFolder.newFile();
pgpDecryptionUtil.decrypt(Files.newInputStream(encryptedFile.toPath()), Files.newOutputStream(decryptedFile.toPath()));
// 比较原始文件内容与解密后的文件内容
assertEquals(IOUtils.toString(Files.newInputStream(originalFile.toPath()), Charset.defaultCharset()),
IOUtils.toString(Files.newInputStream(decryptedFile.toPath()), Charset.defaultCharset()));
}
/**
* 测试输入流加密
* @throws IOException
* @throws URISyntaxException
* @throws PGPException
*/
@Test
public void testInputStreamEncryption() throws IOException, URISyntaxException, PGPException {
// 生成一个从测试文件生成的PGP加密输入流
File originalFile = new File(testFile.toURI());
InputStream encryptedIn = pgpEncryptionUtil.encrypt(Files.newInputStream(originalFile.toPath()), originalFile.length(), publicKey.openStream());
// 解密生成的输入流,并写入临时文件
File decryptedFile = tempFolder.newFile();
pgpDecryptionUtil.decrypt(encryptedIn, Files.newOutputStream(decryptedFile.toPath()));
// 比较原始文件内容与解密后的文件内容
assertEquals(IOUtils.toString(Files.newInputStream(originalFile.toPath()), Charset.defaultCharset()),
IOUtils.toString(Files.newInputStream(decryptedFile.toPath()), Charset.defaultCharset()));
}
/**
* 使用新配置测试字节数组加密
* @throws IOException
* @throws PGPException
*/
@Test
public void testByteEncryptionWithNewConf() throws IOException, PGPException {
pgpEncryptionUtil = PgpEncryptionUtil.builder()
.armor(false)
.compressionAlgorithm(CompressionAlgorithmTags.BZIP2)
.symmetricKeyAlgorithm(SymmetricKeyAlgorithmTags.BLOWFISH)
.withIntegrityCheck(false)
.build();
// 加密测试字节数组
byte[] encryptedBytes = pgpEncryptionUtil.encrypt(testString.getBytes(Charset.defaultCharset()),
publicKey.openStream());
// 解密生成的加密字节数组
byte[] decryptedBytes = pgpDecryptionUtil.decrypt(encryptedBytes);
// 将解密后的字节数组转换为字符串,并与原始测试字符串进行比较
assertEquals(testString, new String(decryptedBytes, Charset.defaultCharset()));
}
}
小结
当我们在互联网上发送电子邮件或文件时,我们希望它们的内容能够保密,并且我们希望确认发送方的身份和数据的完整性。这就是PGP(Pretty Good Privacy)的作用。
想象一下,你有一把钥匙。这把钥匙有两个部分:一个是公钥,一个是私钥。
-
公钥:就像你家门口的邮箱钥匙一样,你可以把它给任何人。任何人都可以用你的公钥锁住一份文件,但只有你才能用你的私钥打开它。
-
私钥:就像你的家里的钥匙一样,只有你有它。你用它来打开那些别人用你的公钥锁住的文件。
当你想给某人发送私密信息时,你会使用他们的公钥来加密消息。然后,只有他们可以使用自己的私钥来解密消息。这样,即使在传输过程中,即使有人截获了消息,他们也无法阅读它,因为他们没有私钥。
此外,PGP还可以用于数字签名。就像在一封信上签名一样,数字签名证明了发送方的身份和消息的完整性。发送方使用自己的私钥对消息进行签名,然后接收方使用发送方的公钥来验证签名,确保消息没有被篡改,并且是来自于发送方的。
总而言之,PGP是一种用于保护电子邮件和文件安全的加密技术,它通过使用公钥和私钥来加密和解密消息,并通过数字签名来验证消息的来源和完整性。