问题
I have a client certificate composed of two files (.crt and .key) which I wish to import to a java KeyStore to then use in a SSLContext to sent HTTP requests with Apache's HTTPClient. However, I can't seem to find a way to do this programmatically, most other questions I've found either point to external tools or aren't fit for my case.
My certificate is encoded with the typical 'BEGIN CERTIFICATE' followed by a Base64 encoded string, and the key with 'BEGIN RSA PRIVATE KEY' and then another Base64 encoded string.
This is what I got so far:
private static SSLContext createSSLContext(File certFile, File keyFile) throws IOException {
try {
PEMParser pemParser = new PEMParser(new FileReader(keyFile));
JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider(new BouncyCastleProvider());
Object object = pemParser.readObject();
KeyPair kp = converter.getKeyPair((PEMKeyPair) object);
PrivateKey privateKey = kp.getPrivate();
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
FileInputStream stream = new FileInputStream(certFile);
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(stream);
KeyStore store = KeyStore.getInstance("JKS");
store.load(null);
store.setCertificateEntry("certificate", cert);
store.setKeyEntry("private-key", privateKey, "changeit".toCharArray(), new Certificate[] { cert });
SSLContext sslContext = SSLContexts.custom()
.loadKeyMaterial(store, "changeit".toCharArray())
.build();
return sslContext;
} catch (IOException | NoSuchAlgorithmException | CertificateException | KeyStoreException | KeyManagementException | UnrecoverableKeyException e) {
throw new IOException(e);
}
}
Stacktrace:
java.io.IOException: java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format at me.failedshack.ssltest.SSLTest.createSSLContext(SSLTest.java:80) at me.failedshack.ssltest.SSLTest.main(SSLTest.java:31)
Caused by: java.security.spec.InvalidKeySpecException: java.security.InvalidKeyException: invalid key format at java.base/sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:216) at java.base/java.security.KeyFactory.generatePrivate(KeyFactory.java:390) at me.failedshack.ssltest.SSLTest.createSSLContext(SSLTest.java:62) ... 1 more
Caused by: java.security.InvalidKeyException: invalid key format at java.base/sun.security.pkcs.PKCS8Key.decode(PKCS8Key.java:330) at java.base/sun.security.pkcs.PKCS8Key.decode(PKCS8Key.java:355) at java.base/sun.security.rsa.RSAPrivateCrtKeyImpl.(RSAPrivateCrtKeyImpl.java:91) at java.base/sun.security.rsa.RSAPrivateCrtKeyImpl.newKey(RSAPrivateCrtKeyImpl.java:75) at java.base/sun.security.rsa.RSAKeyFactory.generatePrivate(RSAKeyFactory.java:315) at java.base/sun.security.rsa.RSAKeyFactory.engineGeneratePrivate(RSAKeyFactory.java:212) ... 3 more
Sadly I keep getting an InvalidKeyException when generating the private key from the file.
回答1:
A PEM file of type RSA PRIVATE KEY
is base64 not binary and more importantly is in PKCS1 format NOT PKCS8 and thus cannot be processed as a PKCS8EncodedKeySpec
.
Your choices are:
convert the PKCS1 PEM format to PKCS8 (unencrypted) PEM format; read that and drop the header and trailer lines and decode the base64 to binary and put that in
PKCS8EncodedKeySpec
-- but you say you don't want external tools, plus it's just as easy to convert the privatekey PLUS cert (or chain) into a PKCS12 (DER) which is already a Java keystore and avoid the issueconvert the PKCS1 PEM format to PKCS8 (unencrypted) DER format, which you can just read as binary and put in
PKCS8EncodedKeySpec
-- dittoif the PKCS1 PEM is unencrypted, read and decode it as above to PKCS1 DER then manually construct the PKCS8 (unencrypted) encoding, and use that
if the PKCS1 PEM is encrypted, which you can detect because its body contains two 822-style header lines in addition to the base64, you have to replicate OpenSSL's 'legacy' key file decryption, PLUS construct the PKCS8 (unencrypted) encoding
if you can use BouncyCastle specifically
bcpkix
, it can directly read and parse all the PEM variants used by OpenSSL for privatekeys, including decrypting the encrypted ones; however, if you're not already using it, that's an additional jar to install and/or deploy
See one or more of these dupes:
Load certificate to KeyStore (JAVA) (Q constructs PKCS8 using BouncyCastle)
Java: Convert DKIM private key from RSA to DER for JavaMail (my answer constructs PKCS8 'by hand')
How to Load RSA Private Key From File (reads using BouncyCastle)
Read RSA private key of format PKCS1 in JAVA (reads using BouncyCastle)
Get a PrivateKey from a RSA .pem file (decrypt using BC)
Decrypting an OpenSSL PEM Encoded RSA private key with Java? (decrypt manually)
maybe PKCS#1 and PKCS#8 format for RSA private key (background)
and Differences between "BEGIN RSA PRIVATE KEY" and "BEGIN PRIVATE KEY" (background)
来源:https://stackoverflow.com/questions/51352762/importing-pem-certificate-into-java-keystore-programmatically