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); } }
Advertisement
Answer
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/FSBonUacYCAggAn" + "MBQGCCqGSIb3DQMHBAidGkS8wYhOpwSCBMi8JaSYOKudMNNFRpzL7QMIZgFtzDayn" + "MmOroy3lW34dOa7dusqDl4d2gklKcHCpbEaTYxm5aQJ1LuiOdGtFy7HwxOvKU5xzn" + "4qsJoeBIpE0eCTKjQW7/I38DzLXx2wUURqhMWOtFsWZEyR5Dqok3N9mIKKKBXAFGn" + "AwNjlTRW2LyPSttiIUGN01lthjifMWoLTWB1aSGOmGeJRBdSZeqZ15xKneR4H5jan" + "yE88YcpOHCDKMIxi6ZVoKs7jDQhu8bBKqS8NsYyh1AlP9QkvWNal36jWSzhqYNzkn" + "NRWUOZngfkdbMALVfRtbrC215jHGWVwosPIIs8rkoarRv8s6QWS1Rg3YfQ3qgcRfn" + "s7hkDFKJf3TUXr+askfamV5hc300ZG64+ldX1YxWXY8Vd/wIvHAc/YE/lTyCgYrYn" + "19Am6MNBfp8/kXvzKj+PizB8oNDO4S8sSShEEzOQ5a/+MTC6bqB0DLWYGUqRbjLcn" + "PyYTC2C4i9Agx/GeGVE3c1UdtXiwwnt2XUn7Y1YGqABk0xGIY4J1NFTbSOxKl9hOn" + "arwopAFrZU5nsjjFzv1DJvhfQWnYX18kPSKNHDlia019M118qZ8ERwD9tH8ix9Fan" + "R2tQdxn1aRGmvXSw+zFkbWD8aWs9n/B+QN1yllJqVoWypOld1yj+fVYYnYOtV1gKn" + "eiygrtrh3JJCvLbEQl4nOgJM3PlEtfBHSaunehIXQMD1z/NDUqgBYjuDPyqRxJeHn" + "Va5k72Nds5PeySKJJnICB3nZKjqgfLhNUrXa1SAQ4vqr0Ik/Lu9P7T+B1XiYwuUTn" + "a20+bxi/x89ZZqwp3jnDuHup7XcO1MtqsoOKP/JgkjVMesb8Q1W8i2dXzg+l4gkkn" + "l1ipreEGtT1YfFTq0DFelz6CjZFLDlGGeGWob94sW94DWTW0nsLPhQWEnwW1CcyJn" + "oJbJdDEgdiIbRJoABDkTuVXLwTlgzHSHh6zeJvNvcojI7UI3nWYCVYvD3kwghXiPn" + "67sKGL3ug7PFDqLia46AudGY7CFh4+wpxyH+fidLC3FMdkDBA6xR6mGgEjRLXR9Mn" + "TnJ/eSYP7eqYZeKn9EarcI7v1zM2IG0/PDQCetiI0ABiHpdKyRQuuiEavp3xC5Vin" + "h7UmJNYt8Zsz3rwqAQ4FR2+Su5R34OOdRmxTaYLe96PXTpLcLef5TkYixSY7Tzgdn" + "PMyRxRPrywklUEFe4KK/KOcdolxybfsIsxQnupLAMEsO7/Cs7mouNHISK51haDRcn" + "vNbKQ5E4xOq1U4ThW5dHR29cGZillfmMzj05ZQh3ZX2TQJP45ahFET3v9kInWCwQn" + "8atqclVPOSnASsJZ0PxjYgKZuY8QWYM6zpfWyWnfu/CHhWbRS/qX8T1ow2SMyPBLn" + "CQbZ+MhcdP0IrjoXhDFQsns16i/BPK5TTVqtEC2ywDf5P4/BOEZkySG9YNOd6THpn" + "VA/dVPafzmLy3ltqH+jG8ZH2+RtWx7kwBjiDWs5cF33BFrPS7AZlzMzZoCHLXD/rn" + "T/SmisybUKHMqri0x0RHeIByW0hogSByWiyIn8POabDzJV6Df9nQPziDGcSsvWfGn" + "7q+hizh6+nnXOY+GZx3ptwg9mA9R4QyCiFNQradOaXSPxyEL2IC77/srFfVEIaU4n" + "SRo=n" + "-----END ENCRYPTED PRIVATE KEY-----"; String certificateContent = "-----BEGIN CERTIFICATE-----n" + "MIIDTjCCAjagAwIBAgIEVnHI3TANBgkqhkiG9w0BAQsFADBIMQswCQYDVQQGEwJOn" + "TDEVMBMGA1UEChMMVGh1bmRlcmJlcnJ5MRIwEAYDVQQLEwlBbXN0ZXJkYW0xDjAMn" + "BgNVBAMTBUhha2FuMB4XDTIwMDgzMTA4MDczOVoXDTMwMDgyOTA4MDczOVowSDELn" + "MAkGA1UEBhMCTkwxFTATBgNVBAoTDFRodW5kZXJiZXJyeTESMBAGA1UECxMJQW1zn" + "dGVyZGFtMQ4wDAYDVQQDEwVIYWthbjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCn" + "AQoCggEBAJZ+pqirEqEk4k1ow8vryld79oCO4P/1y+v60CkLpS2MpeHE3BogTr7gn" + "WWP5HdHBMU5l8yT5tXuyVZZqgyzo0q2Sdm7GGrNunHf6Z8vPQFu69sQC1utDM9u8n" + "ZFKiKsTNJ+5QS6KsOtlACyhaLoaPAWVjtvueMjVwmM9hfv/Gq6VyyuBm2x1C4HTjn" + "zPCLHE2G1D13EJaWsvyZLGSbl0GGXZGPhaDd/vnw5TW36mvNTWW+37ZIEk4kXANan" + "FUNsJemm8HjB/PzHs3/SXGxuD8NKobg3+cNXYwAz2s2DI0W6Xw2g5bbrMQAdBRvnn" + "9/kNftMymDORw3RGwDM2ld4zQfIkNrkCAwEAAaNAMD4wHQYDVR0OBBYEFHhT7ATgn" + "oVVFIsglxD/1iUBRB6gDMB0GA1UdEQEB/wQTMBGCCWxvY2FsaG9zdIcEfwAAATANn" + "BgkqhkiG9w0BAQsFAAOCAQEAhVWH/CgZ0ZNmTfiFAOnWdaZVaa7vAFPT2YbXuvlYn" + "YIRlru0B/zn2Mfwmn5W2o1CqoBuhyfErkkF4aRM1vduIirUjlcH4+cFXrV2gtlnfn" + "eWTg/sJJmYzkJTGeOIqRlB1HKCqoeNCrykkcsikECQ1nCqr1qLh9DXsUgWVW57YWn" + "qvP1P8xOO2/J9shMB6lOhftpawrqZ2hNG8fqMKjVVuUpFBNR+WODQ/rRRtqa6uU2n" + "V8aOOZx1QJUkTdN76YOCuGET7edevjpdbRXde+HQN6mbT9OLxSZHO0aQrDyDmNhpn" + "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; } }