当前位置: 首页 > 知识库问答 >
问题:

使用PGP Bouncy Castle依赖项创建CipherOutputStream

笪昌翰
2023-03-14

我想从另一个OutputStream创建一个OutputStream,其中新的OutputStream将自动加密我写入该OutputStream的内容。我想使用Bouncy Castle,因为我已经在为其他功能使用该依赖项。

我在互联网上看到各种问题如何用Bouncy Castle加密数据,但答案要么是加密给定的文件(我不使用文件,我使用OutputStream),要么是需要复制粘贴大量代码。真不敢相信一定有那么难。

这是我的设置:

  1. 我正在使用此Bouncy Castle依赖项(v1.68)
  2. 我正在使用Java 8
  3. 我有一个由https://pgpkeygen.com/生成的公钥和私钥。算法为RSA,密钥大小为1024。
  4. 我将公钥和私钥保存为计算机上的文件
  5. 我要确保下面的测试通过

我注释掉了一些代码,密码上的init函数(代码编译了,但是测试失败了)。我不知道应该在init函数中放入什么作为第二个参数。读取的函数来自:https://github.com/jordanbaucke/pgp-sign-and-encrypt/blob/472d8932df303d6861ec494a3e942ea268eaf25f/src/signandencrypt.java#l272。只有testencryptdecryptithoutsigning是由我编写的。

代码:

@Test
void testEncryptDecryptWithoutSigning() throws Exception {
    // The data will be written to this property
    ByteArrayOutputStream baos = new ByteArrayOutputStream();

    Security.addProvider(new BouncyCastleProvider());

    PGPSecretKey privateKey = readSecretKey(pathToFile("privatekey0"));
    Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
    //cipher.init(Cipher.ENCRYPT_MODE, privateKey);

    CipherOutputStream os = new CipherOutputStream(baos, cipher);
    // I also need to use a PrintWriter
    PrintWriter printWriter =
            new PrintWriter(new BufferedWriter(new OutputStreamWriter(
                    os,
                    StandardCharsets.UTF_8.name())));

    // This is an example of super secret data to write
    String data = "Some very sensitive data";

    printWriter.print(data);
    printWriter.close();

    // At this point, the data is 'inside' the byte array property
    // Assert the text is encrypted
    if (baos.toString(StandardCharsets.UTF_8.name()).equals(data)) {
        throw new RuntimeException("baos not encrypted");
    }

    PGPSecretKey publicKey = readSecretKey(pathToFile("publickey0"));
    //cipher.init(Cipher.DECRYPT_MODE, publicKey);

    ByteArrayInputStream inputStream = new ByteArrayInputStream(baos.toByteArray());
    ByteArrayOutputStream decrypted = new ByteArrayOutputStream();

    // Decrypt the stream, but how?

    if (!decrypted.toString(StandardCharsets.UTF_8.name()).equals(data)) {
        throw new RuntimeException("Not successfully decrypted");
    }
}

static PGPSecretKey readSecretKey(InputStream input) throws IOException, PGPException
{
    PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(
            PGPUtil.getDecoderStream(input), new JcaKeyFingerprintCalculator());

    //
    // we just loop through the collection till we find a key suitable for encryption, in the real
    // world you would probably want to be a bit smarter about this.
    //

    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())
            {
                return key;
            }
        }
    }

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

static PGPSecretKey readSecretKey(String fileName) throws IOException, PGPException
{
    InputStream keyIn = new BufferedInputStream(new FileInputStream(fileName));
    PGPSecretKey secKey = readSecretKey(keyIn);
    keyIn.close();
    return secKey;
}

static PGPPublicKey readPublicKey(String fileName) throws IOException, PGPException
{
    InputStream keyIn = new BufferedInputStream(new FileInputStream(fileName));
    PGPPublicKey pubKey = readPublicKey(keyIn);
    keyIn.close();
    return pubKey;
}

/**
 * A simple routine that opens a key ring file and loads the first available key
 * suitable for encryption.
 *
 * @param input data stream containing the public key data
 * @return the first public key found.
 * @throws IOException
 * @throws PGPException
 */
static PGPPublicKey readPublicKey(InputStream input) throws IOException, PGPException
{
    PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(
            PGPUtil.getDecoderStream(input), new JcaKeyFingerprintCalculator());

    //
    // we just loop through the collection till we find a key suitable for encryption, in the real
    // world you would probably want to be a bit smarter about this.
    //

    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())
            {
                return key;
            }
        }
    }

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

共有1个答案

常明亮
2023-03-14

首先,网站不会生成一个键区,而是生成三个键区。历史上,在PGP中,在实际的加密密钥和密钥与PGP用户所称的密钥之间一直存在一些歧义,因为对于给定的用户(或实体或角色等)来说,具有一个“主”或“主”密钥以及与该主密钥绑定的一个或多个子密钥是常见的。对于DSA+ELG密钥,技术上需要使用子密钥(而不是主密钥)进行加密;对于RSA来说,这样做被认为是良好的做法,因为单独管理(例如可能撤销)这些密钥通常更好。有些人还认为使用子密钥而不是主密钥来签名数据是很好的做法,并且只使用主密钥来签名密钥(PGP称之为Certificing-C),但有些人不这样做。当PGP用户和文档谈到一个“密钥”时,他们通常指的是一个主密钥及其(所有)子密钥的组,他们说主密钥或子密钥(或加密子密钥或签名子密钥)是指一个特定的实际密钥。

当您选择RSA时,该网站生成一个主密钥(keypair),用法为SCEA--即所有目的--和两个子密钥,每个子密钥的用法为SEA--所有目的对一个子密钥有效。这是荒谬的;如果masterkey支持签名和加密,大多数PGP程序将永远不会使用任何子密钥,即使它没有或您重写了它,子密钥之间也没有任何意义的区别,也没有逻辑方法来选择使用哪一个子密钥。

BouncyCastle通过改变术语来加剧这种情况:大多数PGP程序使用key表示实际密钥或主密钥加子密钥的组,如上所示,'public‘和'secret’key表示每个密钥或组的一半,'keyring'表示您存储的所有密钥组,通常存储在一个文件中,该文件可能用于许多不同的人或实体。然而,Bouncy将一个主密钥及其子密钥(以公开或秘密形式)的组称为KeyRing,将可能包含多个组的文件称为KeyRinghtml" target="_blank">Collection,这两个组都是公开或秘密的变体。不管怎样...

你的第一个问题是你把它倒过来了。在公钥密码学中,我们用公钥(一半)加密,用私钥(一半)解密,PGP(因此BCPG)称之为秘密。此外,由于PGP中的私有/秘密密钥是密码加密的,所以要使用它,我们必须首先解密它。(在JKS和PKCS12这样的“普通”JCA密钥库中也是如此,但在其他密钥库中则不一定。)

你的第二个问题是类型。虽然给定非对称算法的(特定)PGP密钥在语义上只是该算法的密钥,加上一些元数据(标识、首选项和信任/签名信息),但PGP密钥的BCPG中的Java对象(类)不在Java加密体系结构(JCA)中用于密钥的对象的类型层次结构中。用更简单的话说,org.bouncycastle.openpgp.pgppublickey不是java.security.publickey的子类。因此必须将这些关键对象转换为与JCA兼容的对象,以便与JCA一起使用。

通过这些更改和一些添加,以下代码可以工作(FSVO工作):

static void SO66155608BCPGPRawStream (String[] args) throws Exception {
    byte[] plain = "testdata".getBytes(StandardCharsets.UTF_8);
    
    PGPPublicKey p1 = null;
    FileInputStream is = new FileInputStream (args[0]);
    Iterator<PGPPublicKeyRing> i1 = new JcaPGPPublicKeyRingCollection (PGPUtil.getDecoderStream(is)).getKeyRings();
    for( Iterator<PGPPublicKey> j1 = i1.next().getPublicKeys(); j1.hasNext(); ){
        PGPPublicKey t1 = j1.next();
        if( t1.isEncryptionKey() ){ p1 = t1; break; }
    }
    is.close();
    if( p1 == null ) throw new Exception ("no encryption key");
    PublicKey k1 = new JcaPGPKeyConverter().getPublicKey(p1);
    
    Cipher c1 = Cipher.getInstance("RSA/ECB/PKCS1Padding");
    c1.init(Cipher.ENCRYPT_MODE, k1);
    ByteArrayOutputStream b1 = new ByteArrayOutputStream();
    CipherOutputStream s1 = new CipherOutputStream(b1,c1);
    s1.write(plain);
    s1.close();
    byte[] cipher = b1.toByteArray();
    long id = p1.getKeyID();
    System.out.println("keyid="+Long.toString(id,16)+" "+Arrays.toString(cipher));
    if( Arrays.equals(cipher,plain) ) throw new Exception ("didn't encrypt!");
    
    PGPSecretKey p2 = null;
    is = new FileInputStream (args[1]); 
    Iterator<PGPSecretKeyRing> i2 = new JcaPGPSecretKeyRingCollection (PGPUtil.getDecoderStream(is)).getKeyRings();
    for( Iterator<PGPSecretKey> j2 = i2.next().getSecretKeys(); j2.hasNext(); ){
        PGPSecretKey t2 = j2.next();
        if( t2.getKeyID() == id ){ p2 = t2; break; }
    }
    is.close();
    if( p2 == null ) throw new Exception ("no decryption key");
    PGPPrivateKey p3 = p2.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().build(args[2].toCharArray()));
    PrivateKey k2 = new JcaPGPKeyConverter().getPrivateKey(p3);
    
    Cipher c2 = Cipher.getInstance("RSA/ECB/PKCS1Padding");
    c2.init(Cipher.DECRYPT_MODE, k2);
    ByteArrayInputStream b2 = new ByteArrayInputStream(cipher);
    CipherInputStream s2 = new CipherInputStream(b2,c2);
    byte[] back = new byte[cipher.length]; // definitely more than needed
    int actual = s2.read(back);
    s2.close();
    System.out.println ("Result->" + new String(back,0,actual,StandardCharsets.UTF_8));
}

(我发现将代码放在执行顺序的一个位置会更清楚,但您可以将其分解为多个片段,而不做任何实质性更改。)

我保留了您的逻辑(从Bouncy示例中),从第一组中选择第一个具有加密能力的公钥,无论是master还是sub,其中每个组都有一个Bouncy错误调用密钥环;由于以上所述的网站提供了主密钥SCEA,这始终是主密钥。不可能根据是否允许加密来选择一个秘密/私钥,而且在任何情况下都不能保证公钥文件总是按照相同的顺序,因此选择解密密钥的正确方法是匹配用于加密的密钥中的keyid。

而且,现代加密算法(不对称的,如RSA和对称的,如AES或“3DES”)产生的数据是任意的位模式,特别是大多数不是有效的UTF-8,所以将这些字节“解码”为UTF-8以与明文进行比较通常会破坏您的数据;如果您想要这个(不必要的)检查,您应该比较字节数组,如我所示。

最后,如果您不知道,通常不使用非对称算法来加密大型或可变大小的数据,这是您通常使用Java流的目的;这一点在维基百科的文章中也有解释。这种方法,直接使用RSA PKCS1-v1_5,并使用1024位密钥,只能处理117字节的数据(可能少于117个字符,视情况而定)。

如果您希望结果与任何实际的PGP实现兼容或可互操作,那肯定不是--这意味着从PGP密钥格式转换的工作是白费的,因为您可以按照Oracle网站上的基本教程或Stack上的数百个示例直接生成JCA格式的密钥。如果要与GPG或类似的方式互操作,则需要使用BCPG类进行PGP格式的加密和解密,这些类可以在普通字节流上分层,但与JCA的cipher{Input,Output}stream完全不同且不兼容。

 类似资料:
  • 在阅读dagger 2的文档时,我找不到一种简单的方法来在构建测试应用时提供依赖项。我找到的唯一线索是: 匕首2不支持覆盖。覆盖简单测试假货的模块可以创建模块的子类来模拟该行为。应该分解使用覆盖并依赖依赖依赖项注入的模块,以便将被覆盖的模块表示为两个模块之间的选择。 我不明白我将如何在Android上设置这样的配置,任何人都可以解释吗?

  • 我正在使用maven shade插件创建一个胖罐子,其中也包括一些弹性城堡罐子。但这造成了问题,因为Bouncy Castle的未签名版本。

  • 我有一个库,我称之为,它是另一个项目的依赖项,名为。在的构建中。gradle,我正在使用,它以如下方式指定为依赖项: 这很有效-已编译。但是,当我在我的项目中使用它时,我得到了以下内容: <code>生成。的gradle如下所示: 可能这是因为库在,但它没有正确的位置来搜索Maven存储库。有没有一种方法可以使这些搜索位置以及依赖关系本身变得短暂?

  • 我正在使用Gradle插件0.9和Android Studio 0.5.4。 我有一个Android库项目,只有一个依赖项。jar位于/libs文件夹中。当我在Proguard激活的情况下运行AssemblerRelease任务时,生成的AAR具有以下结构: 我的图书馆 问题是myJar。jar类被合并到类中。jar by Proguard(当我在没有Proguard的情况下运行时,它不会发生)。

  • 我想将android库构建为AAR文件,其中包括输出AAR文件中的依赖项。它使用另一个AAR文件作为libs文件夹中的依赖项。 生成库时显示此错误: 我的程序规则。pro文件是 我也试着使用https://github.com/kezong/fat-aar-android库并将facetec sdk模块作为嵌入项目导入,但build命令显示lint错误

  • 问题内容: 首先,我要说我是Ant的新手。就像之前一样,我只是在两天前才开始学习它以完成此任务。 我想要做的是创建一个“ master”蚂蚁脚本,该脚本调用其他几个蚂蚁脚本。例如: 现在,我拥有用于​​A,B和C的所有单独版本。通过这个,我的意思是我可以从这些文件夹中的任何一个运行“ ant”,它将构建项目并生成一个jar文件。如果以这种方式构建A,B和C,则我有3个jar文件。然后,我可以将它们