How to read PKCS8 file with empty password using BouncyCastle

Viewed 333

I can generate a PKCS#8 encrypted file with empty password using OpenSSL:

~ $ openssl pkcs8 -topk8 -in ca_private.pem 
Enter pass phrase for ca_private.pem:
Enter Encryption Password: <ENTER>
Verifying - Enter Encryption Password: <ENTER>
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIBvTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIsWq90VBNFMwCAggA
MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBCFtKOCdDUeRohccAqQZaDIBIIB
YG+ohLBKQ766BTCXXZ7wyAP1l0grcQPgnzI2XmEj33rBIMogS6l3oAN3Faos2I6n
PcUY+aNLQtDSbvPzF4ozd0oWYBTa60iYGboQQ2RolhRRTzNW6K2tWBWUB35v2rLV
VYu7xJMX+dr/PxzhEgaQ4Nerb7v7/EAn4fLv3zcW9f/tPbljKUAiKc/YYP+GjRjA
GyJThdVpyeK6Jflobc3V8gqL8Gk0MgeHmXuUR1+SthA6ia5havH7D/FMLvXxZtRK
CpWOQ8mJp7g7dbUf+qWTLX+dMPQFPZDEofdkCY2/J4dSkgNnPgp+1oxSVpEAAR9v
gWsRezU2KfFUEMIljYOT+s4ZhkeAGtA8qa8qnr0yv9uz1OkzFtrleNf0WV8wRqI7
azo/7ff9TbecseYlTRgR40nd2l3Z9RLMVhsS09vPffYDw3jt+Zqf3m7iEri6eSug
5bMcZTszaQsVT0HOfCcpQ1Q=
-----END ENCRYPTED PRIVATE KEY-----

When reading this with a PEMParser, it returns an instance of PKCS8EncryptedPrivateKeyInfo (as expected).

Trying to decrypt that with JceOpenSSLPKCS8DecryptorProviderBuilder, however, fails with

org.bouncycastle.pkcs.PKCSException: unable to read encrypted data: password empty

There is a hard coded check for empty password when creating the decryption key. Is there a way around this? OpenSSL surely deals just fine reading the PKCS#8 back...

Sample code:

import com.excelfore.api.lib.util.ApiCrypto;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder;
import org.bouncycastle.operator.InputDecryptorProvider;
import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo;

import java.io.FileReader;

class Scratch {
    public static void main(String[] args) throws Exception {

        try (FileReader fr = new FileReader(args[0])) {

            Object whatIsIt = new PEMParser(fr).readObject();
            if (whatIsIt instanceof PKCS8EncryptedPrivateKeyInfo) {
                PKCS8EncryptedPrivateKeyInfo pInfo = (PKCS8EncryptedPrivateKeyInfo) whatIsIt;
                InputDecryptorProvider provider =
                        new JceOpenSSLPKCS8DecryptorProviderBuilder().setProvider(ApiCrypto.bouncyCastleProvider).build(args[1].toCharArray());
                pInfo.decryptPrivateKeyInfo(provider);
                System.out.println("ok");
            } else {
                System.out.println("I don't want "+whatIsIt.getClass().getName());
            }
        }
    }
}
2 Answers

Not sure if there is what you expect or not (do not test yet with empty passphrase)

    public static KeyPair parseKeyPair(Path pemFile, String passPhrase) {
        try(PEMParser pemParser = new PEMParser(Files.newBufferedReader(pemFile))) {
            Object object = pemParser.readObject();
            JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(BouncyCastleProviderHolder.BC_PROVIDER);
            if(object instanceof PEMEncryptedKeyPair) {
                if(passPhrase == null)
                    throw new IllegalArgumentException("Pass phrase required for parsing RSA private key");

                PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build(passPhrase.toCharArray());
                return converter.getKeyPair(((PEMEncryptedKeyPair) object).decryptKeyPair(decProv));
            }
            if(object instanceof PEMKeyPair) {
                return converter.getKeyPair((PEMKeyPair) object);
            }
        } catch(Exception e) {
            throw new PicApplicationException("Couldn't parse pem to keypair", e);
        }

        throw new PicApplicationException("Couldn't parse pem to keypair");
    }

You may pass empty passphrase to this line:

PEMDecryptorProvider decProv = new JcePEMDecryptorProviderBuilder().build("".toCharArray());

The BouncyCastle provider will not use an empty passphrase for PKCS8 decryption (actually any PBE). The SunJCE provider will, BUT you cannot specify it explicitly as the provider to use, because it doesn't handle AlgorithmParameters for the cipher by OID for PBES2; instead you must let the DecryptorProvider use the default JCA search and have Bouncy provider in that search after SunJCE; the simplest way is to put it at the end with Security.addProvider:

    Security.addProvider(new BouncyCastleProvider());
    try( Reader r = new FileReader(args[0]); PEMParser p = new PEMParser(r) ) {
        JceOpenSSLPKCS8DecryptorProviderBuilder b = new JceOpenSSLPKCS8DecryptorProviderBuilder();
        //DON'T:if( args.length>1 ) b.setProvider(args[1]);
        InputDecryptorProvider d = b.build(new char[0]);
        PrivateKeyInfo k0 = ((PKCS8EncryptedPrivateKeyInfo)p.readObject()).decryptPrivateKeyInfo(d);
        PrivateKey k1 = new JcaPEMKeyConverter().getPrivateKey(k0);
        System.out.println (k1.getAlgorithm());
    }
Related