2

I have the following code to extract Private Key

    PEMParser parser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(decoded)));
    Object object = parser.readObject();
    PEMDecryptorProvider provider = new JcePEMDecryptorProviderBuilder()
            .build(props.getProperty(KeytoolFlags.KEYPASS.name()).toCharArray());

    JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME);
    if (object instanceof PEMEncryptedKeyPair) {
        KeyPair pair = converter.getKeyPair(((PEMEncryptedKeyPair) object).decryptKeyPair(provider));
        return loadPublic ? pair.getPublic() : pair.getPrivate();
    } else if (object instanceof PEMKeyPair) {
        return loadPublic ? converter.getPublicKey(((PEMKeyPair) (object)).getPublicKeyInfo())
                : converter.getPrivateKey(((PEMKeyPair) (object)).getPrivateKeyInfo());
    } else {
        InputDecryptorProvider p2 = new JceOpenSSLPKCS8DecryptorProviderBuilder()
                .setProvider(BouncyCastleProvider.PROVIDER_NAME)
                .build(props.getProperty(KeytoolFlags.KEYPASS.name()).toCharArray());
        return converter.getPrivateKey(((PKCS8EncryptedPrivateKeyInfo) object).decryptPrivateKeyInfo(p2));
    }

I would like to get the Public Key from converter when it's JceOpenSSLPKCS8DecryptorProviderBuilder. Is there any way?

Thanks,

ha9u63a7
  • 6,233
  • 16
  • 73
  • 108

2 Answers2

6

The simplest way, although it feels rather ugly to me, is to convert the private key 'back' to one of OpenSSL's 'legacy' forms, which PEMParser is then able to turn into a PEMKeyPair with both halves, from which the public can be selected. Otherwise, the method must be tailored depending on the key algorithm aka type, but can be more efficient which I like better. Here are both options for your consideration:

public static void SO57043669PKCS8_Public_BC (String[] args) throws Exception {
    Object p8e = new PEMParser (new FileReader (args[0])).readObject();
    // for PKCS8-encrypted result is org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo 
    PrivateKeyInfo p8i = ((PKCS8EncryptedPrivateKeyInfo)p8e).decryptPrivateKeyInfo(
            new JceOpenSSLPKCS8DecryptorProviderBuilder().build(args[1].toCharArray()) );
    // or get org.bouncycastle.asn1.pkcs.PrivateKeyInfo directly from PEMParser for PKCS8-clear
    PublicKey pub = null;
    if( args.length>=3 ){ // the simple way:
        PrivateKey prv = new JcaPEMKeyConverter().getPrivateKey(p8i);
        PemObject old = new JcaMiscPEMGenerator (prv,null).generate();
        StringWriter w1 = new StringWriter(); 
        PemWriter w2 = new PemWriter(w1);
        w2.writeObject(old); w2.close();
        Object pair = new PEMParser(new StringReader(w1.toString())).readObject();
        pub = new JcaPEMKeyConverter().getKeyPair((PEMKeyPair)pair).getPublic();
    }else{
        ASN1ObjectIdentifier id = p8i.getPrivateKeyAlgorithm().getAlgorithm();
        PKCS8EncodedKeySpec p8s = new PKCS8EncodedKeySpec (p8i.getEncoded());
        if( id.equals(PKCSObjectIdentifiers.rsaEncryption) ){
            // the standard PKCS1 private key format for RSA redundantly includes e
            KeyFactory rfact = KeyFactory.getInstance("RSA");
            RSAPrivateCrtKey rprv = (RSAPrivateCrtKey) rfact.generatePrivate(p8s);
            // or JcaPEMKeyConverter.getPrivateKey does the same thing
            pub = /*(RSAPublicKey)*/ rfact.generatePublic(
                    new RSAPublicKeySpec (rprv.getModulus(), rprv.getPublicExponent()));
        }else if( id.equals(X9ObjectIdentifiers.id_dsa) ){
            // the apparently ad-hoc format OpenSSL uses for DSA does not include y but it can be computed
            KeyFactory dfact = KeyFactory.getInstance("DSA");
            DSAPrivateKey dprv = (DSAPrivateKey) dfact.generatePrivate(p8s);
            // or JcaPEMKeyConverter.getPrivateKey does the same thing
            BigInteger p = dprv.getParams().getP(), q = dprv.getParams().getQ(), g = dprv.getParams().getG();
            pub = /*(DSAPublicKey)*/ dfact.generatePublic (
                    new DSAPublicKeySpec(g.modPow(dprv.getX(),p), p, q, g) );
            // warning: naive computation probably vulnerable to sidechannel attack if any  
        }else if( id.equals(X9ObjectIdentifiers.id_ecPublicKey) ){
            // the SECG SEC1 format for EC private key _in PKCS8 by OpenSSL_ 
            // includes []] BITSTR(Q) (but not [0] params which is already in the PKCS8 algid)
            org.bouncycastle.asn1.sec.ECPrivateKey eprv = org.bouncycastle.asn1.sec.ECPrivateKey.getInstance(p8i.parsePrivateKey());
            byte[] eenc = new SubjectPublicKeyInfo (p8i.getPrivateKeyAlgorithm(), eprv.getPublicKey().getOctets()).getEncoded(); 
            KeyFactory efact = KeyFactory.getInstance("EC");
            pub = /*(ECPublicKey)*/ KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(eenc));
            //}else if maybe others ...
        }else throw new Exception ("unknown private key OID " + id);
    }
    System.out.println (pub.getAlgorithm() + " " + pub.getClass().getName());
}
dave_thompson_085
  • 34,712
  • 6
  • 50
  • 70
  • uniquely done! Thanks. For now I think I will try and use the public key separately. The reason for this is that we are loading the keys from DB. We have two variants - JKS and PKCS8 (PEM encoded). I was hoping that saving a private key entry should be sufficient. but looks like for PKCS8 format keys are meant to be kept as public and private keys separately. I will go with that approach for now. – ha9u63a7 Aug 05 '19 at 13:41
1

In addition to the other answer, here's a way to convert Ed25519 private keys to public keys.

Edit: As @tytk notes in the comments, given a BCEdDSAPrivateKey, you can just call getPublicKey(). I am not entirely sure if it's possible to obtain a private EdDSAKey that wouldn't be BCEdDSAPrivateKey, using BC or otherwise, but just in case I'm leaving an alternative codepath that works.

private val bouncyCastleProvider = BouncyCastleProvider()
private val pkcs8pemKeyConverter = JcaPEMKeyConverter().setProvider(bouncyCastleProvider)

fun makeKeyPair(keyReader: Reader, passphrase: CharArray): KeyPair {
    var obj = PEMParser(keyReader).readObject()
    ...
    if (obj is PrivateKeyInfo) {
        val privateKey = pkcs8pemKeyConverter.getPrivateKey(obj)

        when (privateKey) {
            is BCEdDSAPrivateKey -> return KeyPair(privateKey.publicKey, privateKey)
            is EdDSAKey -> return KeyPair(genEd25519publicKey(privateKey, obj), privateKey)
        }
        ...
    }
    ...
}

private fun genEd25519publicKey(privateKey: EdDSAKey, privateKeyInfo: PrivateKeyInfo): PublicKey {
    val privateKeyRaw = ASN1OctetString.getInstance(privateKeyInfo.parsePrivateKey()).octets
    val privateKeyParameters = Ed25519PrivateKeyParameters(privateKeyRaw)
    val publicKeyParameters = privateKeyParameters.generatePublicKey()
    val spi = SubjectPublicKeyInfo(privateKeyInfo.privateKeyAlgorithm, publicKeyParameters.encoded)
    val factory = KeyFactory.getInstance(privateKey.algorithm, bouncyCastleProvider)
    return factory.generatePublic(X509EncodedKeySpec(spi.encoded))
}

squirrel
  • 5,114
  • 4
  • 31
  • 43
  • 1
    if `privateKey` is an instance of `EdDSAPrivateKey` then you can call `getPublicKey()`. – tytk Nov 25 '22 at 21:59
  • @tytk Weirdest thing, a few hours ago I randomly run into an issue with the above code (I have a strong suspicion that it's incorrect!) and about at the same time you post a comment that actually helps me. What a timing... I'll be trying to understand what exactly is wrong with the code – squirrel Nov 26 '22 at 01:09