title: Parsing encrypted PKCS#8 encoded pem file programatically
body: <p>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 <code>secret</code>. It is purely created for sharing here, so it is not a private key which is used on a production machine.</p>
  <p>I created it from a p12 file with the following command: <code>openssl pkcs12 -in key-pair.p12 -out key-pair.pem</code></p>
  <p><strong>Valid example (throw-away) key-pair</strong></p>
  <pre><code>-----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-----
  </code></pre>
  <p>I know <strong>BouncyCastle</strong> 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.</p>
  <p>I am already able to parse private key with the following header/footer:</p>
  <pre><code>-----BEGIN PRIVATE KEY-----
             *
  -----END PRIVATE KEY-----
  </code></pre>
  <p>and</p>
  <pre><code>-----BEGIN RSA PRIVATE KEY-----
             *
  -----END RSA PRIVATE KEY-----
  </code></pre>
  <p>I already use the following snippet to accomplish that:</p>
  <pre class="lang-java prettyprint-override"><code>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 = &quot;PKCS12&quot;;
      private static final String KEY_FACTORY_ALGORITHM = &quot;RSA&quot;;
      private static final String CERTIFICATE_TYPE = &quot;X.509&quot;;
  
      private static final Pattern CERTIFICATE_PATTERN = Pattern.compile(&quot;-----BEGIN CERTIFICATE-----(.*?)-----END CERTIFICATE-----&quot;, Pattern.DOTALL);
      private static final Pattern PRIVATE_KEY_PATTERN = Pattern.compile(&quot;-----BEGIN PRIVATE KEY-----(.*?)-----END PRIVATE KEY-----&quot;, Pattern.DOTALL);
      private static final Pattern ENCRYPTED_PRIVATE_KEY_PATTERN = Pattern.compile(&quot;-----BEGIN ENCRYPTED PRIVATE KEY-----(.*?)-----END ENCRYPTED PRIVATE KEY-----&quot;, Pattern.DOTALL);
      private static final Pattern RSA_PRIVATE_KEY_PATTERN = Pattern.compile(&quot;-----BEGIN RSA PRIVATE KEY-----(.*?)-----END RSA PRIVATE KEY-----&quot;, Pattern.DOTALL);
  
      private static final String NEW_LINE = &quot;\n&quot;;
      private static final String EMPTY = &quot;&quot;;
  
      public static void main(String[] args) throws CertificateException, NoSuchAlgorithmException, KeyStoreException, IOException, InvalidKeySpecException {
          String privateKeyContent = &quot;&quot;;
          String certificateContent = &quot;&quot;;
  
          PrivateKey privateKey = parsePrivateKey(privateKeyContent);
          Certificate[] certificates = parseCertificate(certificateContent).values()
                  .toArray(new Certificate[]{});
  
          KeyStore keyStore = createEmptyKeyStore();
          keyStore.setKeyEntry(&quot;client&quot;, 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() &lt; 9) {
                  throw new RuntimeException(&quot;Parsed key content doesn't have the minimum required sequence size of 9&quot;);
              }
  
              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&lt;?&gt; asn1Object) {
          if (asn1Object instanceof ASN1Integer) {
              return ((ASN1Integer) asn1Object).getValue();
          } else {
              throw new RuntimeException(String.format(
                      &quot;Unable to parse the provided value of the object type [%s]. The type should be an instance of [%s]&quot;,
                      asn1Object.getClass().getName(), ASN1Integer.class.getName())
              );
          }
      }
  
      private static Map&lt;String, Certificate&gt; parseCertificate(String certificateContent) throws IOException, CertificateException {
          Map&lt;String, Certificate&gt; certificates = new HashMap&lt;&gt;();
          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;
      }
  
  }
  </code></pre>
  <p>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: <a href="https://stackoverflow.com/a/42733858/6777695">https://stackoverflow.com/a/42733858/6777695</a> but <a href="http://www.docjar.com/docs/api/sun/security/util/DerInputStream.html" rel="nofollow noreferrer">DerInputStream</a> and <a href="http://www.docjar.com/docs/api/sun/security/util/DerValue.html" rel="nofollow noreferrer">DerValue</a> aren't accessible anymore from jdk 11 onwards. And the sun packages should be avoided... So I discovered <a href="https://github.com/hierynomus/asn-one" rel="nofollow noreferrer">asn-one java library</a> to parse asn.1 encoded private key, and it works quite well.</p>
  <p>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.</p>
  <p>From the following article: <a href="https://tls.mbed.org/kb/cryptography/asn1-key-structures-in-der-and-pem" rel="nofollow noreferrer">ASN.1 key structures in DER and PEM</a> I learned that a private key having the following header/footer: <code>-----BEGIN ENCRYPTED PRIVATE KEY----- * -----END ENCRYPTED PRIVATE KEY-----</code> is also an asn.1 encoded private key with the following structure:</p>
  <pre><code>EncryptedPrivateKeyInfo ::= SEQUENCE {
    encryptionAlgorithm  EncryptionAlgorithmIdentifier,
    encryptedData        EncryptedData
  }
  
  EncryptionAlgorithmIdentifier ::= AlgorithmIdentifier
  
  EncryptedData ::= OCTET STRING
  </code></pre>
  <p>I also tried the answers provided here <a href="https://stackoverflow.com/questions/2654949/how-to-read-a-password-encrypted-key-with-java">How to read a password encrypted key with java?</a> 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!</p>
  <p>======&gt;
  Updated progress at 12-09-2020 based on the input of <code>dave_thompson_085</code></p>
  <pre><code>Matcher privateKeyMatcher = Pattern.compile(&quot;-----BEGIN ENCRYPTED PRIVATE KEY-----(.*?)-----END ENCRYPTED PRIVATE KEY-----&quot;, 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(&quot;PBKDF2withHmacSHA1&quot;);
          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(&quot;DESede/CBC/PKCS5Padding/&quot;);
  
          SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec);
  
          cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
          PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher);
      }
  }
  </code></pre>
  <p>Results into the following exception: <code>java.security.InvalidKeyException: Wrong algorithm: DESede or TripleDES required</code> on the statement <code>cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);</code></p>
  <p>======&gt;
  Updated progress at 13-09-2020 based on the input of <code>Michael Fehr</code></p>
  <pre><code>Matcher privateKeyMatcher = Pattern.compile(&quot;-----BEGIN ENCRYPTED PRIVATE KEY-----(.*?)-----END ENCRYPTED PRIVATE KEY-----&quot;, 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(&quot;PBKDF2withHmacSHA1&quot;);
          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(&quot;DESede/CBC/PKCS5Padding/&quot;);
  
          SecretKey secretKeyPbkdf = secretKeyFactory.generateSecret(pbeKeySpec);
          SecretKey secretKey = new SecretKeySpec(secretKeyPbkdf.getEncoded(), &quot;DESede&quot;);
  
          cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
          PKCS8EncodedKeySpec keySpec = encryptedPrivateKeyInfo.getKeySpec(cipher);
      }
  }
  </code></pre>