英文:
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 Stack Overflow, 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 an 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 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
...
SRo=
-----END ENCRYPTED PRIVATE KEY-----
-----BEGIN CERTIFICATE-----
MIIDTjCCAjagAwIBAgIEVnHI3TANBgkqhkiG9w0BAQsFADBIMQswCQYDVQQGEwJO
...
7LCdg==
-----END CERTIFICATE-----
I know BouncyCastle can parse it, but I want to avoid using additional libraries. So I am wondering if it is possible with just plain JDK or some other lightweight libraries.
I am already able to parse private keys with the following headers/footers:
-----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 ... // (imports)
class App {
// ... (other constants and methods)
public static void main(String[] args) throws ... {
String privateKeyContent = "..."; // (private key content)
String certificateContent = "..."; // (certificate content)
PrivateKey privateKey = parsePrivateKey(privateKeyContent);
Certificate[] certificates = parseCertificate(certificateContent).values()
.toArray(new Certificate[]{});
KeyStore keyStore = createEmptyKeyStore();
keyStore.setKeyEntry("client", privateKey, null, certificates);
}
private static PrivateKey parsePrivateKey(String identityContent) throws ... {
// ... (current parsing logic)
}
private static KeySpec getKeySpecFromAsn1StructuredData(byte[] decodedPrivateKeyContent) throws ... {
// ... (current ASN.1 parsing logic)
}
// ... (other methods)
private static Map<String, Certificate> parseCertificate(String certificateContent) throws ... {
// ... (current certificate parsing logic)
}
}
I also need to handle an ASN.1 encoded private key. I could use BouncyCastle, but again I wanted to avoid it because I am curious about alternatives. I found the asn-one Java library to parse ASN.1 encoded private keys, 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 article ASN.1 key structures in DER and PEM, I learned that a private key with the following header/footer: -----BEGIN ENCRYPTED PRIVATE KEY----- * -----END ENCRYPTED PRIVATE KEY-----
is also an ASN.1 encoded private key with a specific structure.
I've tried different approaches, but I am not sure how to parse it correctly to a Java object and load it as a KeySpec
. Any help is welcome!
英文:
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 https://stackoverflow.com/questions/2654949/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);
}
}
答案1
得分: 4
一个更正式的关于PEM格式的参考资料是rfc7468的第11节,它指定了ASN.1内容(在去除PEM后)是PKCS8的加密形式,定义在rfc5208的第6节和附录A中略微进行了修改:
EncryptedPrivateKeyInfo ::= SEQUENCE {
encryptionAlgorithm AlgorithmIdentifier {{KeyEncryptionAlgorithms}},
encryptedData EncryptedData
}
EncryptedData ::= OCTET STRING
(已删除部分)
KeyEncryptionAlgorithms ALGORITHM-IDENTIFIER ::= {
... -- 对于本地配置文件
}
但它未能解释在PKCS8中通常(除了PKCS12之外)使用的密钥加密算法是PKCS5 v2 rfc2898 或现在的v2.1 rfc8018中定义的基于密码的加密(次要差异在这里并不重要)。特别是对于您的问题中的密钥文件,AlgorithmIdentifier在OpenSSL中解析为:
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在第6.2节中定义,它由PBKDF2密钥派生后跟实际上任何对称加密/解密组成。
您应该能够看到顶层参数与附录A.4中的PBES2-Params
匹配,而密钥派生功能部分中的参数与附录A.2中的PBKDF2-Params
匹配(包括盐和迭代次数,省略了密钥长度和PRF),而用于加密方案部分的参数只是一个包含初始化向量(IV)的OCTET STRING,参见附录B.2.2。
因此,要解密这个密钥,您需要根据第5.2节的规定实现PBKDF2密钥派生,使用那些参数,然后使用CBC模式中的三重DES(也称为TDEA)解密,密钥长度为三倍长度,使用PKCS5/7填充,Java加密支持的方式是Cipher.getInstance("DESede/CBC/PKCS5Padding")
,使用从第一步派生的密钥和输入数据中的IV作为IVParameterSpec
。结果将是一个_PKCS8未加密_密钥结构,您可以将其作为PKCS8EncodedKeySpec
通过适当的KeyFactory
运行,就像您已经为未加密的输入格式所做的那样。
尽管考虑到JRE的大小通常超过100MB,而Bouncy库只增加了大约5MB,而且使用起来更加容易,但我不能说我同意您的评估。
英文:
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, <strike>which Java crypto supports as SecretKeyFactory.getInstance("PBKDF2withHmacSHA1")
and PBEKeySpec
,</strike> 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 https://stackoverflow.com/questions/61613286/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!
答案2
得分: 2
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...
]
]
]
(Note: The code is quite long and complex. I've provided the output of the code execution along with the private key and certificate information.)
英文:
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;
}
}
答案3
得分: 1
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);
}
}
英文:
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);
}
}
答案4
得分: 1
我花了几个星期的时间在我的pem-utils项目上工作,现在我拥有了纯Java代码,没有使用任何外部库,似乎可以加载我投入其中的任何内容。
我希望这能帮助原始发布者(在18个月前!),以及其他跟随我们脚步的人。
英文:
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.
答案5
得分: 1
下面是翻译好的内容:
最终我决定使用BC来解析PEM文件中的证书和私钥。在我的使用情况下,我需要处理各种不同类型的私钥,因此复杂性也增加了,但我希望能够尽可能地简化自己和其他人的操作,因此我将其封装成了一个库。下面是一个示例用法。该库在这里可用:GitHub - SSLContext Kickstart
import javax.net.ssl.SSLContext;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.List;
import nl.altindag.ssl.SSLFactory;
import nl.altindag.ssl.util.PemUtils;
class App {
public static void main(String[] args) {
PrivateKey privateKey = PemUtils.parsePrivateKey(privateKeyContent, privateKeyPassword);
List<X509Certificate> certificateChain = PemUtils.parseCertificate(certificateChainContent);
SSLFactory sslFactory = SSLFactory.builder()
.withIdentityMaterial(privateKey, privateKeyPassword, certificateChain)
.build();
SSLContext sslContext = sslFactory.getSslContext();
}
private static char[] privateKeyPassword = "secret".toCharArray();
private static String privateKeyContent = "..."; // 省略私钥内容
private static String certificateChainContent = "..."; // 省略证书链内容
}
英文:
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. The library is available here: GitHub - SSLContext Kickstart
import javax.net.ssl.SSLContext;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.List;
import nl.altindag.ssl.SSLFactory;
import nl.altindag.ssl.util.PemUtils;
class App {
public static void main(String[] args) {
PrivateKey privateKey = PemUtils.parsePrivateKey(privateKeyContent, privateKeyPassword);
List<X509Certificate> certificateChain = PemUtils.parseCertificate(certificateChainContent);
SSLFactory sslFactory = SSLFactory.builder()
.withIdentityMaterial(privateKey, privateKeyPassword, certificateChain)
.build();
SSLContext sslContext = sslFactory.getSslContext();
}
private static char[] privateKeyPassword = "secret".toCharArray();
private static 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-----
""";
private static 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-----
""";
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论