'Parsing encrypted PKCS#8 encoded pem file programatically

I am having trouble figuring out how to properly read a private key of a pem file. I have gone through different topics on stackoverflow, but I couldn't find the solution for it. What I want to achieve is reading an encrypted private key in pkcs#8 encoding file from a classpath and load it as a key-entry in a in memory keystore. Below is an example private key which I try to parse, the password is secret. It is purely created for sharing here, so it is not a private key which is used on a production machine.

I created it from a p12 file with the following command: openssl pkcs12 -in key-pair.p12 -out key-pair.pem

Valid example (throw-away) key-pair

-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI9/FSBonUacYCAggA
MBQGCCqGSIb3DQMHBAidGkS8wYhOpwSCBMi8JaSYOKudMNNFRpzL7QMIZgFtzDay
MmOroy3lW34dOa7dusqDl4d2gklKcHCpbEaTYxm5aQJ1LuiOdGtFy7HwxOvKU5xz
4qsJoeBIpE0eCTKjQW7/I38DzLXx2wUURqhMWOtFsWZEyR5Dqok3N9mIKKKBXAFG
AwNjlTRW2LyPSttiIUGN01lthjifMWoLTWB1aSGOmGeJRBdSZeqZ15xKneR4H5ja
yE88YcpOHCDKMIxi6ZVoKs7jDQhu8bBKqS8NsYyh1AlP9QkvWNal36jWSzhqYNzk
NRWUOZngfkdbMALVfRtbrC215jHGWVwosPIIs8rkoarRv8s6QWS1Rg3YfQ3qgcRf
s7hkDFKJf3TUXr+askfamV5hc300ZG64+ldX1YxWXY8Vd/wIvHAc/YE/lTyCgYrY
19Am6MNBfp8/kXvzKj+PizB8oNDO4S8sSShEEzOQ5a/+MTC6bqB0DLWYGUqRbjLc
PyYTC2C4i9Agx/GeGVE3c1UdtXiwwnt2XUn7Y1YGqABk0xGIY4J1NFTbSOxKl9hO
arwopAFrZU5nsjjFzv1DJvhfQWnYX18kPSKNHDlia019M118qZ8ERwD9tH8ix9Fa
R2tQdxn1aRGmvXSw+zFkbWD8aWs9n/B+QN1yllJqVoWypOld1yj+fVYYnYOtV1gK
eiygrtrh3JJCvLbEQl4nOgJM3PlEtfBHSaunehIXQMD1z/NDUqgBYjuDPyqRxJeH
Va5k72Nds5PeySKJJnICB3nZKjqgfLhNUrXa1SAQ4vqr0Ik/Lu9P7T+B1XiYwuUT
a20+bxi/x89ZZqwp3jnDuHup7XcO1MtqsoOKP/JgkjVMesb8Q1W8i2dXzg+l4gkk
l1ipreEGtT1YfFTq0DFelz6CjZFLDlGGeGWob94sW94DWTW0nsLPhQWEnwW1CcyJ
oJbJdDEgdiIbRJoABDkTuVXLwTlgzHSHh6zeJvNvcojI7UI3nWYCVYvD3kwghXiP
67sKGL3ug7PFDqLia46AudGY7CFh4+wpxyH+fidLC3FMdkDBA6xR6mGgEjRLXR9M
TnJ/eSYP7eqYZeKn9EarcI7v1zM2IG0/PDQCetiI0ABiHpdKyRQuuiEavp3xC5Vi
h7UmJNYt8Zsz3rwqAQ4FR2+Su5R34OOdRmxTaYLe96PXTpLcLef5TkYixSY7Tzgd
PMyRxRPrywklUEFe4KK/KOcdolxybfsIsxQnupLAMEsO7/Cs7mouNHISK51haDRc
vNbKQ5E4xOq1U4ThW5dHR29cGZillfmMzj05ZQh3ZX2TQJP45ahFET3v9kInWCwQ
8atqclVPOSnASsJZ0PxjYgKZuY8QWYM6zpfWyWnfu/CHhWbRS/qX8T1ow2SMyPBL
CQbZ+MhcdP0IrjoXhDFQsns16i/BPK5TTVqtEC2ywDf5P4/BOEZkySG9YNOd6THp
VA/dVPafzmLy3ltqH+jG8ZH2+RtWx7kwBjiDWs5cF33BFrPS7AZlzMzZoCHLXD/r
T/SmisybUKHMqri0x0RHeIByW0hogSByWiyIn8POabDzJV6Df9nQPziDGcSsvWfG
7q+hizh6+nnXOY+GZx3ptwg9mA9R4QyCiFNQradOaXSPxyEL2IC77/srFfVEIaU4
SRo=
-----END ENCRYPTED PRIVATE KEY-----

-----BEGIN CERTIFICATE-----
MIIDTjCCAjagAwIBAgIEVnHI3TANBgkqhkiG9w0BAQsFADBIMQswCQYDVQQGEwJO
TDEVMBMGA1UEChMMVGh1bmRlcmJlcnJ5MRIwEAYDVQQLEwlBbXN0ZXJkYW0xDjAM
BgNVBAMTBUhha2FuMB4XDTIwMDgzMTA4MDczOVoXDTMwMDgyOTA4MDczOVowSDEL
MAkGA1UEBhMCTkwxFTATBgNVBAoTDFRodW5kZXJiZXJyeTESMBAGA1UECxMJQW1z
dGVyZGFtMQ4wDAYDVQQDEwVIYWthbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAJZ+pqirEqEk4k1ow8vryld79oCO4P/1y+v60CkLpS2MpeHE3BogTr7g
WWP5HdHBMU5l8yT5tXuyVZZqgyzo0q2Sdm7GGrNunHf6Z8vPQFu69sQC1utDM9u8
ZFKiKsTNJ+5QS6KsOtlACyhaLoaPAWVjtvueMjVwmM9hfv/Gq6VyyuBm2x1C4HTj
zPCLHE2G1D13EJaWsvyZLGSbl0GGXZGPhaDd/vnw5TW36mvNTWW+37ZIEk4kXANa
FUNsJemm8HjB/PzHs3/SXGxuD8NKobg3+cNXYwAz2s2DI0W6Xw2g5bbrMQAdBRvn
9/kNftMymDORw3RGwDM2ld4zQfIkNrkCAwEAAaNAMD4wHQYDVR0OBBYEFHhT7ATg
oVVFIsglxD/1iUBRB6gDMB0GA1UdEQEB/wQTMBGCCWxvY2FsaG9zdIcEfwAAATAN
BgkqhkiG9w0BAQsFAAOCAQEAhVWH/CgZ0ZNmTfiFAOnWdaZVaa7vAFPT2YbXuvlY
YIRlru0B/zn2Mfwmn5W2o1CqoBuhyfErkkF4aRM1vduIirUjlcH4+cFXrV2gtlnf
eWTg/sJJmYzkJTGeOIqRlB1HKCqoeNCrykkcsikECQ1nCqr1qLh9DXsUgWVW57YW
qvP1P8xOO2/J9shMB6lOhftpawrqZ2hNG8fqMKjVVuUpFBNR+WODQ/rRRtqa6uU2
V8aOOZx1QJUkTdN76YOCuGET7edevjpdbRXde+HQN6mbT9OLxSZHO0aQrDyDmNhp
aVHuQn/KtYNWCZ78XKK8wtVnflmfqE/c9xO1n/EcVvLCdg==
-----END CERTIFICATE-----

I know BouncyCastle is able to parse it, but I want to avoid using additional libraries. So I am wondering if it is possible with just plain jdk or with some other lightweight libraries.

I am already able to parse private key with the following header/footer:

-----BEGIN PRIVATE KEY-----
           *
-----END PRIVATE KEY-----

and

-----BEGIN RSA PRIVATE KEY-----
           *
-----END RSA PRIVATE KEY-----

I already use the following snippet to accomplish that:

import com.hierynomus.asn1.ASN1InputStream;
import com.hierynomus.asn1.encodingrules.der.DERDecoder;
import com.hierynomus.asn1.types.ASN1Object;
import com.hierynomus.asn1.types.constructed.ASN1Sequence;
import com.hierynomus.asn1.types.primitive.ASN1Integer;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.KeyFactory;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

class App {

    private static final String KEYSTORE_TYPE = "PKCS12";
    private static final String KEY_FACTORY_ALGORITHM = "RSA";
    private static final String CERTIFICATE_TYPE = "X.509";

    private static final Pattern CERTIFICATE_PATTERN = Pattern.compile("-----BEGIN CERTIFICATE-----(.*?)-----END CERTIFICATE-----", Pattern.DOTALL);
    private static final Pattern PRIVATE_KEY_PATTERN = Pattern.compile("-----BEGIN PRIVATE KEY-----(.*?)-----END PRIVATE KEY-----", Pattern.DOTALL);
    private static final Pattern ENCRYPTED_PRIVATE_KEY_PATTERN = Pattern.compile("-----BEGIN ENCRYPTED PRIVATE KEY-----(.*?)-----END ENCRYPTED PRIVATE KEY-----", Pattern.DOTALL);
    private static final Pattern RSA_PRIVATE_KEY_PATTERN = Pattern.compile("-----BEGIN RSA PRIVATE KEY-----(.*?)-----END RSA PRIVATE KEY-----", Pattern.DOTALL);

    private static final String NEW_LINE = "\n";
    private static final String EMPTY = "";

    public static void main(String[] args) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, InvalidKeySpecException {
        String privateKeyContent = "";
        String certificateContent = "";

        PrivateKey privateKey = parsePrivateKey(privateKeyContent);
        Certificate[] certificates = parseCertificate(certificateContent).values()
                .toArray(new Certificate[]{});

        KeyStore keyStore = createEmptyKeyStore();
        keyStore.setKeyEntry("client", privateKey, null, certificates);
    }

    private static KeyStore createEmptyKeyStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
        KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
        keyStore.load(null, null);
        return keyStore;
    }

    private static PrivateKey parsePrivateKey(String identityContent) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
        KeySpec keySpec = null;

        Matcher privateKeyMatcher = PRIVATE_KEY_PATTERN.matcher(identityContent);
        if (privateKeyMatcher.find()) {
            String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
            byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
            keySpec = new PKCS8EncodedKeySpec(decodedPrivateKeyContent);
        }

        privateKeyMatcher = RSA_PRIVATE_KEY_PATTERN.matcher(identityContent);
        if (privateKeyMatcher.find()) {
            String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
            byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
            keySpec = getKeySpecFromAsn1StructuredData(decodedPrivateKeyContent);
        }

        privateKeyMatcher = ENCRYPTED_PRIVATE_KEY_PATTERN.matcher(identityContent);
        if (privateKeyMatcher.find()) {
            String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
            byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
            keySpec = null; //TODO
        }

        Objects.requireNonNull(keySpec);

        KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
        return keyFactory.generatePrivate(keySpec);
    }

    private static KeySpec getKeySpecFromAsn1StructuredData(byte[] decodedPrivateKeyContent) throws IOException {
        try(ByteArrayInputStream privateKeyAsInputStream = new ByteArrayInputStream(decodedPrivateKeyContent)) {
            ASN1InputStream stream = new ASN1InputStream(new DERDecoder(), privateKeyAsInputStream);
            ASN1Sequence asn1Sequence = stream.readObject();

            if (asn1Sequence.getValue().size() < 9) {
                throw new RuntimeException("Parsed key content doesn't have the minimum required sequence size of 9");
            }

            BigInteger modulus = extractIntValueFrom(asn1Sequence.get(1));
            BigInteger publicExponent = extractIntValueFrom(asn1Sequence.get(2));
            BigInteger privateExponent = extractIntValueFrom(asn1Sequence.get(3));
            BigInteger primeP = extractIntValueFrom(asn1Sequence.get(4));
            BigInteger primeQ = extractIntValueFrom(asn1Sequence.get(5));
            BigInteger primeExponentP = extractIntValueFrom(asn1Sequence.get(6));
            BigInteger primeExponentQ = extractIntValueFrom(asn1Sequence.get(7));
            BigInteger crtCoefficient = extractIntValueFrom(asn1Sequence.get(8));

            return new RSAPrivateCrtKeySpec(
                    modulus, publicExponent, privateExponent,
                    primeP, primeQ, primeExponentP, primeExponentQ, crtCoefficient
            );
        }
    }

    private static BigInteger extractIntValueFrom(ASN1Object<?> asn1Object) {
        if (asn1Object instanceof ASN1Integer) {
            return ((ASN1Integer) asn1Object).getValue();
        } else {
            throw new RuntimeException(String.format(
                    "Unable to parse the provided value of the object type [%s]. The type should be an instance of [%s]",
                    asn1Object.getClass().getName(), ASN1Integer.class.getName())
            );
        }
    }

    private static Map<String, Certificate> parseCertificate(String certificateContent) throws IOException, CertificateException {
        Map<String, Certificate> certificates = new HashMap<>();
        Matcher certificateMatcher = CERTIFICATE_PATTERN.matcher(certificateContent);

        while (certificateMatcher.find()) {
            String sanitizedCertificate = certificateMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
            byte[] decodedCertificate = Base64.getDecoder().decode(sanitizedCertificate);
            try(ByteArrayInputStream certificateAsInputStream = new ByteArrayInputStream(decodedCertificate)) {
                CertificateFactory certificateFactory = CertificateFactory.getInstance(CERTIFICATE_TYPE);
                Certificate certificate = certificateFactory.generateCertificate(certificateAsInputStream);
                certificates.put(UUID.randomUUID().toString(), certificate);
            }
        }

        return certificates;
    }

}

I also am required to handle asn.1 encoded private key. I could use BouncyCastle, but again I wanted to avoid it because I am curious for alternatives. I got inspired from this topic: https://stackoverflow.com/a/42733858/6777695 but DerInputStream and DerValue aren't accessible anymore from jdk 11 onwards. And the sun packages should be avoided... So I discovered asn-one java library to parse asn.1 encoded private key, and it works quite well.

I have shared my full snippet of handling the other cases, so it will be easier to try out when someone wants to help me.

From the following article: ASN.1 key structures in DER and PEM I learned that a private key having the following header/footer: -----BEGIN ENCRYPTED PRIVATE KEY----- * -----END ENCRYPTED PRIVATE KEY----- is also an asn.1 encoded private key with the following structure:

EncryptedPrivateKeyInfo ::= SEQUENCE {
  encryptionAlgorithm  EncryptionAlgorithmIdentifier,
  encryptedData        EncryptedData
}

EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier

EncryptedData ::= OCTET STRING

I also tried the answers provided here How to read a password encrypted key with java? but those also didn't work. However I am not quite sure how to parse it correctly to a java object and load it as a KeySpec. So any help is welcome!

======> Updated progress at 12-09-2020 based on the input of dave_thompson_085

Matcher privateKeyMatcher = Pattern.compile("-----BEGIN ENCRYPTED PRIVATE KEY-----(.*?)-----END ENCRYPTED PRIVATE KEY-----", Pattern.DOTALL)
        .matcher(identityContent);

if (privateKeyMatcher.find()) {
    String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
    byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
    try (ByteArrayInputStream privateKeyAsInputStream = new ByteArrayInputStream(decodedPrivateKeyContent)) {
        ASN1InputStream stream = new ASN1InputStream(new DERDecoder(), privateKeyAsInputStream);
        ASN1Sequence asn1Sequence = stream.readObject();

        ASN1OctetString privateKeyAsOctetString = (ASN1OctetString) asn1Sequence.get(1);
        ASN1OctetString saltAsOctetString = (ASN1OctetString) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(0)).get(1)).get(0);
        ASN1OctetString initializationVectorAsOctec = ((ASN1OctetString) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(1)).get(1));
        ASN1Integer iterationCount = (ASN1Integer) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(0)).get(1)).get(1);

        IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVectorAsOctec.getValue());

        SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1");
        EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(secretKeyFactory.getAlgorithm(), privateKeyAsOctetString.getValue());

        int keyLength = encryptedPrivateKeyInfo.getEncoded().length;
        PBEKeySpec pbeKeySpec = new PBEKeySpec(keyPassword, saltAsOctetString.getValue(), iterationCount.getValue().intValue(), keyLength);

        Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding/");

        SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec);

        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
        PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher);
    }
}

Results into the following exception: java.security.InvalidKeyException: Wrong algorithm: DESede or TripleDES required on the statement cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);

======> Updated progress at 13-09-2020 based on the input of Michael Fehr

Matcher privateKeyMatcher = Pattern.compile("-----BEGIN ENCRYPTED PRIVATE KEY-----(.*?)-----END ENCRYPTED PRIVATE KEY-----", Pattern.DOTALL)
        .matcher(identityContent);

if (privateKeyMatcher.find()) {
    String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
    byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
    try (ByteArrayInputStream privateKeyAsInputStream = new ByteArrayInputStream(decodedPrivateKeyContent)) {
        ASN1InputStream stream = new ASN1InputStream(new DERDecoder(), privateKeyAsInputStream);
        ASN1Sequence asn1Sequence = stream.readObject();

        ASN1OctetString privateKeyAsOctetString = (ASN1OctetString) asn1Sequence.get(1);
        ASN1OctetString saltAsOctetString = (ASN1OctetString) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(0)).get(1)).get(0);
        ASN1OctetString initializationVectorAsOctec = ((ASN1OctetString) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(1)).get(1));
        ASN1Integer iterationCount = (ASN1Integer) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(0)).get(1)).get(1);

        IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVectorAsOctec.getValue());

        SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1");
        EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(secretKeyFactory.getAlgorithm(), privateKeyAsOctetString.getValue());

        int keyLength = 24 * 8;
        PBEKeySpec pbeKeySpec = new PBEKeySpec(keyPassword, saltAsOctetString.getValue(), iterationCount.getValue().intValue(), keyLength);

        Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding/");

        SecretKey secretKeyPbkdf = secretKeyFactory.generateSecret(pbeKeySpec);
        SecretKey secretKey = new SecretKeySpec(secretKeyPbkdf.getEncoded(), "DESede");

        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
        PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher);
    }
}


Solution 1:[1]

A more official reference for this PEM format is rfc7468 section 11 which specifies that the ASN.1 content (after de-PEM) is PKCS8's encrypted form defined in rfc5208 section 6 and appendix A which modifies it slightly to:

EncryptedPrivateKeyInfo ::= SEQUENCE {
    encryptionAlgorithm AlgorithmIdentifier {{KeyEncryptionAlgorithms}},
    encryptedData EncryptedData
}

EncryptedData ::= OCTET STRING

(redacted)

KeyEncryptionAlgorithms ALGORITHM-IDENTIFIER ::= {
     ... -- For local profiles
}

which fails to explain that the key-encryption algorithms commonly used for PKCS8 (except in PKCS12) are the password-based encryptions defined in PKCS5 v2 rfc2898 or now v2.1 rfc8018 (the minor differences don't matter here). In particular for the key file in your Q the AlgorithmIdentifier parses (with OpenSSL) as:

    4:d=1  hl=2 l=  64 cons:  SEQUENCE
    6:d=2  hl=2 l=   9 prim:   OBJECT            :PBES2
   17:d=2  hl=2 l=  51 cons:   SEQUENCE
   19:d=3  hl=2 l=  27 cons:    SEQUENCE
   21:d=4  hl=2 l=   9 prim:     OBJECT            :PBKDF2
   32:d=4  hl=2 l=  14 cons:     SEQUENCE
   34:d=5  hl=2 l=   8 prim:      OCTET STRING      [HEX DUMP]:F7F1520689D469C6
   44:d=5  hl=2 l=   2 prim:      INTEGER           :0800
   48:d=3  hl=2 l=  20 cons:    SEQUENCE
   50:d=4  hl=2 l=   8 prim:     OBJECT            :des-ede3-cbc
   60:d=4  hl=2 l=   8 prim:     OCTET STRING      [HEX DUMP]:9D1A44BCC1884EA7

PBES2 is defined in section 6.2 to consist of PBKDF2 key derivation followed by practically any symmetric encryption/decryption. You should be able to see that the top-level parameters match PBES2-Params in appendix A.4 and the parameters within the keyDerivationFunc part match PBKDF2-Params in appendix A.2 (salt and iterationCount present, keyLength and prf omitted), while the parameter for the encryptionScheme part is simply an OCTET STRING containing the Initialization Vector (IV), see appendix B.2.2.

Thus to decrypt this you need to implement PBKDF2 key derivation as specified in section 5.2 with those parameters, which Java crypto supports as SecretKeyFactory.getInstance("PBKDF2withHmacSHA1") and PBEKeySpec, and then triple-DES aka TDEA decryption with triple-length key aka keying option 1 in CBC mode with PKCS5/7 padding, which Java crypto supports as Cipher.getInstance("DESede/CBC/PKCS5Padding"), using the key derived from the first step and the IV from the input data as IVParameterSpec. The result will be a PKCS8-unencrypted key structure, which you can run through a suitable KeyFactory as PKCS8EncodedKeySpec, just as you already do for the unencrypted input format.

Although considering that a JRE is usually upward of 100MB to begin with, while Bouncy adds about 5MB and is shedloads easier to use, I can't say I agree with your assessment.


UPDATE: I didn't realize, but JCE's SKF for PBKDF2 doesn't actually do the key derivation, it only packages up the data elements for use by a suitable Cipher algorithm -- and it doesn't implement a Cipher algorithm for the case we need here. So you'll need to manually code PBKDF2 which fortunately(?) I already did for Decrypt file encrypted using openssl with aes-cbc-256 . Putting those together, here's tested code:

static void SO63832456ManualPKCS8EPBES2 (String[] args) throws Exception {
    String file = args[0], pass = args[1];
    // I use Bouncy to read because it's much easier for me, 
    // but you should get the same results with any ASN.1 lib
    PemObject obj; try(Reader rdr = new FileReader(file);
            PEMParser p = new PEMParser(rdr) ){ obj = p.readPemObject(); }
    if( ! obj.getType().equals("ENCRYPTED PRIVATE KEY") ) throw new Exception ("wrong type");
    org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo eki = org.bouncycastle.asn1.pkcs.EncryptedPrivateKeyInfo.getInstance(obj.getContent());
    AlgorithmIdentifier algid = eki.getEncryptionAlgorithm();
    if( ! algid.getAlgorithm().equals(PKCSObjectIdentifiers.id_PBES2) ) throw new Exception("not PBES2");
    PBES2Parameters top = PBES2Parameters.getInstance(algid.getParameters());
    KeyDerivationFunc kdf = top.getKeyDerivationFunc();
    if( ! kdf.getAlgorithm().equals(PKCSObjectIdentifiers.id_PBKDF2) ) throw new Exception("not PBKDF2");
    PBKDF2Params kdp = PBKDF2Params.getInstance(kdf.getParameters());
    EncryptionScheme esa = top.getEncryptionScheme();
    if( ! esa.getAlgorithm().equals(PKCSObjectIdentifiers.des_EDE3_CBC) ) throw new Exception("not DES3");
    ASN1OctetString esp = ASN1OctetString.getInstance(esa.getParameters());
    // these are the (only) items actually used
    byte[] salt = kdp.getSalt(), iv = esp.getOctets(), edata = eki.getEncryptedData(); 
    int iter = kdp.getIterationCount().intValueExact();
    
    //SecretKeyFactory fact = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1");
    //SecretKey skey = fact.generateSecret(new PBEKeySpec(pass.toCharArray(), salt, iter, 24));
    byte[] skey = PBKDF2 ("HmacSHA1", pass.getBytes("UTF-8"), salt, iter, 24);
    Cipher ciph = Cipher.getInstance("DESEDE/CBC/PKCS5Padding"); 
    ciph.init(Cipher.DECRYPT_MODE, new SecretKeySpec(skey,"DESEDE"), new IvParameterSpec(iv));
    byte[] plain = ciph.doFinal(edata);
    
    KeyFactory fac2 = KeyFactory.getInstance("RSA");
    PrivateKey pkey = fac2.generatePrivate(new PKCS8EncodedKeySpec(plain));
    System.out.println ( ((java.security.interfaces.RSAPrivateKey)pkey).getModulus() );
}

public static byte[] PBKDF2 (String prf, byte[] pass, byte[] salt, int iter, int len)
        throws NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException {
    byte[] result = new byte[len];
    Mac mac = Mac.getInstance(prf);
    mac.init (new SecretKeySpec (pass,prf));
    byte[] saltcnt = Arrays.copyOf (salt, salt.length+4);
    while( /*remaining*/len>0 ){
        for( int o = saltcnt.length; --o>=saltcnt.length-4; ) if( ++saltcnt[o] != 0 ) break; 
        byte[] u = saltcnt, x = new byte[mac.getMacLength()];
        for( int i = 1; i <= iter; i++ ){
            u = mac.doFinal (u); 
            for( int o = 0; o < x.length; o++ ) x[o] ^= u[o];
        }
        int len2 = Math.min (len, x.length);
        System.arraycopy (x,0, result,result.length-len, len2);
        len -= len2;
    }
    return result;
}

-->
18998199686208613730227071552042680566660281525162377778227794768879762338654464000476266705180956908841709177555966832423085875590556580445428066261332223955361646420713402269532603328557674453787190302285379571808527818182992417461094551685532849252957724361115461909983939654809533488667032982867230996411860328826589806804115764031480737011900431983025490936659008858316889367140757195359233313370592669605576636360123189540118611655293825465309125578750878878980002346272931570343463549347548711194000065166346537274382220211242975801253306067862461345994215674963367480607539333468674983712264803816027753559737

Bleah!

Solution 2:[2]

Your edited code gives an error "java.security.InvalidKeyException: Wrong key size" and that's because you "read" the keyLength with this line:

int keyLength = encryptedPrivateKeyInfo.getEncoded().length;

With the given private key the length would be 1247 bits and that is not an allowed TripleDES length. You can fix it to (hardcoded):

int keyLength = 24 * 8;

Now we get a keyLength of 192 bit = 24 byte and that's ok for an TripleDES-key but we receive a new error:

java.security.InvalidKeyException: Wrong algorithm: DESede or TripleDES required

The reason for this behavior is the following line (commented out) that can get fixed with the 2 additional lines:

// SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec); // ### old routine
SecretKey secretKeyPbkdf = secretKeyFactory.generateSecret(pbeKeySpec); // ### new
SecretKey secretKey = new SecretKeySpec(secretKeyPbkdf.getEncoded(), "DESede"); // ### new

Now your code is successfully running and print outs the private key and the certificate:

original keyLength:  1247
fixed keyLength(bit): 192

privateKey:
SunRsaSign RSA private CRT key, 2048 bits
  params: null
  modulus: 18998199686208613730227071552042680566660281525162377778227794768879762338654464000476266705180956908841709177555966832423085875590556580445428066261332223955361646420713402269532603328557674453787190302285379571808527818182992417461094551685532849252957724361115461909983939654809533488667032982867230996411860328826589806804115764031480737011900431983025490936659008858316889367140757195359233313370592669605576636360123189540118611655293825465309125578750878878980002346272931570343463549347548711194000065166346537274382220211242975801253306067862461345994215674963367480607539333468674983712264803816027753559737
  private exponent: 2470690692670644289832084636740463653655882622624760758103597888447170520657826825702415751838767348735186037205082706146786714644526202528897926495648786865000645626802269367582073915029633022103975204027927584273373553784482725392082470421529769049284506228997163157587212073758970565083984956787424047216319298607371397767542633302071090323391531162434678662485530085857362259661656308472034152915312425224731842425134593468185574918070451135641322780271191791839285884885643517240131520070881951542294552185519522325857178404160441369354693465035929601010771762928436963178293113507661955562266846499005032897533

number of certificates: 1

certificate: 0 data:
[
[
  Version: V3
  Subject: CN=Hakan, OU=Amsterdam, O=Thunderberry, C=NL
  Signature Algorithm: SHA256withRSA, OID = 1.2.840.113549.1.1.11

  Key:  Sun RSA public key, 2048 bits
  params: null
  modulus: 18998199686208613730227071552042680566660281525162377778227794768879762338654464000476266705180956908841709177555966832423085875590556580445428066261332223955361646420713402269532603328557674453787190302285379571808527818182992417461094551685532849252957724361115461909983939654809533488667032982867230996411860328826589806804115764031480737011900431983025490936659008858316889367140757195359233313370592669605576636360123189540118611655293825465309125578750878878980002346272931570343463549347548711194000065166346537274382220211242975801253306067862461345994215674963367480607539333468674983712264803816027753559737
  public exponent: 65537
  Validity: [From: Mon Aug 31 10:07:39 CEST 2020,
               To: Thu Aug 29 10:07:39 CEST 2030]
  Issuer: CN=Hakan, OU=Amsterdam, O=Thunderberry, C=NL
  SerialNumber: [    5671c8dd]

Certificate Extensions: 2
[1]: ObjectId: 2.5.29.17 Criticality=true
SubjectAlternativeName [
  DNSName: localhost
  IPAddress: 127.0.0.1
]

[2]: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 78 53 EC 04 E0 A1 55 45   22 C8 25 C4 3F F5 89 40  xS....UE".%.?..@
0010: 51 07 A8 03                                        Q...
]
]

]
  Algorithm: [SHA256withRSA]
  Signature:
0000: 85 55 87 FC 28 19 D1 93   66 4D F8 85 00 E9 D6 75  .U..(...fM.....u
0010: A6 55 69 AE EF 00 53 D3   D9 86 D7 BA F9 58 60 84  .Ui...S......X`.
0020: 65 AE ED 01 FF 39 F6 31   FC 26 9F 95 B6 A3 50 AA  e....9.1.&....P.
0030: A0 1B A1 C9 F1 2B 92 41   78 69 13 35 BD DB 88 8A  .....+.Axi.5....
0040: B5 23 95 C1 F8 F9 C1 57   AD 5D A0 B6 59 DF 79 64  .#.....W.]..Y.yd
0050: E0 FE C2 49 99 8C E4 25   31 9E 38 8A 91 94 1D 47  ...I...%1.8....G
0060: 28 2A A8 78 D0 AB CA 49   1C B2 29 04 09 0D 67 0A  (*.x...I..)...g.
0070: AA F5 A8 B8 7D 0D 7B 14   81 65 56 E7 B6 16 AA F3  .........eV.....
0080: F5 3F CC 4E 3B 6F C9 F6   C8 4C 07 A9 4E 85 FB 69  .?.N;o...L..N..i
0090: 6B 0A EA 67 68 4D 1B C7   EA 30 A8 D5 56 E5 29 14  k..ghM...0..V.).
00A0: 13 51 F9 63 83 43 FA D1   46 DA 9A EA E5 36 57 C6  .Q.c.C..F....6W.
00B0: 8E 39 9C 75 40 95 24 4D   D3 7B E9 83 82 B8 61 13  .9.u@.$M......a.
00C0: ED E7 5E BE 3A 5D 6D 15   DD 7B E1 D0 37 A9 9B 4F  ..^.:]m.....7..O
00D0: D3 8B C5 26 47 3B 46 90   AC 3C 83 98 D8 69 69 51  ...&G;F..<...iiQ
00E0: EE 42 7F CA B5 83 56 09   9E FC 5C A2 BC C2 D5 67  .B....V...\....g
00F0: 7E 59 9F A8 4F DC F7 13   B5 9F F1 1C 56 F2 C2 76  .Y..O.......V..v

]

Here is the full code that is running on Open Java 11:

import com.hierynomus.asn1.ASN1InputStream;
import com.hierynomus.asn1.encodingrules.der.DERDecoder;
import com.hierynomus.asn1.types.ASN1Object;
import com.hierynomus.asn1.types.constructed.ASN1Sequence;
import com.hierynomus.asn1.types.primitive.ASN1Integer;
import com.hierynomus.asn1.types.string.ASN1OctetString;

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
// https://mvnrepository.com/artifact/com.hierynomus/asn-one/0.4.0
// additionally you need https://mvnrepository.com/artifact/org.slf4j/slf4j-api/1.7.30
// and https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12/1.7.30
// https://mvnrepository.com/artifact/org.eclipse.equinox/org.apache.log4j/1.2.13.v200706111418

class MainOrg3 {

    private static final String KEYSTORE_TYPE = "PKCS12";
    private static final String KEY_FACTORY_ALGORITHM = "RSA";
    private static final String CERTIFICATE_TYPE = "X.509";

    private static final Pattern CERTIFICATE_PATTERN = Pattern.compile("-----BEGIN CERTIFICATE-----(.*?)-----END CERTIFICATE-----", Pattern.DOTALL);
    private static final Pattern PRIVATE_KEY_PATTERN = Pattern.compile("-----BEGIN PRIVATE KEY-----(.*?)-----END PRIVATE KEY-----", Pattern.DOTALL);
    private static final Pattern ENCRYPTED_PRIVATE_KEY_PATTERN = Pattern.compile("-----BEGIN ENCRYPTED PRIVATE KEY-----(.*?)-----END ENCRYPTED PRIVATE KEY-----", Pattern.DOTALL);
    private static final Pattern RSA_PRIVATE_KEY_PATTERN = Pattern.compile("-----BEGIN RSA PRIVATE KEY-----(.*?)-----END RSA PRIVATE KEY-----", Pattern.DOTALL);

    private static final String NEW_LINE = "\n";
    private static final String EMPTY = "";

    static char[] keyPassword = "secret".toCharArray();

    public static void main(String[] args) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, InvalidKeySpecException {
        String privateKeyContent = "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" +
                "MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI9/FSBonUacYCAggA\n" +
                "MBQGCCqGSIb3DQMHBAidGkS8wYhOpwSCBMi8JaSYOKudMNNFRpzL7QMIZgFtzDay\n" +
                "MmOroy3lW34dOa7dusqDl4d2gklKcHCpbEaTYxm5aQJ1LuiOdGtFy7HwxOvKU5xz\n" +
                "4qsJoeBIpE0eCTKjQW7/I38DzLXx2wUURqhMWOtFsWZEyR5Dqok3N9mIKKKBXAFG\n" +
                "AwNjlTRW2LyPSttiIUGN01lthjifMWoLTWB1aSGOmGeJRBdSZeqZ15xKneR4H5ja\n" +
                "yE88YcpOHCDKMIxi6ZVoKs7jDQhu8bBKqS8NsYyh1AlP9QkvWNal36jWSzhqYNzk\n" +
                "NRWUOZngfkdbMALVfRtbrC215jHGWVwosPIIs8rkoarRv8s6QWS1Rg3YfQ3qgcRf\n" +
                "s7hkDFKJf3TUXr+askfamV5hc300ZG64+ldX1YxWXY8Vd/wIvHAc/YE/lTyCgYrY\n" +
                "19Am6MNBfp8/kXvzKj+PizB8oNDO4S8sSShEEzOQ5a/+MTC6bqB0DLWYGUqRbjLc\n" +
                "PyYTC2C4i9Agx/GeGVE3c1UdtXiwwnt2XUn7Y1YGqABk0xGIY4J1NFTbSOxKl9hO\n" +
                "arwopAFrZU5nsjjFzv1DJvhfQWnYX18kPSKNHDlia019M118qZ8ERwD9tH8ix9Fa\n" +
                "R2tQdxn1aRGmvXSw+zFkbWD8aWs9n/B+QN1yllJqVoWypOld1yj+fVYYnYOtV1gK\n" +
                "eiygrtrh3JJCvLbEQl4nOgJM3PlEtfBHSaunehIXQMD1z/NDUqgBYjuDPyqRxJeH\n" +
                "Va5k72Nds5PeySKJJnICB3nZKjqgfLhNUrXa1SAQ4vqr0Ik/Lu9P7T+B1XiYwuUT\n" +
                "a20+bxi/x89ZZqwp3jnDuHup7XcO1MtqsoOKP/JgkjVMesb8Q1W8i2dXzg+l4gkk\n" +
                "l1ipreEGtT1YfFTq0DFelz6CjZFLDlGGeGWob94sW94DWTW0nsLPhQWEnwW1CcyJ\n" +
                "oJbJdDEgdiIbRJoABDkTuVXLwTlgzHSHh6zeJvNvcojI7UI3nWYCVYvD3kwghXiP\n" +
                "67sKGL3ug7PFDqLia46AudGY7CFh4+wpxyH+fidLC3FMdkDBA6xR6mGgEjRLXR9M\n" +
                "TnJ/eSYP7eqYZeKn9EarcI7v1zM2IG0/PDQCetiI0ABiHpdKyRQuuiEavp3xC5Vi\n" +
                "h7UmJNYt8Zsz3rwqAQ4FR2+Su5R34OOdRmxTaYLe96PXTpLcLef5TkYixSY7Tzgd\n" +
                "PMyRxRPrywklUEFe4KK/KOcdolxybfsIsxQnupLAMEsO7/Cs7mouNHISK51haDRc\n" +
                "vNbKQ5E4xOq1U4ThW5dHR29cGZillfmMzj05ZQh3ZX2TQJP45ahFET3v9kInWCwQ\n" +
                "8atqclVPOSnASsJZ0PxjYgKZuY8QWYM6zpfWyWnfu/CHhWbRS/qX8T1ow2SMyPBL\n" +
                "CQbZ+MhcdP0IrjoXhDFQsns16i/BPK5TTVqtEC2ywDf5P4/BOEZkySG9YNOd6THp\n" +
                "VA/dVPafzmLy3ltqH+jG8ZH2+RtWx7kwBjiDWs5cF33BFrPS7AZlzMzZoCHLXD/r\n" +
                "T/SmisybUKHMqri0x0RHeIByW0hogSByWiyIn8POabDzJV6Df9nQPziDGcSsvWfG\n" +
                "7q+hizh6+nnXOY+GZx3ptwg9mA9R4QyCiFNQradOaXSPxyEL2IC77/srFfVEIaU4\n" +
                "SRo=\n" +
                "-----END ENCRYPTED PRIVATE KEY-----";

        String certificateContent = "-----BEGIN CERTIFICATE-----\n" +
                "MIIDTjCCAjagAwIBAgIEVnHI3TANBgkqhkiG9w0BAQsFADBIMQswCQYDVQQGEwJO\n" +
                "TDEVMBMGA1UEChMMVGh1bmRlcmJlcnJ5MRIwEAYDVQQLEwlBbXN0ZXJkYW0xDjAM\n" +
                "BgNVBAMTBUhha2FuMB4XDTIwMDgzMTA4MDczOVoXDTMwMDgyOTA4MDczOVowSDEL\n" +
                "MAkGA1UEBhMCTkwxFTATBgNVBAoTDFRodW5kZXJiZXJyeTESMBAGA1UECxMJQW1z\n" +
                "dGVyZGFtMQ4wDAYDVQQDEwVIYWthbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\n" +
                "AQoCggEBAJZ+pqirEqEk4k1ow8vryld79oCO4P/1y+v60CkLpS2MpeHE3BogTr7g\n" +
                "WWP5HdHBMU5l8yT5tXuyVZZqgyzo0q2Sdm7GGrNunHf6Z8vPQFu69sQC1utDM9u8\n" +
                "ZFKiKsTNJ+5QS6KsOtlACyhaLoaPAWVjtvueMjVwmM9hfv/Gq6VyyuBm2x1C4HTj\n" +
                "zPCLHE2G1D13EJaWsvyZLGSbl0GGXZGPhaDd/vnw5TW36mvNTWW+37ZIEk4kXANa\n" +
                "FUNsJemm8HjB/PzHs3/SXGxuD8NKobg3+cNXYwAz2s2DI0W6Xw2g5bbrMQAdBRvn\n" +
                "9/kNftMymDORw3RGwDM2ld4zQfIkNrkCAwEAAaNAMD4wHQYDVR0OBBYEFHhT7ATg\n" +
                "oVVFIsglxD/1iUBRB6gDMB0GA1UdEQEB/wQTMBGCCWxvY2FsaG9zdIcEfwAAATAN\n" +
                "BgkqhkiG9w0BAQsFAAOCAQEAhVWH/CgZ0ZNmTfiFAOnWdaZVaa7vAFPT2YbXuvlY\n" +
                "YIRlru0B/zn2Mfwmn5W2o1CqoBuhyfErkkF4aRM1vduIirUjlcH4+cFXrV2gtlnf\n" +
                "eWTg/sJJmYzkJTGeOIqRlB1HKCqoeNCrykkcsikECQ1nCqr1qLh9DXsUgWVW57YW\n" +
                "qvP1P8xOO2/J9shMB6lOhftpawrqZ2hNG8fqMKjVVuUpFBNR+WODQ/rRRtqa6uU2\n" +
                "V8aOOZx1QJUkTdN76YOCuGET7edevjpdbRXde+HQN6mbT9OLxSZHO0aQrDyDmNhp\n" +
                "aVHuQn/KtYNWCZ78XKK8wtVnflmfqE/c9xO1n/EcVvLCdg==\n" +
                "-----END CERTIFICATE-----";

        PrivateKey privateKey = parsePrivateKey(privateKeyContent);
        Certificate[] certificates = parseCertificate(certificateContent).values()
                .toArray(new Certificate[]{});

        KeyStore keyStore = createEmptyKeyStore();
        keyStore.setKeyEntry("client", privateKey, null, certificates);
        System.out.println("\nprivateKey:\n" + privateKey);
        int certificatesLength = certificates.length;
        System.out.println("\nnumber of certificates: " + certificatesLength);
        for (int i = 0; i < certificatesLength; i++) {
            System.out.println("\ncertificate: " + i + " data:\n" + certificates[i]);
        }
    }

    private static KeyStore createEmptyKeyStore() throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException {
        KeyStore keyStore = KeyStore.getInstance(KEYSTORE_TYPE);
        keyStore.load(null, null);
        return keyStore;
    }

    private static PrivateKey parsePrivateKey(String identityContent) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
        KeySpec keySpec = null;

        Matcher privateKeyMatcher = PRIVATE_KEY_PATTERN.matcher(identityContent);
        if (privateKeyMatcher.find()) {
            String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
            byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
            keySpec = new PKCS8EncodedKeySpec(decodedPrivateKeyContent);
        }

        privateKeyMatcher = RSA_PRIVATE_KEY_PATTERN.matcher(identityContent);
        if (privateKeyMatcher.find()) {
            String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
            byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
            keySpec = getKeySpecFromAsn1StructuredData(decodedPrivateKeyContent);
        }

        privateKeyMatcher = ENCRYPTED_PRIVATE_KEY_PATTERN.matcher(identityContent);
        if (privateKeyMatcher.find()) {
            String privateKeyContent = privateKeyMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
            byte[] decodedPrivateKeyContent = Base64.getDecoder().decode(privateKeyContent);
            try (ByteArrayInputStream privateKeyAsInputStream = new ByteArrayInputStream(decodedPrivateKeyContent)) {
                ASN1InputStream stream = new ASN1InputStream(new DERDecoder(), privateKeyAsInputStream);
                ASN1Sequence asn1Sequence = stream.readObject();
                ASN1OctetString privateKeyAsOctetString = (ASN1OctetString) asn1Sequence.get(1);
                ASN1OctetString saltAsOctetString = (ASN1OctetString) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(0)).get(1)).get(0);
                ASN1OctetString initializationVectorAsOctec = ((ASN1OctetString) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(1)).get(1));
                ASN1Integer iterationCount = (ASN1Integer) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) ((ASN1Sequence) asn1Sequence.get(0)).get(1)).get(0)).get(1)).get(1);
                IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVectorAsOctec.getValue());
                SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance("PBKDF2withHmacSHA1");
                EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(secretKeyFactory.getAlgorithm(), privateKeyAsOctetString.getValue());
                int keyLength = encryptedPrivateKeyInfo.getEncoded().length;
                System.out.println("original keyLength:  " + keyLength);
                keyLength = 24 * 8; // ### fixed
                System.out.println("fixed keyLength(bit): " + keyLength);
                PBEKeySpec pbeKeySpec = new PBEKeySpec(keyPassword, saltAsOctetString.getValue(), iterationCount.getValue().intValue(), keyLength);
                Cipher cipher = Cipher.getInstance("DESede/CBC/PKCS5Padding/");

                // SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec); // ### old routine
                SecretKey secretKeyPbkdf = secretKeyFactory.generateSecret(pbeKeySpec); // ### new
                SecretKey secretKey = new SecretKeySpec(secretKeyPbkdf.getEncoded(), "DESede"); // ### new

                cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
                keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher);
                //PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher);
            } catch (NoSuchPaddingException | InvalidKeyException | InvalidAlgorithmParameterException e) {
                e.printStackTrace();
            }
        }

        Objects.requireNonNull(keySpec);

        KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
        return keyFactory.generatePrivate(keySpec);
    }

    private static KeySpec getKeySpecFromAsn1StructuredData(byte[] decodedPrivateKeyContent) throws IOException {
        try(ByteArrayInputStream privateKeyAsInputStream = new ByteArrayInputStream(decodedPrivateKeyContent)) {
            ASN1InputStream stream = new ASN1InputStream(new DERDecoder(), privateKeyAsInputStream);
            ASN1Sequence asn1Sequence = stream.readObject();

            if (asn1Sequence.getValue().size() < 9) {
                throw new RuntimeException("Parsed key content doesn't have the minimum required sequence size of 9");
            }

            BigInteger modulus = extractIntValueFrom(asn1Sequence.get(1));
            BigInteger publicExponent = extractIntValueFrom(asn1Sequence.get(2));
            BigInteger privateExponent = extractIntValueFrom(asn1Sequence.get(3));
            BigInteger primeP = extractIntValueFrom(asn1Sequence.get(4));
            BigInteger primeQ = extractIntValueFrom(asn1Sequence.get(5));
            BigInteger primeExponentP = extractIntValueFrom(asn1Sequence.get(6));
            BigInteger primeExponentQ = extractIntValueFrom(asn1Sequence.get(7));
            BigInteger crtCoefficient = extractIntValueFrom(asn1Sequence.get(8));

            return new RSAPrivateCrtKeySpec(
                    modulus, publicExponent, privateExponent,
                    primeP, primeQ, primeExponentP, primeExponentQ, crtCoefficient
            );
        }
    }

    private static BigInteger extractIntValueFrom(ASN1Object<?> asn1Object) {
        if (asn1Object instanceof ASN1Integer) {
            return ((ASN1Integer) asn1Object).getValue();
        } else {
            throw new RuntimeException(String.format(
                    "Unable to parse the provided value of the object type [%s]. The type should be an instance of [%s]",
                    asn1Object.getClass().getName(), ASN1Integer.class.getName())
            );
        }
    }

    private static Map<String, Certificate> parseCertificate(String certificateContent) throws IOException, CertificateException {
        Map<String, Certificate> certificates = new HashMap<>();
        Matcher certificateMatcher = CERTIFICATE_PATTERN.matcher(certificateContent);

        while (certificateMatcher.find()) {
            String sanitizedCertificate = certificateMatcher.group(1).replace(NEW_LINE, EMPTY).trim();
            byte[] decodedCertificate = Base64.getDecoder().decode(sanitizedCertificate);
            try(ByteArrayInputStream certificateAsInputStream = new ByteArrayInputStream(decodedCertificate)) {
                CertificateFactory certificateFactory = CertificateFactory.getInstance(CERTIFICATE_TYPE);
                Certificate certificate = certificateFactory.generateCertificate(certificateAsInputStream);
                certificates.put(UUID.randomUUID().toString(), certificate);
            }
        }
        return certificates;
    }
}

Solution 3:[3]

If you don't be shy to convert your given encrypted private key prior with openssl you can use the sample code for easy parsing.

To run my example I saved your Encrypted Private Key to a file 'so_privatekey_secret.pem'.

Using this comand line in openssl I converted the (already) encrypted key with another cipher and generated the (new) keyfile 'so_privatekey_3des_secret.pem':

openssl pkcs8 -v1 PBE-SHA1-3DES -topk8 -in so_privatekey_secret.pem -out so_privatekey_3des_secret.pem

Your encrypted private key will look like:

-----BEGIN ENCRYPTED PRIVATE KEY-----
MIIE6jAcBgoqhkiG9w0BDAEDMA4ECAdT0CV4sPOPAgIIAASCBMi3H3WjyWEiAS/G
gw6gZTHiZFmidehU8XPh1HPYVb385u36WO16CwZVIBperQoXptCaE2LRwxmrN7T/
K2l59EHlV8i8FAfGKqvc7b1DW1fqx7jvw+/iT//3uNc3GHUNXfBrwHDi/MTghII4
gEFNUm7ZO7rAqeR7pJnZxm3giCvRHFbFcZRkz6LhCif3eYF9Lx3gSe2x6SPeNG/T
p0vrierxhHyCJk+S543J3cZ4vHUOjYzOsT1xz9hTbxAxeAEPkaCpD0P9jyth8M8k
2SRedRvR1Ia6r7sAfFz6XoHz5jH/lhd47EYpOc/A7rsgWkFLObwdFwGLRIvQsgeN
VXnwg5UoxRdjE3ZXQMFgHgzc4igmoVCjwgNZW6nAMwlcTctjsQyvRnAEnQbqLUw1
kddkjDzC1LFk+NiZ7L9oKVWAQuAQnLHMOxr8kJrTlSoS8zt5x5OEnR4I7JnUEHqs
2RythkJnoAsXaslBIkIXw4Kg6wisZjr/fL7ER0D0TBdZFPMvdMw8p4TxNxEF7v/K
G97HmCe39cVl3DkYayK2Bs4T6FxqbqUzUMOQnNHpbj82+ybEhm/5duhD5Kd/Inz7
8mTpDM08P7OMG27AiJDReQJ18XQ5y3DxQJu8UgfQEtjpVW3BQIVzYRM4JqEEv72h
YLELhtmBQom1SWadMW6jzRJRS+f+ivEApVyLcKlnPj2SY7niSmDjYChDx30fHoDt
z25UYBb8LmZ6P5rwNpMgGutBYqg721ioYt86mQsDDaLYOxjlhPfwbrQ9HqUrPCyQ
0UBeP9hNYkGbZQ+erZmyJ4hhlGGXz9v1LZ4xqNelKuUUREZaLWNVO7UwX2AgUebv
V7V3ne3ZNuLo5kDQqGz3hxwI4tcHgneNMpbxwdR7/B9QTXtirgZck9QIU6zxPhIf
agyCfpqE5pE3ln5n+FfbOik1pNcQxFr0nLy6vnlcTATLHftX0JycFc9OcJ9eRnI/
zk0ekYcw7QLxzNp/ews+Krwy+2SXaacTi+fXihCks10FZ/TEMYV0wC7OMW1yC35K
jUU12u7kSccvUk4xQftyA39XYCN6g7kxDqLjCfc6pMMWZZ9anSo7rmuQ6scoMhdr
904PcX3ne9rBJLv3a3W25TolNKZx6JofCViyIipcJY4FRyIRoIfwUTZ643KLSZ4o
nAPxtWHpbgA8ucrMKwQ0kbhT/qEvz3HxliL91N7qqMfZ3RfsgK2fUXQZ4zsU1oZ2
f7UT9+D0w/Pusond7SHPFo9G68GjsE48CQ1J/l5QreZ+bLxBohs2xM1WIz2TBJX9
ghdbo+0eatoxfq0o/H0vj66xMd8fvqePrzNw3kSqSpdQiZl95vqWM8Y7xEW0PxAU
78I0koI98v/3n9abSUNDhodPsk8I3abr7MoQsKe7VSu/x+mgMcfvNOiH6j6LuUDx
Eii7nfHhC/M69hWoh8Y+a2qPdbT2WMZSgXdBC+dCQeL4o6+flht6/24Eg+HAQMyY
3X5aH2ZM4BJAyJvdtj7btMDKq6nunyMywYhIOgKTZUg8wytxDsi2F4KyNKpXXgsj
0yR2L6EUYeQ1ZLG/NcM86sHueF1x38RznpyD4rujRGslWAKRXk4D2O2GpYS4rYRk
4r4pPXltZ472q7PGBfM=
-----END ENCRYPTED PRIVATE KEY-----

Running my sample program (that does have no exception handling) gives an output:

pkInfo.getAlgName: PBEWithSHA1AndDESede
decryptedPrivateKey: SunRsaSign RSA private CRT key, 2048 bits
  params: null
  modulus: 18998199686208613730227071552042680566660281525162377778227794768879762338654464000476266705180956908841709177555966832423085875590556580445428066261332223955361646420713402269532603328557674453787190302285379571808527818182992417461094551685532849252957724361115461909983939654809533488667032982867230996411860328826589806804115764031480737011900431983025490936659008858316889367140757195359233313370592669605576636360123189540118611655293825465309125578750878878980002346272931570343463549347548711194000065166346537274382220211242975801253306067862461345994215674963367480607539333468674983712264803816027753559737
  private exponent: 2470690692670644289832084636740463653655882622624760758103597888447170520657826825702415751838767348735186037205082706146786714644526202528897926495648786865000645626802269367582073915029633022103975204027927584273373553784482725392082470421529769049284506228997163157587212073758970565083984956787424047216319298607371397767542633302071090323391531162434678662485530085857362259661656308472034152915312425224731842425134593468185574918070451135641322780271191791839285884885643517240131520070881951542294552185519522325857178404160441369354693465035929601010771762928436963178293113507661955562266846499005032897533

code:

import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.util.Base64;

public class ReadPkcs8PrivateKeysSo {
    public static void main(String[] args) throws IOException, NoSuchAlgorithmException, InvalidKeySpecException, InvalidKeyException {

        String encrypted = new String(Files.readAllBytes(Paths.get("so_privatekey_3des_secret.pem")));
        String password = "secret";

        //Create object from encrypted private key
        encrypted = encrypted.replace("-----BEGIN ENCRYPTED PRIVATE KEY-----", "");
        encrypted = encrypted.replace("-----END ENCRYPTED PRIVATE KEY-----", "");
        EncryptedPrivateKeyInfo pkInfo = new EncryptedPrivateKeyInfo(Base64.getMimeDecoder().decode(encrypted));
        PBEKeySpec keySpec = new PBEKeySpec(password.toCharArray()); // password
        System.out.println("pkInfo.getAlgName: " + pkInfo.getAlgName());
        SecretKeyFactory pbeKeyFactory = SecretKeyFactory.getInstance(pkInfo.getAlgName());
        SecretKey secretKey = pbeKeyFactory.generateSecret(keySpec);
        PKCS8EncodedKeySpec encodedKeySpec = pkInfo.getKeySpec(secretKey);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        PrivateKey decryptedPrivateKey = keyFactory.generatePrivate(encodedKeySpec);
        System.out.println("decryptedPrivateKey: " + decryptedPrivateKey);
    }
}

Solution 4:[4]

I spent a number of weeks working on my pem-utils project on GitHub and I now have pure-Java code with no external libraries that seems to be able to load anything I throw at it.

I hope this helps the original poster (from 18 months ago!) as well as anyone else following in our footsteps.

Solution 5:[5]

In the end I decided to use BC to parse the PEM files for certificates and private keys. In my use case I needed to handle all different kind of private keys and therefor the complexity also grew, but I wanted to make it as easy as possible for my self and others, so I simplified it by creating a library out of it, see below for an example usage:

import javax.net.ssl.X509ExtendedKeyManager;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.List;

public class App {

    public static void main(String[] args) {
        char[] privateKeyPassword = "secret".toCharArray();
        
        String privateKeyContent = """
                -----BEGIN ENCRYPTED PRIVATE KEY-----
                MIIFDjBABgkqhkiG9w0BBQ0wMzAbBgkqhkiG9w0BBQwwDgQI9/FSBonUacYCAggA
                MBQGCCqGSIb3DQMHBAidGkS8wYhOpwSCBMi8JaSYOKudMNNFRpzL7QMIZgFtzDay
                MmOroy3lW34dOa7dusqDl4d2gklKcHCpbEaTYxm5aQJ1LuiOdGtFy7HwxOvKU5xz
                4qsJoeBIpE0eCTKjQW7/I38DzLXx2wUURqhMWOtFsWZEyR5Dqok3N9mIKKKBXAFG
                AwNjlTRW2LyPSttiIUGN01lthjifMWoLTWB1aSGOmGeJRBdSZeqZ15xKneR4H5ja
                yE88YcpOHCDKMIxi6ZVoKs7jDQhu8bBKqS8NsYyh1AlP9QkvWNal36jWSzhqYNzk
                NRWUOZngfkdbMALVfRtbrC215jHGWVwosPIIs8rkoarRv8s6QWS1Rg3YfQ3qgcRf
                s7hkDFKJf3TUXr+askfamV5hc300ZG64+ldX1YxWXY8Vd/wIvHAc/YE/lTyCgYrY
                19Am6MNBfp8/kXvzKj+PizB8oNDO4S8sSShEEzOQ5a/+MTC6bqB0DLWYGUqRbjLc
                PyYTC2C4i9Agx/GeGVE3c1UdtXiwwnt2XUn7Y1YGqABk0xGIY4J1NFTbSOxKl9hO
                arwopAFrZU5nsjjFzv1DJvhfQWnYX18kPSKNHDlia019M118qZ8ERwD9tH8ix9Fa
                R2tQdxn1aRGmvXSw+zFkbWD8aWs9n/B+QN1yllJqVoWypOld1yj+fVYYnYOtV1gK
                eiygrtrh3JJCvLbEQl4nOgJM3PlEtfBHSaunehIXQMD1z/NDUqgBYjuDPyqRxJeH
                Va5k72Nds5PeySKJJnICB3nZKjqgfLhNUrXa1SAQ4vqr0Ik/Lu9P7T+B1XiYwuUT
                a20+bxi/x89ZZqwp3jnDuHup7XcO1MtqsoOKP/JgkjVMesb8Q1W8i2dXzg+l4gkk
                l1ipreEGtT1YfFTq0DFelz6CjZFLDlGGeGWob94sW94DWTW0nsLPhQWEnwW1CcyJ
                oJbJdDEgdiIbRJoABDkTuVXLwTlgzHSHh6zeJvNvcojI7UI3nWYCVYvD3kwghXiP
                67sKGL3ug7PFDqLia46AudGY7CFh4+wpxyH+fidLC3FMdkDBA6xR6mGgEjRLXR9M
                TnJ/eSYP7eqYZeKn9EarcI7v1zM2IG0/PDQCetiI0ABiHpdKyRQuuiEavp3xC5Vi
                h7UmJNYt8Zsz3rwqAQ4FR2+Su5R34OOdRmxTaYLe96PXTpLcLef5TkYixSY7Tzgd
                PMyRxRPrywklUEFe4KK/KOcdolxybfsIsxQnupLAMEsO7/Cs7mouNHISK51haDRc
                vNbKQ5E4xOq1U4ThW5dHR29cGZillfmMzj05ZQh3ZX2TQJP45ahFET3v9kInWCwQ
                8atqclVPOSnASsJZ0PxjYgKZuY8QWYM6zpfWyWnfu/CHhWbRS/qX8T1ow2SMyPBL
                CQbZ+MhcdP0IrjoXhDFQsns16i/BPK5TTVqtEC2ywDf5P4/BOEZkySG9YNOd6THp
                VA/dVPafzmLy3ltqH+jG8ZH2+RtWx7kwBjiDWs5cF33BFrPS7AZlzMzZoCHLXD/r
                T/SmisybUKHMqri0x0RHeIByW0hogSByWiyIn8POabDzJV6Df9nQPziDGcSsvWfG
                7q+hizh6+nnXOY+GZx3ptwg9mA9R4QyCiFNQradOaXSPxyEL2IC77/srFfVEIaU4
                SRo=
                -----END ENCRYPTED PRIVATE KEY-----
                """;

        String certificateChainContent = """
                -----BEGIN CERTIFICATE-----
                MIIDTjCCAjagAwIBAgIEVnHI3TANBgkqhkiG9w0BAQsFADBIMQswCQYDVQQGEwJO
                TDEVMBMGA1UEChMMVGh1bmRlcmJlcnJ5MRIwEAYDVQQLEwlBbXN0ZXJkYW0xDjAM
                BgNVBAMTBUhha2FuMB4XDTIwMDgzMTA4MDczOVoXDTMwMDgyOTA4MDczOVowSDEL
                MAkGA1UEBhMCTkwxFTATBgNVBAoTDFRodW5kZXJiZXJyeTESMBAGA1UECxMJQW1z
                dGVyZGFtMQ4wDAYDVQQDEwVIYWthbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
                AQoCggEBAJZ+pqirEqEk4k1ow8vryld79oCO4P/1y+v60CkLpS2MpeHE3BogTr7g
                WWP5HdHBMU5l8yT5tXuyVZZqgyzo0q2Sdm7GGrNunHf6Z8vPQFu69sQC1utDM9u8
                ZFKiKsTNJ+5QS6KsOtlACyhaLoaPAWVjtvueMjVwmM9hfv/Gq6VyyuBm2x1C4HTj
                zPCLHE2G1D13EJaWsvyZLGSbl0GGXZGPhaDd/vnw5TW36mvNTWW+37ZIEk4kXANa
                FUNsJemm8HjB/PzHs3/SXGxuD8NKobg3+cNXYwAz2s2DI0W6Xw2g5bbrMQAdBRvn
                9/kNftMymDORw3RGwDM2ld4zQfIkNrkCAwEAAaNAMD4wHQYDVR0OBBYEFHhT7ATg
                oVVFIsglxD/1iUBRB6gDMB0GA1UdEQEB/wQTMBGCCWxvY2FsaG9zdIcEfwAAATAN
                BgkqhkiG9w0BAQsFAAOCAQEAhVWH/CgZ0ZNmTfiFAOnWdaZVaa7vAFPT2YbXuvlY
                YIRlru0B/zn2Mfwmn5W2o1CqoBuhyfErkkF4aRM1vduIirUjlcH4+cFXrV2gtlnf
                eWTg/sJJmYzkJTGeOIqRlB1HKCqoeNCrykkcsikECQ1nCqr1qLh9DXsUgWVW57YW
                qvP1P8xOO2/J9shMB6lOhftpawrqZ2hNG8fqMKjVVuUpFBNR+WODQ/rRRtqa6uU2
                V8aOOZx1QJUkTdN76YOCuGET7edevjpdbRXde+HQN6mbT9OLxSZHO0aQrDyDmNhp
                aVHuQn/KtYNWCZ78XKK8wtVnflmfqE/c9xO1n/EcVvLCdg==
                -----END CERTIFICATE-----
                """;

        PrivateKey privateKey = PemUtils.parsePrivateKey(privateKeyContent, privateKeyPassword);
        List<X509Certificate> certificates = PemUtils.parseCertificate(certificateChainContent);

        X509ExtendedKeyManager keyManager = PemUtils.parseIdentityMaterial(certificateChainContent, privateKeyContent, privateKeyPassword);
    }

}

The library is available here: GitHub - SSLContext Kickstart

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Community
Solution 2 Michael Fehr
Solution 3 Michael Fehr
Solution 4 Christopher Schultz
Solution 5 Hakan54