1

I was trying to encrypt the files with bouncy-gpg and my p12 key. But it's getting an error No (suitable) public key for encryption to p12 email address found Honestly, I am a newbie with a bouncy castle. That would be appreciated with any advice.

            KeyStore keystore = KeyStore.getInstance("PKCS12", "SunJSSE");
            keystore.load(is, p12Password.toCharArray());
            String alias = keystore.aliases().nextElement();

            PrivateKey privateKey = (PrivateKey)keystore.getKey(alias, p12Password.toCharArray());

            Certificate cert = keystore.getCertificate(alias);
            PublicKey publicKey = cert.getPublicKey();

            X509Certificate x509cert = (X509Certificate) cert;
            X509Principal principal = PrincipalUtil.getSubjectX509Principal(x509cert);
            Vector<?> values = principal.getValues(X509Name.EmailAddress);
            String email = (String) values.get(0);

            JcaPGPKeyConverter jcaPGPKeyConverter = new JcaPGPKeyConverter();
            PGPPublicKey pgpPublicKey = jcaPGPKeyConverter.getPGPPublicKey(1, publicKey, new Date());

            PGPPrivateKey pgpPrivateKey = jcaPGPKeyConverter.getPGPPrivateKey(pgpPublicKey, privateKey);
            PGPSecretKey pgpSecretKey = new PGPSecretKey(pgpPrivateKey, pgpPublicKey, null, true, null);

            final InMemoryKeyring keyring = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withPassword(p12Password));
            keyring.addPublicKey(pgpPublicKey.getEncoded());
            keyring.addSecretKey(pgpSecretKey.getEncoded());

            final OutputStream outputStream = BouncyGPG
                            .encryptToStream()
                            .withConfig(keyring)
                            .withStrongAlgorithms()
                            .toRecipient(email)
                            .andDoNotSign()
                            .binaryOutput()
                            .andWriteTo(bufferedOut);
David Fang
  • 47
  • 2
  • 7

2 Answers2

0

Okay, it took awhile because that project is apparently by a real 'kOOlaid drinker' -- related operations that could easily be done by maybe 10 lines of code in one class take 50 methods in 20 classes spread over multiple parts of a hierarchy, with lots of unneeded redispatching. To be clear, that is NOT BouncyCastle, which is mostly sane, though not perfect, and often not well commented; Bouncy-GPG is layered on top of BouncyCastle.

PGP keys can have associated metadata of several kinds -- normally userid(s) and signature(s), and rarely attributes. See RFC4880 section 11.1 and 11.2. A PGP key created by a real PGP program, like GnuPG, will always have this metadata added, although depending on the program it may be possible to remove it subsequently. Because PGP was originally designed to be used for email, traditionally and usually a userid contains an email address, optionally plus other info, but technically this is not required and the userid can be any string. A PGP key signature also contains 'subpackets' with other data in addition to the actual signature. Also PGP keys often -- nowadays almost always -- come in groups of one masterkey and one or more subkey(s) linked to that masterkey, as also shown in section 11.1 linked above. In this case the userids apply to (and are the same for) all keys in the group, but the signatures are separate for each actual key and/or userid, and normally there is at least one signature on each actual key by the 'owner' i.e. the entity identified by the userid(s) (always by the masterkey) which among other things contains subpackets defining the key's expiration (if any) and usage flags. As I noted on that other Q, BouncyCastle adds to the confusion by calling this group of related keys a {Public,Secret}KeyRing while other software uses that name for a file that can contain multiple such groups.

And most PGP programs allow you to select a key when you need to do so (for encryption or signing) by either the userid (or one of them) or a 'keyid' (or fingerprint or keygrip, depending on the program). In the former case if subkeys are used it automatically selects the appropriate one based on usage (i.e. encryption or signing); in the latter case usually you just use the keyid of the masterkey and let it choose the subkey, but you can specify a particular (sub)key by keyid if appropriate. (For decryption and verifying, which need to use the same actual key as the previous encryption or signing, it normally selects the correct (sub)key automatically by keyid, but in some cases it may be necessary or possible to select manually.) In particular BouncyCastle at the level it calls a KeyRingCollection lets you select a key group which it calls a KeyRing either by userid or by keyid: see the javadoc for PGPPublicKeyRingCollection and PGPSecretKeyRingCollection.

But Bouncy-GPG doesn't: it uses only the former option and selects either by the whole userid, or the email part of the userid, but not by keyid. And the keys converted from JCA by JcaPGPKeyConverter have no userid so they cannot be selected by userid or email, and thus can't be used by Bouncy-GPG for an operation that needs such selection.

There are two ways to approach this. These key objects can be used with BouncyCastle directly, with code similar to that in their example KeyBasedFileProcessor except using the converted keys instead of ones selected from a 'KeyRingCollection' (normally a keyring file) as the example does.

The other approach is instead of using the simple PGP{Public,Secret}Key objects, which correspond to only the actual key, to create the KeyRing's with metadata. This got me into some of the not-commented areas which took a while and left several things uncertain, but I think something like this will work:

    String userid = "<email@host>"; // Bouncy-GPG defaults to email, but can be changed for other userids
    PGPSignatureSubpacketGenerator hsub = new PGPSignatureSubpacketGenerator();
    hsub.setKeyFlags(false, 0xF); // should be enough for masterkey-only, I hope
    // don't bother with expiration, features, or (multiple) preferences, for now at least
    // the generator below automatically adds hashed 2=signtime and unhashed 16=issuerkeyid
    PGPKeyRingGenerator gen = new PGPKeyRingGenerator(
            PGPSignature.DEFAULT_CERTIFICATION, new PGPKeyPair(pub2,prv2), userid,
            new JcaPGPDigestCalculatorProviderBuilder().build().get(HashAlgorithmTags.SHA1), 
            hsub.generate(), /*unhash*/null, 
            new JcaPGPContentSignerBuilder(pub2.getAlgorithm(), HashAlgorithmTags.SHA1), 
            new JcePBESecretKeyEncryptorBuilder(SymmetricKeyAlgorithmTags.NULL).build(new char[0]) );
    // I'm not sure ContentSigner will work right for all key-algos with SHA1, may need to tweak;
    // if so I don't _think_ DigestCalculator has to match ContentSigner, but I could be wrong
    // use gen.generatePublicKeyRing() directly or its .getEncoded() see below
    // and gen.generateSecretKeyRing() directly or its .getEncoded() see below

Note the Bouncy-GPG InMemoryKeyRing.add{Public,Secret}Key methods actually parse their (encoded) argument as a KeyRing (in the BC meaning) not just a simple key (which is technically a minimal case of such a group), or you can call add{Public,Secret}KeyRing with the (structured) objects from BC's generate{Public,Secret}KeyRing directly.

ADDED: I meant to say, but forgot, this design smells. The whole point of public-key encryption is that we normally encrypt with the publickey belonging to another person or entity (or several others) and we should never have the privatekey for such other person's (public)key, only our own key(s). This is true both for PGP format and trust model and for others like PKCS12 and X.509. For email, which PGP was designed and is often used for, there is a partial exception where we may want to encrypt to another person or persons and also the same message to ourself (and save it) so that we can later check exactly what was sent in case of miscommunication or disagreement. But you aren't doing that; you are encrypting only to a key for which you have the privatekey, which provides no security benefit and makes no sense.

Whew.

Community
  • 1
  • 1
dave_thompson_085
  • 34,712
  • 6
  • 50
  • 70
0

Modified code:

            hsub.setKeyFlags(false, 0xF);

            PGPKeyRingGenerator gen = new PGPKeyRingGenerator(
                    PGPSignature.DEFAULT_CERTIFICATION,
                    new PGPKeyPair(pgpPublicKey, pgpPrivateKey),
                    '<' + email + '>',
                    null,
                    null,
                    null,
                    new JcaPGPContentSignerBuilder(pgpPublicKey.getAlgorithm(), HashAlgorithmTags.SHA1),
                    new JcePBESecretKeyEncryptorBuilder(SymmetricKeyAlgorithmTags.NULL).build(p12Password.toCharArray())
            );

            //PGPSecretKey pgpSecretKey = new PGPSecretKey(pgpPrivateKey, pgpPublicKey, null, true, null);

            final InMemoryKeyring keyring = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withPassword(p12Password));
            keyring.addPublicKey(gen.generatePublicKeyRing().getEncoded());
            keyring.addSecretKey(gen.generateSecretKeyRing().getEncoded());

            try (
                    final InputStream cipherTextStream = Files.newInputStream(sourceFile);

                    final OutputStream fileOutput = Files.newOutputStream(destFile);
                    final BufferedOutputStream bufferedOut = new BufferedOutputStream(fileOutput, BUFFERSIZE);

                    final InputStream plaintextStream = BouncyGPG
                            .decryptAndVerifyStream()
                            .withConfig(keyring)
                            .andValidateSomeoneSigned()
                            .fromEncryptedInputStream(cipherTextStream)
David Fang
  • 47
  • 2
  • 7