加密与安全_PGP、OpenPGP和GPG加密通信协议
文章目录
- 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.Defaultprivate int compressionAlgorithm = CompressionAlgorithmTags.ZIP;/*** 对称密钥算法,默认为AES-128*/@Builder.Defaultprivate int symmetricKeyAlgorithm = SymmetricKeyAlgorithmTags.AES_128;/*** 是否启用ASCII Armor,默认为true*/@Builder.Defaultprivate boolean armor = true;/*** 是否启用完整性检查,默认为true*/@Builder.Defaultprivate boolean withIntegrityCheck = true;/*** 缓冲区大小,默认为65536字节*/@Builder.Defaultprivate 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,则创建ArmoredOutputStreamif (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*/@BeforeClasspublic static void construct() throws IOException {tempFolder.delete();tempFolder.create();}/*** 在测试类运行之后清理临时文件夹*/@AfterClasspublic static void destroy() {tempFolder.delete();}/*** 初始化方法,在每个测试方法运行之前执行*/@Beforepublic 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*/@Testpublic 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*/@Testpublic 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*/@Testpublic 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*/@Testpublic 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是一种用于保护电子邮件和文件安全的加密技术,它通过使用公钥和私钥来加密和解密消息,并通过数字签名来验证消息的来源和完整性。
相关文章:

加密与安全_PGP、OpenPGP和GPG加密通信协议
文章目录 PGPOpenPGPGPG工作原理工作流程用途案例说明过程 代码实现pom依赖PgpEncryptionUtilPgpDecryptionUtilCommonUtilsPgpEncryptionTest 小结 PGP PGP (Pretty Good Privacy) 是一种加密通信协议,用于保护电子邮件和文件的安全性和隐私。它通过使用加密、数字…...
Maven模块化最佳实践
一,模块化的原因及意义 模块化是一种将大型的软件系统拆分成相互独立的模块的方法。具有以下优势: 代码复用:不同的模块可以共享相同的代码。这样可以避免重复编写相同的代码,提高开发效率。 模块独立性:每个模块都可…...

嵌入式C语言中 #pragma once 的作用
1、#pragma once有什么作用? 为了避免同一个头文件被包含(include)多次,C/C中有两种宏实现方式: 一种是#ifndef方式; 另一种是#pragma once方式。 在能够支持这两种方式的编译器上,二者并没…...

spring-cloud-openfeign 3.0.0(对应spring boot 2.4.x之前版本)之前版本feign整合ribbon请求流程
在之前写的文章配置基础上 https://blog.csdn.net/zlpzlpzyd/article/details/136060312 下图为自己整理的...

#数据结构 线性表的顺序存储
目录 每日文案 一、线性表的定义 二、线性表的操作 顺序表的存储结构 顺序表的初始化操作 判断顺序表是否为空表 将顺序表置为空表 计算顺序表中的元素个数 取出顺序表中的对应位置元素 取出对应数值的位序 在对应位置插入元素 将对应位置的元素删除 将顺序表中的数据…...

[iOS]高版本MacOS运行低版本Xcode
Xcode 版本支持文档 目的: 在MacOS Sonoma 系统上安装 Xcode14.3.1 第一步 先在Xcode下载一个Xcode14.3.1的压缩包 第二步 本地解压Xcode,将外层目录名变更为Xcode_14.3.1,将文件拷贝到 /Applications目录下。 第三步 变更xcode-sel…...

仿牛客项目Day5:开发登录、退出功能
登录功能 数据库 创建了一个表login_ticket来记录登录凭证,类似于session 核心字段是ticket entity 创建了一个类loginTicket mapper 处理login_ticket的mapper接口层,用来往里面查询数据、增加数据和修改数据 查询数据通过ticket来查 select是通…...

Vue3全家桶 - Vue3 - 【3】模板语法(指令+修饰符 + v-model语法糖)
一、模板语法 主要还是记录一些指令的使用和vue2的区别;vue3指令导航; 1.1 v-text 和 v-html 指令的区别: v-text: 更新元素的文本内容;v-text 通过设置元素的 textContent 属性来工作,因此它将覆盖元素…...

OpenCV开发笔记(七十七):相机标定(二):通过棋盘标定计算相机内参矩阵矫正畸变摄像头图像
若该文为原创文章,转载请注明原文出处 本文章博客地址:https://hpzwl.blog.csdn.net/article/details/136616551 各位读者,知识无穷而人力有穷,要么改需求,要么找专业人士,要么自己研究 红胖子(红模仿)的博…...

2024蓝桥杯每日一题(时间日期)
一、第一题:日期差值 解题思路:模拟 写一个计算时间的板子两者相减 【Python程序代码】 mon [0,31,28,31,30,31,30,31,31,30,31,30,31] def pd(x):if x%4000 or (x%40 and x%100!0):return Truereturn False def get_day(y,m,d):res 0for i …...
js【详解】事件
给 DOM 节点绑定事件 推荐使用 addEventListener 函数 第一个参数:事件名称第二个参数:事件处理函数(第一个参数为 event)第三个参数: true 采用捕获法来处理事件false 【推荐】采用冒泡法来处理事件 let div1 docu…...
webpack5基础--14_优化css
Css 处理 提取 Css 成单独文件 Css 文件目前被打包到 js 文件中,当 js 文件加载时,会创建一个 style 标签来生成样式 这样对于网站来说,会出现闪屏现象,用户体验不好 我们应该是单独的 Css 文件,通过 link 标签加载…...

Skywalking(9.7.0) 告警配置
图片被吞,来这里看吧:https://juejin.cn/post/7344567669893021736 过年前一天发版,大家高高兴兴准备回家过年去了。这时候老板说了一句,记得带上电脑,关注用户反馈。有紧急问题在高速上都得给我找个服务区改好。 但是…...
删除、创建、验证Kafka安装自带的__consumer_offsets topic
删除Kafka自带Topic 一般情况下,你删除Kafka自带的__consumer_offsets topic,会报错提示不能删除。 倔强的你直接找到zookeeper删掉了它,list查看确实没有这个topic了,但是这会导致消费者和偏移量无法记录。 创建Kafka自带的Topi…...

在文件夹下快速创建vue项目搭建vue框架详细步骤
一、首先在你的电脑目录下新建一个文件夹 进入该文件夹并打开控制台(输入cmd指令) 进入控制台后输入 vue create springboot_vue (自己指定名称) 如果出现这类报错如:npm install 的报错npm ERR! network request to http://registry.cnp…...

蓝桥杯倒计时 36天-DFS练习
文章目录 飞机降落仙境诅咒小怂爱水洼串变换 飞机降落 思路:贪心暴搜。 #include<bits/stdc.h>using namespace std; const int N 10; int t,n; //这题 N 比较小,可以用暴力搜搜复杂度是 TN*N! struct plane{int t,d,l; }p[N]; bool vis[N];//用…...

ctfshow web入门 php特性总结
1.web89 intval函数的利用,intval函数获取变量的整数值,失败时返回0,空的数组返回,非空数组返回1 num[]1 intval ( mixed $var [, int $base 10 ] ) : int Note: 如果 base 是 0,通过检测 var 的格式来决定使用的进…...

Media Encoder 2024:未来媒体编码的新纪元 mac/win版
随着科技的飞速发展,媒体内容已成为我们日常生活中不可或缺的一部分。为了满足用户对高质量视频内容不断增长的需求,Media Encoder 2024应运而生,它凭借卓越的技术和创新的特性,重塑了媒体编码的未来。 Media Encoder 2024 mac/w…...
2024年AI辅助研发趋势:数智时代革新新引擎
随着科技的飞速发展,人工智能(AI)已经渗透到我们生活的方方面面,而在软件开发领域,AI辅助研发正成为一股不可忽视的力量。本文将探讨2024年AI辅助研发的趋势,以及它如何成为数智时代革新的新引擎。 AI辅助研…...

2024年家政预约上门服务小程序【用户端+商家端+师傅端】源码
024最新家政预约上门服务小程序源码 主要功能:商家入住,师傅入住,缴纳保正金 支持师傅,抢单派单 支持多城市多门下单,支持预约上门服务到店核销 支持补差价义价,支持区域服务限制 基于thinkphp和原生小程序开发...
浏览器访问 AWS ECS 上部署的 Docker 容器(监听 80 端口)
✅ 一、ECS 服务配置 Dockerfile 确保监听 80 端口 EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]或 EXPOSE 80 CMD ["python3", "-m", "http.server", "80"]任务定义(Task Definition&…...

docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...

基于距离变化能量开销动态调整的WSN低功耗拓扑控制开销算法matlab仿真
目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.算法仿真参数 5.算法理论概述 6.参考文献 7.完整程序 1.程序功能描述 通过动态调整节点通信的能量开销,平衡网络负载,延长WSN生命周期。具体通过建立基于距离的能量消耗模型&am…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...

高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
【磁盘】每天掌握一个Linux命令 - iostat
目录 【磁盘】每天掌握一个Linux命令 - iostat工具概述安装方式核心功能基础用法进阶操作实战案例面试题场景生产场景 注意事项 【磁盘】每天掌握一个Linux命令 - iostat 工具概述 iostat(I/O Statistics)是Linux系统下用于监视系统输入输出设备和CPU使…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...

华硕a豆14 Air香氛版,美学与科技的馨香融合
在快节奏的现代生活中,我们渴望一个能激发创想、愉悦感官的工作与生活伙伴,它不仅是冰冷的科技工具,更能触动我们内心深处的细腻情感。正是在这样的期许下,华硕a豆14 Air香氛版翩然而至,它以一种前所未有的方式&#x…...