Java加解密(十一)PGP协议

目录

  • PGP协议
    • 1 PGP提供的操作
      • 1.1 身份识别
      • 1.2 机密性
      • 1.3 机密性与鉴别
      • 1.4 电子邮件的兼容性
      • 1.5 压缩
      • 1.6 分段和重装
    • 2 加密密钥和密钥环
      • 2.1 公钥环
      • 2.2 私钥环
    • 3 公开密钥管理
      • 3.1 公开密钥管理机制
      • 3.2 防止篡改公钥的方法
      • 3.3 信任的使用
    • 4 代码示例

PGP协议

PGP(Pretty Good Privacy),是一个基于 公匙基础设施(PKI) 的安全传输协议, 包括数字签名、压缩、加密、数字信封以及 Base64编码等过程。

可以用它对以防止非授权者阅读,它还能对加上数字签名从而使收信人可 以确认的发送者,并能确信没有被篡改。

现代信息社会里,当电子邮件广受欢迎的同时,其安全性问题也很突出。实际上,电子邮件的传递过程是邮件在网络上反复复制的过程,其网络传输路径不确定,很容易遭到不明身份者的窃取、篡改、冒用甚至恶意破坏,给收发双方带来麻烦。进行信息加密,保障电子邮件的传输安全已经成为广大E-mail用户的迫切要求。PGP的出现与应用很好地解决了电子邮件的安全传输问题。将传统的对称性加密与公开密钥方法结合起来,兼备了两者的优点。PGP提供了一种机密性和鉴别的服务,支持1024位的公开密钥与128位的传统加密算法,可以用于军事目的,完全能够满足电子邮件对于安全性能的要求。

1 PGP提供的操作

PGP的实际操作由五种服务组成:鉴别、机密性、电子邮件的兼容性、压缩、分段和重装。

1.1 身份识别

  1. 发送者创建报文;
  2. 发送者使用SHA-1生成报文的160bit散列代码(邮件文摘);
  3. 发送者使用自己的私钥,采用RSA算法对散列代码进行加密,串接在报文的前面;
  4. 接收者使用发送者的公钥,采用RSA解密和恢复散列代码;
  5. 接收者为报文生成新的散列代码,并与被解密的散列代码相比较。如果两者匹配,则报文作为已鉴别的报文而接收。

另外,签名是可以分离的。例如法律合同,需要多方签名,每个人的签名是独立的,因而可以仅应用到文档上。否则,签名将只能递归使用,第二个签名对文档的第一个签名进行签名,依此类推。

1.2 机密性

在PGP中,每个常规密钥只使用一次,即对每个报文生成新的128bit的随机数。为了保护密钥,使用接收者的公开密钥对它进行加密。过程如下:

  1. 发送者生成报文和用作该报文会话密钥(对称秘钥)的128bit随机数;
  2. 发送者采用CAST-128加密算法,使用会话密钥对报文进行加密。也可使用IDEA或3DES;
  3. 发送者采用RSA算法,使用接收者的公钥对会话密钥进行加密,并附加到报文前面;
  4. 接收者采用RSA算法,使用自己的私钥解密和恢复会话密钥;
  5. 接收者使用会话密钥解密报文。

除了使用RSA算法加密外,PGP还提供了DiffieHellman的变体EIGamal算法。

常规加密和公开密钥结合的好处

  1. 常规加密和公开密钥加密相结合使用比直接使用RSA或E1Gamal要快得多。
  2. 使用公开密钥算法解决了会话密钥分配问题。
  3. 由于电子邮件的存储转发特性,使用握手协议来保证双方具有相同会话密钥的方法是不现实的,而使用一次性的常规密钥加强了已经是很强的常规加密方法。

1.3 机密性与鉴别

首先为明文生成签名并附加到报文首部;然后使用CAST-128(或IDEA、3DES)对明文报文和签名进行加密,再使用RSA(或E1Gamal)对会话密钥进行加密。在这里要注意次序,如果先加密再签名的话,别人可以将签名去掉后签上自己的签名,从而篡改签名。

1.4 电子邮件的兼容性

当使用PGP时,至少传输报文的一部分需要加密,因此部分或全部的结果报文由任意8bit字节流组成。但由于很多的电子邮件系统只允许使用由ASCII正文组成的块,所以PGP提供了radix-64(就是MIME的BASE 64格式)转换方案,将原始二进制流转化为可打印的ASCII字符。

1.5 压缩

PGP在加密前进行预压缩处理,PGP内核使用PKZIP算法压缩加密前的明文。一方面对电子邮件而言,压缩后再经过radix-64编码有可能比明文更短,这就节省了网络传输的时间和存储空间;另一方面,明文经过压缩,实际上相当于经过一次变换,对明文攻击的抵御能力更强。

1.6 分段和重装

电子邮件设施经常受限于最大报文长度(50000个)八位组的限制。分段是在所有其他的处理(包括radix-64转换)完成后才进行的,因此,会话密钥部分和签名部分只在第一个报文段的开始位置出现一次。在接收端,PGP必须剥掉所在的电子邮件首部,并且重新装配成原来的完整的分组。

2 加密密钥和密钥环

PGP要求用户保持一个密钥的本地缓存。这个缓存被称为用户的密钥环。每个用户至少有两个密钥环:公钥环和私钥环。每个密钥环都用来存放用于特定目标的一套密钥。然而保持这两个密钥环的安全性很重要;如果有人窜改公钥环,就会使你错误地验证签名或者给错误的接收者加密消息。

2.1 公钥环

公钥环为所有与你通信的各方存放公钥、useris、签名和信任参数。无论PGP什么时候要查找密钥来验证签名或加密消息,它都会到你的公钥环中去查找。这意味着你要让公钥环保持最新,即可以通过频繁地公报来完成,也可以通过访问PGP公钥服务器来实现。
  信任参数存放在公钥环中,因此人与人之间不可能共享密钥环。而且,PGP不能正确处理多个密钥环,因此使用当前的版本创建一个站点范围(site-wide)的密钥环并不容易。
  这是PGP中一个很有名的故障。等到在将来的版本中支持了多个密钥环,他布密钥最好的办法是使用密钥服务器。有关公钥环的一个安全性问题是一个被损坏的公钥环可能会导致错误的签名验证,更糟糕的是,还可能把消息发送给错误的对象。攻击者可以改变存放在公钥环的信任参数,或者改变存放在那里的实际密钥资料。这些攻击在“公钥环的攻击”部分将详细描述。
  在设计密钥环的时候,只是想用它保存一些比较亲密的朋友和同事的密钥。很不幸,从当前的使用来看这种设计的假设有很大的局限性。许多人把他从来没见过甚至从来没联系过的人的密钥都放到密钥环国。这样就带来许多问题,主要是由于信息和复制和访问密钥环所需要的时间造成的。推荐的办法是保持密钥环尽可能小,当必要时从密钥服务器或站点级密钥环中取得密钥。

2.2 私钥环

私钥环是PGP中存放个人私密的地方。当你产生一个密钥时,不能泄露的部分就存放在私钥环中。需要私下保存的数据被加密,因此对私钥环的访问不会自动允许对其秘密的使用。
  然而,如果一个攻击者能够访问私钥环,那么他伪造签名解密消息的障碍就小多了。
  因为私钥不在人们中间传送,用户的钥环中唯一可能的密钥就是他自己和私钥。因为私钥环受到通过短语的保护,简单的密钥环内容传送不允许对密钥资料的访问。
  我们不推荐各方共享一个私钥,尽管有时可能有这种需要。尤其,当你拥有属于一个组织的私钥时,可能有必要让这个组织的多个成员都能访问这个私钥。这意味着任何个人都可以完全代表那个组织行动。
  有时,拥有一个不带通过短语的私钥可能会有用。例如,建立一个带有私钥的服务器代表一群人。尤其,你可以运行一个经过加密的邮件列表,这个邮件列表的邮件服务器有它自己的密钥,而且有所有列表成员的公钥。列表成员用邮件服务器的密钥加密消息并把它发送给列表。列表处理解密消息,然后用相应列表成员加密的公钥重新消息。此时列表服务器可以和列表密钥签署消息,但这不是必须的。在这种情况下服务器进程需要访问一个私钥,这就需要密钥没有通过短语。
  因为一个私钥环中可能有多个私钥,PGP有一个选项可以指定你想要使用的私钥的userid。
  无论何时PGP需要选择一个私钥时,它都会选择密钥环的第一个密钥,这个密钥通常是最近创建的。你可以使用-u选项向PGP提供userid来修改它,这样PGP就会使用相应userid的密钥。

3 公开密钥管理

3.1 公开密钥管理机制

一个成熟的加密体系必然要有一个成熟的密钥管理机制配磁。公钥体制的提出就是为了解决传统加密体系的密钥分配过程不安全、不方便的缺点。例如网络黑客们常用的手段之一就是“监听”,通过网络传送的密钥很容易被截获。对PGP来说,公钥本来就是要公开,就没有防监听的问题。但公钥的发布仍然可能存在安全性问题,例如公钥被篡改(public key tampering),使得使用公钥与公钥持有人的公钥不一致。这在公钥密码体系中是很严重的安全问题。因此必须帮助用户确信使用的公钥是与他通信的对方的公钥。

以用户A和用户B通信为例,现假设用户A想给用户B发信。首先用户A就必须获取用户B的公钥,用户A从BBS上下载或通过其它途径得到B的公钥,并用它加密信件发给B。不幸的是,用户A和B都不知道,攻击者C潜入BBS或网络中,侦听或截取到用户B的公钥,然后在自己的PGP系统中以用户B的名字生成密钥对中的公钥,替换了用户B的公钥,并放在BBS上或直接以用户B的身份把更换后的用户B的“公钥”发给用户A。那A用来发信的公钥是已经更改过的,实际上是C伪装B生成的另一个公钥(A得到的B的公钥实际上是C的公钥/密钥对,用户名为B)。这样一来B收到A的来信后就不能用自己的私钥解密了。更可恶的是,用户C还可伪造用户B的签名给A或其他人发信,因为A手中的B的公钥是仿造的,用户A会以为真是用户B的来信。于是C就可以用他手中的私钥来解密A给B的信,还可以用B真正的公钥来转发A给B的信,甚至还可以改动A给B的信。

3.2 防止篡改公钥的方法

  1. 直接从B手中得到其公钥,这种方法有局限性。
  2. 通过电话认证密钥:在电话上以radix-64的形式口述密钥或密钥指纹。密钥指纹(keys fingerprint)就是PGP生成密钥的160bit的SHA-1摘要(16个8位十六进制)。
  3. 从双方信任的D那里获得B的公钥。如果A和B有一个共同的朋友D,而D知道他手中的B的公钥是正确的。D签名的B的公钥上载到BBS上让用户去拿,A想要获得B的公钥就必须先获取D的公钥来解密BBS或网上经过D签名的B的公钥,这样就等于加了双重保险,一般没有可能去篡改而不被用户发现,即使是BBS管理员。这就是从公共渠道传递公钥的安全手段。有可能A拿到的D或其他签名的朋友的公钥也是假的,但这就要求攻击者C必须对三人甚至很多人都很熟悉,这样的可能性不大,而且必需经过长时间的策划。
    只通过一个签名证力度可能是小了一点,于是PGP把用不同私钥签名的公钥收集在一起,发送到公共场合,希望大部分从至少认识其中一个,从而间接认证了用户(A)的公钥。同样用户(D)签了朋友(A)的公钥后应该寄回给他(A)(朋友),这样可以让他(A)通过该用户(D)被该用户(D)的其他朋友所认证。与现实中人的交往一样。PGP会自动根据用户拿到的公钥分析出哪些是朋友介绍来的签名的公钥,把它们赋以不同的信任级别,供用户参考决定对它们的信任程度。也可指定某人有几层转介公钥的能力,这种能力随着认证的传递而递减的。
  4. 由一个普通信任的机构担当第三方,即“认证机构”。这样的“认证机构”适合由非个人控制的组织或政府机构充当,来注册和管理用户的密钥对。现在已经有等级认证制定的机构存在,如广东省电子商务电子认证中心(WWW.cnca.net)就是一个这样的认证机构。对于那些非常分散的用户,PGP更赞成使用私人方式的密钥转介。

3.3 信任的使用

PGP确实为公开密钥附加侂任和开发信任信息提供了一种方便的方法使用信任。
公开密钥环的每个实体都是一个公开的密钥证书。与每个这亲的实体相联系的是密钥合法性字段,用来指示PGP信任“这是这个用户合法的公开密钥”的程度;信任程度越高,这个用户ID与这个密钥的绑定越紧密。这个字段由PGP计算。与每个实体相联系的还有用户收集的多个签名。反过来,每个签名都带有签名信任字段,用来指示该PGP用户信任签名者对这个公开密钥证明的程度。密钥合法性字段是从这个实体的一组签名信任字节中推导出来的。最后,每个实体定义了与特定的拥有者相联系的公开密钥,包括拥有者信任字段,用来指示这个公开密钥对其他公开密钥证书进行签名的信任程度(这个信任程度是由该用户指定的)。可以把签名信任字段看成是来自于其他实体的拥有者信任字段的副本。

一共有四种信任级别:

  • 完全信任(somplete trust)
  • 边缘信任(marginal trust)
  • 不信任(no trust)
  • 未知信任(unknown trust)

例如正在处理用户A的公开密钥环,操作描述如下:

  1. 当A在公开密钥环中插入了新的公开密钥时,PGP为与这个公开密钥拥有者相关联的信任标志赋值,插入KUa,则赋值=1终极信任;否则,需说明这个拥有者是未知的、不可任信的、少量信任的和完全可信的等,赋以相应的权重值1/x、1/y等。
  2. 当新的公开密钥输入后,可以在它上面附加一个或多个签名,以后还可以增加更多的签名。在实体中插入签名时,PGP在公开密钥环中搜索,查看这个签名的作者是否属于已知的公开密钥拥有者。如果是,为这个签名的SIGTRUST字段赋以该拥的者的OWNERTRUST值。否则,赋以不认识的用户值。
  3. 密钥合法性字段的值是在这个实体的签名信任字段的基础上计算的。如果至少一个签名具有终极信任的值,那么密钥合法性字段的设置为完全;否则,PGP计算信任值的权重和。对于总是可信任的签名赋以1/x的权重,对于通常可信任的签名赋以权重1/y,其中x和y都是用户可配置的参数。当介绍者的密钥/用户ID绑定的权重总达到1时,绑定被认为是值得信任的,密钥合法性被设置为完全。因此,在没有终极信任的情况下,需要至少x个签名总是可信的,或者至少y个签名是可信的,或者上述两种情况的某种组合。

总之,PGP采用了RSA和传统加密的杂合算法,用于数字签名的邮件文摘算法、加密前压缩等,可以用来加密文件,还可以代替Uuencode生成RADIX 64格式(就是MIME的BASE 64格式)的编码文件。PGP创造性地把RSA公钥体系的方便和传统加密体系的高速度结合起来,并且在数字签名和密钥认证管理机制上有巧妙的设计。这是目前最难破译的密码体系之一。
用户通过PGP的软件加密程序,可以在不安全的通信链路上创建安全的消息和通信。PGP协议已经成为公钥加密技术和全球范围内消息安全性的事实标准。因为所有人都能看到它的源代码,从而查找出故障和安全性漏局。

4 代码示例

下面的用Java代码模拟了一个报文交互的过程:
Peter需要发送一段转账请求给Owen,请求Owen转一笔钱。报文中包含了Peter的银行账号,所以报文内容需要加密;并且Owen需要保证请求的确是Peter发送的,以免将钱转给攻击者。整个交互过程描述如下:

  1. Peter创建报文:
Hello Bob,
  How are you doing.
  This is my bank account number: 123456.
Regards
Yours Peter.
  1. Peter使用自己的私钥,用PGP创建报文的签名(SHA1摘要+RSA私钥加密),并将签名加在报文的前面;
  2. Peter使用会话秘钥(一次性对称算法秘钥)加密签名和报文,并用Owen的公钥加密会话秘钥,并加在加密报文前面;
  3. Peter将加密报文发送给Owen;
  4. Owen收到报文后,使用自己的私钥解密会话秘钥,并用会话秘钥解密签名和报文;
  5. Owen使用Peter的公钥,验证签名;
  6. 签名验证通过,接收报文;否则丢弃报文。
package com.qupeng.crypto.pgp;

import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.BCPGOutputStream;
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.*;
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.jcajce.*;
import org.bouncycastle.util.io.Streams;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.SecureRandom;
import java.security.Security;
import java.util.*;

public class PgpCipher {

    private static final String PROVIDER_BC = "BC";

    static {
        if (Security.getProvider(PROVIDER_BC) == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    public static void main(String[] args) throws Exception {
        // 1.As a message sender, Peter create PGP key rings to store his own key pairs
        PGPSecretKey pgpSecretKeyPeter = PgpKeyRingGenerator.generateKeyPair("Peter(sender)", "111111", 1024, PeterConfigurations.ALGORITHM);
        PgpKeyRingGenerator.createDirectory(PeterConfigurations.KEY_REPO_DIRECTORY);
        PgpKeyRingGenerator.setupSecretKeyRings(pgpSecretKeyPeter, PeterConfigurations.PRIVATE_KEY_RING_FILE_PATH);

        // 2.As a message receiver, Owen create PGP key rings to store his own key pairs
        PGPSecretKey pgpSecretKeyOwen = PgpKeyRingGenerator.generateKeyPair("Owen(receiver)", "000000", 2048, OwenConfigurations.ALGORITHM);
        PgpKeyRingGenerator.createDirectory(OwenConfigurations.KEY_REPO_DIRECTORY);
        PgpKeyRingGenerator.setupSecretKeyRings(pgpSecretKeyOwen, OwenConfigurations.PRIVATE_KEY_RING_FILE_PATH);

        // 3. Owen save the public from Peter
        PgpKeyRingGenerator.setupPublicKeyRings(pgpSecretKeyPeter.getPublicKey(), OwenConfigurations.PUBLIC_KEY_RING_FILE_PATH, OwenConfigurations.PUBLIC_KEY_FINGERPRINT_FILE_PATH);

        // 4. Peter save the public from Owen
        PgpKeyRingGenerator.setupPublicKeyRings(pgpSecretKeyOwen.getPublicKey(), PeterConfigurations.PUBLIC_KEY_RING_FILE_PATH, PeterConfigurations.PUBLIC_KEY_FINGERPRINT_FILE_PATH);

        // 5. Peter want to send a message to Owen, sign message by Owen's private key
        byte[] rawData = Files.readAllBytes(Paths.get(PeterConfigurations.PLAIN_TXT_FILE_PATH));
        byte[] signature = digitalSign(rawData, "111111", PeterConfigurations.PRIVATE_KEY_RING_FILE_PATH, "Peter(sender)");
        Files.write(Paths.get(PeterConfigurations.CIPHER_TXT_FILE_PATH), signature, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);

        // 6. Peter encrypt message by Owen's public key
        encrypt(rawData, PeterConfigurations.CIPHER_TXT_FILE_PATH, PeterConfigurations.PUBLIC_KEY_RING_FILE_PATH, "Owen(receiver)");

        // 7. After receiving Peter's message, Owen decrypt message by his own private key
        byte[] cipherData = Files.readAllBytes(Paths.get(PeterConfigurations.CIPHER_TXT_FILE_PATH));
        List<byte[]> decryptedDataList = decrypt(cipherData, OwenConfigurations.PRIVATE_KEY_RING_FILE_PATH, "000000");

        // 8. And Owen verify signature by Peter's public key
        List<PGPSignature> pgpSignatureList = readSignatures(cipherData);
        verifySignature(decryptedDataList, pgpSignatureList, OwenConfigurations.PUBLIC_KEY_RING_FILE_PATH);

        // 9. If the verification signature is passed, Owen accept this message and save it to a file.
        Optional<byte[]> reducing = decryptedDataList.stream().reduce((bytes, bytes2) -> {
            byte[] merging = Arrays.copyOf(bytes, bytes.length + bytes2.length);
            System.arraycopy(bytes2, 0, bytes, 0, bytes2.length);
            return merging;
        });
        Files.write(Paths.get(OwenConfigurations.DECRYPT_TXT_FILE_PATH), reducing.orElse(new byte[0]), StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
    }

    public static byte[] digitalSign(byte[] rawData, String passwordPhase, String secretKeyPath, String userId) throws Exception {
        PGPSecretKey pgpSecretKey = readSecretKey(userId, secretKeyPath);
        return digitalSign(rawData, pgpSecretKey, passwordPhase, true);
    }

    public static void encrypt(byte[] rawData, String outputFilePath, String publicKeyPath, String userId) throws Exception {
        PGPPublicKey pgpPublicKey = readPublicKeyByUser(userId, publicKeyPath);
        encrypt(rawData, outputFilePath, "cipher.txt", pgpPublicKey);
    }

    public static void encrypt(byte[] rawData, String outputFilePath, String fileName, PGPPublicKey pgpPublicKey) throws IOException, PGPException {
        byte[] compressedData = compressData(rawData, fileName);
        PGPEncryptedDataGenerator pgpEncryptedDataGenerator = new PGPEncryptedDataGenerator(
                new JcePGPDataEncryptorBuilder(PGPEncryptedData.CAST5)
                        .setWithIntegrityPacket(true)
                        .setSecureRandom(new SecureRandom())
                        .setProvider(PROVIDER_BC));
        pgpEncryptedDataGenerator.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(pgpPublicKey).setProvider(PROVIDER_BC));

        try (FileOutputStream fileOutputStream = new FileOutputStream(outputFilePath, true);
             OutputStream encryptOutputStream = pgpEncryptedDataGenerator.open(fileOutputStream, compressedData.length)) {
            encryptOutputStream.write(compressedData);
        }
    }

    public static List<PGPSignature> readSignatures (byte[] cipherData) throws IOException {
        try (InputStream signatureInputStream = new ByteArrayInputStream(cipherData);InputStream pgpEncryptedData = PGPUtil.getDecoderStream(signatureInputStream)) {
            JcaPGPObjectFactory jcaPGPObjectFactory = new JcaPGPObjectFactory(pgpEncryptedData);
            Object object = jcaPGPObjectFactory.nextObject();

            PGPSignatureList pgpSignatures;
            List<PGPSignature> pgpSignatureList = new ArrayList<>();
            if (object instanceof PGPSignatureList) {
                pgpSignatures = (PGPSignatureList) object;
                // find the secret key
                Iterator<PGPSignature> iterator = pgpSignatures.iterator();
                while (iterator.hasNext()) {
                    PGPSignature pgpSignature = iterator.next();
                    pgpSignatureList.add(pgpSignature);
                }
            }

            return pgpSignatureList;
        }
    }

    public static boolean verifySignature(List<byte[]> decryptedDataList, List<PGPSignature> pgpSignatureList, String publicKeyPath) throws IOException, PGPException {
        boolean result = true;
        if (decryptedDataList.size() != pgpSignatureList.size()) {
            throw new RuntimeException("The number of signature is incorrect.");
        }
        for (int i = 0; i < decryptedDataList.size(); i++) {
            PGPSignature pgpSignature = pgpSignatureList.get(i);
            PGPPublicKey pgpPublicKey = readPublicKeyById(pgpSignature.getKeyID(), publicKeyPath);
            pgpSignature.init(new JcaPGPContentVerifierBuilderProvider().setProvider(new BouncyCastleProvider()), pgpPublicKey);
            ByteArrayInputStream inputStream = new ByteArrayInputStream(decryptedDataList.get(i));

            int ch;
            while ((ch = inputStream.read()) >= 0) {
                pgpSignature.update((byte) ch);
            }
            inputStream.close();

            result &= pgpSignature.verify();
        }

        return result;
    }

    public static PGPPublicKey readPublicKeyById(Long keyId, String publicKeyPath) throws IOException, PGPException {
        try (FileInputStream publicKeyInputStream = new FileInputStream(publicKeyPath)) {
            PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(publicKeyInputStream), new JcaKeyFingerprintCalculator());
            return pgpPub.getPublicKey(keyId);
        }
    }

    public static List<byte[]> decrypt(byte[] cipherData, String secretKeyPath, String passwordPhase) throws Exception{
        try (InputStream encryptedInputStream = new ByteArrayInputStream(cipherData);
             InputStream pgpEncryptedInputStream = PGPUtil.getDecoderStream(encryptedInputStream)) {
            JcaPGPObjectFactory jcaPGPObjectFactory = new JcaPGPObjectFactory(pgpEncryptedInputStream);
            Object object = jcaPGPObjectFactory.nextObject();
            PGPSecretKeyRingCollection pgpSecretKeyRingCollection = readSecretKeys(secretKeyPath);
            List<byte[]> decryptedDatas = new ArrayList<>();
            while (null != object) {
                if (object instanceof PGPEncryptedDataList) {
                    PGPEncryptedDataList pgpEncryptedDataList = (PGPEncryptedDataList) object;
                    Iterator<PGPEncryptedData> iterator = pgpEncryptedDataList.getEncryptedDataObjects();
                    while (iterator.hasNext()) {
                        PGPEncryptedData pgpEncryptedData = iterator.next();
                        if (pgpEncryptedData instanceof PGPPublicKeyEncryptedData) {
                            PGPPublicKeyEncryptedData pgpPublicKeyEncryptedData = (PGPPublicKeyEncryptedData)pgpEncryptedData;
                            PGPPrivateKey pgpPrivateKey = findSecretKey(pgpSecretKeyRingCollection, pgpPublicKeyEncryptedData.getKeyID(), passwordPhase.toCharArray());

                            InputStream pgpPrivateKeyInputStream = pgpPublicKeyEncryptedData.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider(PROVIDER_BC).build(pgpPrivateKey));
                            JcaPGPObjectFactory pgpPrivateKey_jcaPGPObjectFactory = new JcaPGPObjectFactory(pgpPrivateKeyInputStream);
                            Object message = pgpPrivateKey_jcaPGPObjectFactory.nextObject();
                            if (message instanceof PGPCompressedData) {
                                PGPCompressedData pgpCompressedData = (PGPCompressedData) message;
                                JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(pgpCompressedData.getDataStream());

                                message = pgpFact.nextObject();
                            }

                            if (message instanceof PGPLiteralData) {
                                PGPLiteralData pgpLiteralData = (PGPLiteralData) message;
                                InputStream unc = pgpLiteralData.getInputStream();
                                try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
                                    Streams.pipeAll(unc, outputStream);
                                    decryptedDatas.add(outputStream.toByteArray());
                                }
                            }

                            if (pgpPublicKeyEncryptedData.isIntegrityProtected()) {
                                if (!pgpPublicKeyEncryptedData.verify()) {
                                    System.err.println("message failed integrity check");
                                } else {
                                    System.out.println("message integrity check passed");
                                }
                            } else {
                                System.out.println("no message integrity check");
                            }
                        }
                    }
                }
                object = jcaPGPObjectFactory.nextObject();
            }
            return decryptedDatas;
        } catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    public static byte[] compressData(byte[] data, String fileName) throws IOException {

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        PGPCompressedDataGenerator pgpCompressedDataGenerator = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP);
        OutputStream compressStream = pgpCompressedDataGenerator.open(byteArrayOutputStream);

        PGPLiteralDataGenerator pgpLiteralDataGenerator = new PGPLiteralDataGenerator();
        // we want to generate compressed data. This might be a user option later,
        OutputStream pgpOutputStream = pgpLiteralDataGenerator.open(
                compressStream,
                PGPLiteralData.BINARY,
                fileName,
                data.length,
                new Date()
        );

        // in which case we would pass in bOut.
        pgpOutputStream.write(data);
        pgpOutputStream.close();
        compressStream.close();

        return byteArrayOutputStream.toByteArray();
    }

    public static PGPPublicKey readPublicKeyByUser(String userId, String publicKeyPath) throws IOException, PGPException {
        try (FileInputStream publicKeyInputStream = new FileInputStream(publicKeyPath)) {
            PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(
                    PGPUtil.getDecoderStream(publicKeyInputStream), new JcaKeyFingerprintCalculator());

            Iterator keyRingIter = pgpPub.getKeyRings();
            while (keyRingIter.hasNext()) {
                PGPPublicKeyRing keyRing = (PGPPublicKeyRing) keyRingIter.next();

                Iterator keyIter = keyRing.getPublicKeys();
                while (keyIter.hasNext()) {
                    PGPPublicKey key = (PGPPublicKey) keyIter.next();
                    if (key.isEncryptionKey()) {
                        Iterator<String> userIdItor = key.getUserIDs();
                        while (userIdItor.hasNext()) {
                            String userIdInKey = userIdItor.next();
                            if (userId.equals(userIdInKey)) {
                                return key;
                            }
                        }
                    }
                }
            }
            throw new IllegalArgumentException("Can't find encryption key in key ring.");
        }

    }

    public static PGPPrivateKey findSecretKey(PGPSecretKeyRingCollection pgpSecretKeyRingCollection, long keyID, char[] pass) throws PGPException {

        PGPSecretKey pgpSecretKey = pgpSecretKeyRingCollection.getSecretKey(keyID);
        if (pgpSecretKey == null) return null;

        PBESecretKeyDecryptor pbeSecretKeyDecryptor = new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass);
        return pgpSecretKey.extractPrivateKey(pbeSecretKeyDecryptor);

    }

    private static byte[] digitalSign(byte[] encData, PGPSecretKey pgpSecretKey, String pass, boolean armor) throws  IOException, PGPException {
        PGPPrivateKey pgpPrivateKey = pgpSecretKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider(new BouncyCastleProvider()).build(pass.toCharArray()));
        PGPSignatureGenerator sGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(pgpSecretKey.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1).setProvider(new BouncyCastleProvider()));

        sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivateKey);
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        ArmoredOutputStream aOut = new ArmoredOutputStream(byteOut);
        BCPGOutputStream bOut = new BCPGOutputStream(byteOut);

        int i = 0;
        while (i < encData.length) {
            sGen.update(encData[i++]);
        }
        aOut.endClearText();
        sGen.generate().encode(bOut);
        if (armor) {
            aOut.close();
        }

        return byteOut.toByteArray();
    }

    private static PGPSecretKey readSecretKey(String userId, String secretKeyPath) throws IOException, PGPException {
        try (FileInputStream secretKeyInputStream = new FileInputStream(secretKeyPath)) {
            PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(secretKeyInputStream), new JcaKeyFingerprintCalculator());
            Iterator keyRingIter = pgpSec.getKeyRings();
            while (keyRingIter.hasNext()) {
                PGPSecretKeyRing keyRing = (PGPSecretKeyRing) keyRingIter.next();

                Iterator keyIter = keyRing.getSecretKeys();
                while (keyIter.hasNext()) {
                    PGPSecretKey key = (PGPSecretKey) keyIter.next();

                    if (key.isSigningKey()) {
                        Iterator<String> userIdIterator = key.getUserIDs();
                        while (userIdIterator.hasNext()) {
                            String userIdInKey = userIdIterator.next();
                            if (userId.equals(userIdInKey)) {
                                return key;
                            }
                        }
                    }
                }
            }

            throw new IllegalArgumentException("Can't find signing key in key ring.");
        }
    }

    private static PGPSecretKeyRingCollection readSecretKeys(String secretKeyPath) throws IOException, PGPException {
        try (FileInputStream secretKeyInputStream = new FileInputStream(secretKeyPath)) {
            return new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(secretKeyInputStream), new JcaKeyFingerprintCalculator());
        }
    }
}
package com.qupeng.crypto.pgp;

import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.openpgp.*;
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
import sun.misc.BASE64Encoder;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class PgpKeyRingGenerator {

    public static void setupSecretKeyRings(PGPSecretKey pgpSecretKey, String privateKeyPath) {
        try (OutputStream priOutputStream = new FileOutputStream(privateKeyPath)) {

            // Setup private key ring
            List<PGPSecretKey> secretKeyList = new ArrayList<>();
            secretKeyList.add(pgpSecretKey);
            PGPSecretKeyRing pgpSecretKeyRing = new PGPSecretKeyRing(secretKeyList);
            List<PGPSecretKeyRing> secretKeyRingList = new ArrayList<>();
            secretKeyRingList.add(pgpSecretKeyRing);
            PGPSecretKeyRingCollection pgpSecretKeyRings = new PGPSecretKeyRingCollection(secretKeyRingList);
            String secretKeyString = new BASE64Encoder().encode(pgpSecretKeyRings.getEncoded());
            priOutputStream.write(secretKeyString.getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void setupPublicKeyRings(PGPPublicKey pgpPublicKey, String publicKeyPath,
                                            String publicKeyFingerPath) {
        try (OutputStream pubOutputStream = new FileOutputStream(publicKeyPath);
             OutputStream pubFpOutputStream = new FileOutputStream(publicKeyFingerPath)) {

            // Setup public key ring
            List<PGPPublicKey> publicKeyList = new ArrayList<>();
            publicKeyList.add(pgpPublicKey);
            PGPPublicKeyRing pgpPublicKeyRing = new PGPPublicKeyRing(publicKeyList);
            List<PGPPublicKeyRing> publicKeyRingList = new ArrayList<>();
            publicKeyRingList.add(pgpPublicKeyRing);
            PGPPublicKeyRingCollection pgpPublicKeyRings = new PGPPublicKeyRingCollection(publicKeyRingList);
            String publicKeyString = new BASE64Encoder().encode(pgpPublicKeyRings.getEncoded());
            pubOutputStream.write(publicKeyString.getBytes());

            // Output fingerprint of public key
            String publicKeyFpString = new BASE64Encoder().encode(pgpPublicKey.getFingerprint());
            pubFpOutputStream.write(publicKeyFpString.getBytes());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static PGPSecretKey generateKeyPair(String identity, String passPhrase, int keyWidth, String algorithm) throws Exception {
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(algorithm, Configurations.PROVIDER_BC);
        keyPairGenerator.initialize(keyWidth);
        KeyPair generateKeyPair = keyPairGenerator.generateKeyPair();

        // key format without armored
        // the pass phrase for open private key
        char[] passwordPhraseBytes = passPhrase.toCharArray();
        // Hash algorithm using SHA1 as certificate
        PGPDigestCalculator sha1Calc = (new JcaPGPDigestCalculatorProviderBuilder()).build().get(HashAlgorithmTags.SHA1);
        // Generate RSA key pair
        JcaPGPKeyPair jcaPGPKeyPair = new JcaPGPKeyPair(PGPPublicKey.RSA_GENERAL, generateKeyPair, new Date());

        PGPSecretKey pgpSecretKey = new PGPSecretKey(
                PGPSignature.DEFAULT_CERTIFICATION,
                jcaPGPKeyPair,
                identity,
                sha1Calc,
                null,
                null,
                new JcaPGPContentSignerBuilder(jcaPGPKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1),
                (new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.CAST5, sha1Calc)).setProvider(Configurations.PROVIDER_BC).build(passwordPhraseBytes));
        return pgpSecretKey;
    }

    public static void createDirectory(String path) {
        File file = new File(path);
        if (!file.exists() && !file.isDirectory()) {
            file.mkdirs();
        }
    }
}

package com.qupeng.crypto.pgp;

public interface Configurations {
    String PROVIDER_BC = "BC";
}

package com.qupeng.crypto.pgp;

public interface PeterConfigurations {
    String ALGORITHM = "RSA";
    String KEY_REPO_DIRECTORY = "src/main/resources/pgp/peter/";
    String PUBLIC_KEY_RING_FILE_PATH = KEY_REPO_DIRECTORY + "public_key_rings.asc";
    String PUBLIC_KEY_FINGERPRINT_FILE_PATH  = KEY_REPO_DIRECTORY + "public_key_fingerprint.txt";
    String PRIVATE_KEY_RING_FILE_PATH  = KEY_REPO_DIRECTORY + "secret_key_rings.asc";
    String PLAIN_TXT_FILE_PATH = KEY_REPO_DIRECTORY + "plain.txt";
    String CIPHER_TXT_FILE_PATH = KEY_REPO_DIRECTORY + "cipher.txt";
}

package com.qupeng.crypto.pgp;

public interface OwenConfigurations {
    String ALGORITHM = "RSA";
    String KEY_REPO_DIRECTORY = "src/main/resources/pgp/owen/";
    String PUBLIC_KEY_RING_FILE_PATH = KEY_REPO_DIRECTORY + "public_key_rings.asc";
    String PUBLIC_KEY_FINGERPRINT_FILE_PATH  = KEY_REPO_DIRECTORY + "public_key_fingerprint.txt";
    String PRIVATE_KEY_RING_FILE_PATH  = KEY_REPO_DIRECTORY + "secret_key_rings.asc";
    String DECRYPT_TXT_FILE_PATH = KEY_REPO_DIRECTORY + "decrypt.txt";
}

        <dependency>
            <groupId>org.bouncycastlegroupId>
            <artifactId>bcpg-jdk18onartifactId>
            <version>1.72version>
        dependency>
        <dependency>
            <groupId>org.bouncycastlegroupId>
            <artifactId>bcprov-jdk18onartifactId>
            <version>1.72version>
        dependency>

你可能感兴趣的:(java,网络)