加密与安全_PGP、OpenPGP和GPG加密通信协议

news2024/11/16 4:18:08

文章目录

  • 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不仅仅用于电子邮件,它也可以用于加密文件和数据。

  1. 加密和解密: PGP 使用对称加密和非对称加密相结合的方式来实现加密和解密。发送方使用接收方的公钥对消息进行加密,接收方使用自己的私钥对消息进行解密。此外,PGP 还支持对数据进行数字签名,以确保数据的完整性和验证发送方的身份。

  2. 密钥管理: PGP 使用密钥对来管理加密和解密过程。每个用户都有一个公钥和一个私钥。公钥用于加密消息,私钥用于解密消息。这些密钥对可以通过密钥服务器或密钥交换方式获取。

  3. 数字签名: PGP 允许用户使用自己的私钥对消息进行数字签名。接收方可以使用发送方的公钥验证签名,以确保消息的完整性和发送方的身份。

  4. 信任模型: PGP 使用基于信任的模型来验证密钥的真实性。用户可以通过直接交换密钥、使用信任链或通过信任服务器来建立信任。

  5. 开放标准: 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)涉及加密、数字签名和密钥管理等关键步骤

在这里插入图片描述

  1. 密钥生成: 用户生成一对公钥和私钥。公钥用于加密消息,私钥用于解密消息和生成数字签名。

  2. 加密: 发送方使用接收方的公钥来加密消息。这样,只有拥有相应私钥的接收方才能解密消息。

  3. 数字签名: 发送方使用自己的私钥对消息进行签名。接收方使用发送方的公钥验证签名,确保消息的完整性和发送方的身份。

  4. 密钥管理: 用户可以通过密钥服务器或直接交换密钥的方式来管理和共享公钥。


工作流程

  1. 密钥交换: 发送方和接收方需要交换公钥。这可以通过密钥服务器、直接交换或其他安全渠道完成。

  2. 加密消息:

    • 发送方选择要发送的消息,并使用接收方的公钥对消息进行加密。
    • 发送方可以选择使用对称加密算法来加密消息内容,然后再使用接收方的公钥来加密对称密钥,这样可以提高效率。
    • 发送方发送加密后的消息给接收方。
  3. 解密消息:

    • 接收方使用自己的私钥解密接收到的消息。
    • 如果消息有数字签名,接收方使用发送方的公钥验证签名。
  4. 数字签名验证:

    • 接收方使用发送方的公钥验证数字签名,确保消息的完整性和发送方的身份。
  5. 信任管理:

    • 用户可以建立信任关系,以确保使用其他用户的公钥时其真实性。
    • 信任关系可以通过直接交换密钥、信任链或信任服务器来建立。

总的来说,PGP的工作原理涉及加密、数字签名和密钥管理,通过这些步骤保证了消息的机密性、完整性和可验证性。


用途

在这里插入图片描述

PGP 本质上有三个主要用途:

  • 发送和接收加密电子邮件。
  • 验证向你发送消息的人的身份。
  • 加密文件。

案例说明

假设Alice和Bob是两个使用PGP加密通信的用户。他们希望通过电子邮件进行安全通信,以保护其消息的机密性和完整性。

过程

  1. 密钥生成:

    • Alice 和 Bob 分别生成一对公钥和私钥。
  2. 密钥交换:

    • Alice 将她的公钥发送给 Bob,而 Bob 也将他的公钥发送给 Alice。这可以通过安全的电子邮件或其他安全通道完成。
  3. 加密消息:

    • Alice 决定向 Bob 发送一封加密的电子邮件。
    • Alice 使用 Bob 的公钥将邮件内容进行加密。
    • Alice 还可以选择使用对称加密算法来加密邮件内容,然后再使用 Bob 的公钥来加密对称密钥,以提高效率。
  4. 解密消息:

    • Bob 收到 Alice 发送的加密邮件后,使用自己的私钥解密邮件内容。
    • 如果邮件有数字签名,Bob 使用 Alice 的公钥验证签名,确保邮件的完整性和 Alice 的身份。
  5. 数字签名验证:

    • 如果 Alice 在邮件中添加了数字签名,Bob 使用 Alice 的公钥验证签名,以确保邮件的完整性和 Alice 的身份。
  6. 信任管理:

    • 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是一种用于保护电子邮件和文件安全的加密技术,它通过使用公钥和私钥来加密和解密消息,并通过数字签名来验证消息的来源和完整性。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1509892.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

福派斯狗粮质量怎么样?

亲爱的宠物家长们&#xff0c;你们是不是也在为选择一款高质量的狗粮而犯愁呢&#xff1f;今天&#xff0c;我要和大家分享一下福派斯狗粮的质量问题&#xff0c;希望能够为你们提供一些参考。 &#x1f43e; 首先&#xff0c;让我们来聊聊福派斯狗粮的原材料。福派斯狗粮选用的…

机器学习模型—支持向量机 (SVM)

机器学习模型—支持向量机 (SVM) 支持向量机 (SVM) 是一种强大的机器学习算法,用于线性或非线性分类、回归,甚至异常值检测任务。SVM 可用于各种任务,例如文本分类、图像分类、垃圾邮件检测、笔迹识别、基因表达分析、人脸检测和异常检测。SVM 在各种应用中具有适应性和高效…

【VUe】简略学习 vue

Vue 是一套用于构建用户界面的渐进式框架。要想使用这个框架&#xff0c;就需要先在页面中引用&#xff1a; 如何使用&#xff1a; 来到控制台&#xff1a; 数据绑定 若要在标签里替换&#xff0c;就需要使用 v-bind 指令了&#xff1a; 在标签里&#xff08;尖括号里&#xf…

jpg 转 ico 强大的图片处理工具 imageMagick

点击下载 windows, mac os, linux版本 GitHub - ImageMagick/ImageMagick: &#x1f9d9;‍♂️ ImageMagick 7 1. windows程序 链接&#xff1a;https://pan.baidu.com/s/1wZLqpcytpCVAl52pIrBBEw 提取码&#xff1a;hbfy 一直点击下一步安装 2. 然后 winr键 打开cmd 然…

软文推广怎么做效果更好?

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 软文推广想要效果好&#xff0c;你可以这么做&#xff1a; 1. 选对平台很重要 首先&#xff0c;你得找准你的目标受众喜欢去哪&#xff0c;然后就去那儿发软文。别盲目乱投&#xff0c;…

【计算机网络】1.5 分组交换网中的时延、丢包和吞吐量

A.分组交换网中的时延 当分组从一个节点沿着路径到后一节点时&#xff0c;该分组在沿途的各个节点经受了几种不同类型的时延。 时延的类型 处理时延 - d n o d a l d_{nodal} dnodal​ 处理时延包括以下部分—— a. 检查分组首部 b. 决定分组导向 排队时延 - d p r o c d_{…

原生JavaScript,根据后端返回扁平JSON动态【动态列头、动态数据】生成表格数据

前期准备&#xff1a; JQ下载地址&#xff1a; https://jquery.com/ <!DOCTYPE html> <html><head><meta charset"utf-8"><title>JSON动态生成表格数据,动态列头拼接</title><style>table {width: 800px;text-align: cen…

2.1 塑性力学——应力分析基本概念

个人专栏—塑性力学 1.1 塑性力学基本概念 塑性力学基本概念 1.2 弹塑性材料的三杆桁架分析 弹塑性材料的三杆桁架分析 1.3 加载路径对桁架的影响 加载路径对桁架的影响 目录 个人专栏—塑性力学 应力 \color{blue}应力 应力&#xff1a;内力的分布集度 单元体 \color{blue}单…

C++初学

1>思维导图 2>试编程 提示并输入一个字符串&#xff0c;统计该字符中大写、小写字母个数、数字个数、空格个数以及其他字符个数要求使用C风格字符串完成 #include <iostream> #include<string.h> using namespace std;int main() {string str;cout <<…

选择短信群发平台要小心陷阱

你知道短信群发平台也有陷阱吗&#xff1f;选择短信群发平台很重要&#xff0c;今天小编就为您介绍短信群发平台有哪些陷阱&#xff1f; 这几点你要注意了&#xff1a; 1、扣量&#xff0c;有些不靠谱的短信群发平台开始以低价诱惑“客户”&#xff0c;但是发送过程中就暗中扣…

不锈钢电阻器-栅极电阻器 - 为什么要使用它们呢?

常规 如果你看一个典型的吉他放大器的原理图&#xff0c;你会注意到有一个电阻器与第一个电子管的栅极串联&#xff0c;通常在68K左右&#xff0c;还有一个电阻器与功率管的栅极串联&#xff0c;通常为1.5K或5.6K&#xff0c;你可能会偶尔看到非常大的电阻&#xff0c; 例如 4…

学习JavaEE日子 Day24 TreeSet,内置比较器,外置比较器,HashMap

Day24 TreeSet 1.TreeSet 1.1 TreeSet的使用 注意&#xff1a;TreeSet的使用和HashSet一样 public class Test01 {public static void main(String[] args) {//创建TreeSet集合的对象TreeSet<String> set new TreeSet<>();//添加元素set.add("麻生希"…

Excel生成 chart 混合图表

在开发中有这样一个需求&#xff0c;邮件预警的时候&#xff0c;要求邮件主体内容是一个Chart 图表&#xff08;生成后的img&#xff09;&#xff0c;邮件需要有附件&#xff0c;且附件是Excel列表加图表&#xff0c;图表类型是混合图。 回顾&#xff1a;在之前一篇讲到如何使用…

Java中 常见的开源图库介绍

阅读本文之前请参阅------Java中 图的基础知识介绍 在 Java 中&#xff0c;有几种流行的开源图库&#xff0c;它们提供了丰富的图算法和高级操作&#xff0c;可以帮助开发者更高效地处理图相关的问题。以下是几种常见的 Java 图库及其特点和区别&#xff1a; JGraphT …

【C++】---string的OJ题

【C】---string的OJ题 1.字符串转整形数字&#xff08;重要&#xff09;&#xff08;1&#xff09;题目描述&#xff08;2&#xff09;思路展示&#xff08;3&#xff09;代码实现 2.字符串相加&#xff08;重要&#xff09;&#xff08;1&#xff09;题目描述&#xff08;2&am…

vim使用命令

1、 复制粘贴 复制一行: yy p vim 1.txt 进入编辑模式&#xff0c;在需要复制的行先按两下y键&#xff0c;再按p就完成复制复制2行&#xff1a; 2yy p 复制n行&#xff1a; nyy p 2、 删除 删除一行: dd删除2行: 2dd删除n行: ndd 3、 撤销 撤销上一步操作&#xff1a;…

【三、接口协议与抓包】使用ApiPost进行接口测试

你好&#xff0c;我是山茶&#xff0c;一个探索AI 测试的程序员。 接口测试是测试系统组件间接口的一种测试。接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点。测试的重点是要检查数据的交换&#xff0c;传递和控制管理过程&#xff0c;以及系统间的相…

前端javascript Promise使用方法详解(非常全面)

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属的专栏&#xff1a;前端零基础教学&#xff0c;实战进阶 景天的主页&#xff1a;景天科技苑 文章目录 Promise对象&#xff08;1&#xff09;…

鸿蒙开发:从入门到精通的全方位学习指南

随着华为鸿蒙HarmonyOS生态系统的迅速扩展&#xff0c;越来越多的开发者渴望深入了解并掌握这一前沿技术。本文旨在为鸿蒙开发新手提供一份详尽且实用的学习教程&#xff0c;助您从零开始&#xff0c;逐步迈向鸿蒙开发的巅峰。 一、鸿蒙开发环境搭建 DevEco Studio安装&#x…

12---风扇电路设计

视频链接 风扇硬件电路设计01_哔哩哔哩_bilibili 风扇电路设计 1、风扇简介 电脑风扇又称为散热风扇&#xff0c;一般用于散热。提供给散热器和机箱使用。市面上一般的散热风扇尺寸大小由直径2.5cm到30cm都有&#xff0c;厚度由6mm到76mm都有&#xff0c;而根据不同运作要求…