Convert a PKCS#8 private key to PEM in java - java

Hello everyone I'm trying to convert a PKCS#8 private key that I generate in my java program to a PEM encoded file.
Security.addProvider(new BouncyCastleProvider());
SecureRandom rand = new SecureRandom();
JDKKeyPairGenerator.RSA keyPairGen = new JDKKeyPairGenerator.RSA();
keyPairGen.initialize(2048, rand);
KeyPair keyPair = keyPairGen.generateKeyPair();
PEMWriter privatepemWriter = new PEMWriter(new FileWriter(new File(dir + "private.key")));
privatepemWriter.writeObject(keyPair.getPrivate());
After running the program I have the private key in both formats and a public key(the code isn't shown as it works). I then use this openssl command to conver the private.key back to a pem formated file.
openssl pkcs8 -nocrypt -inform DER -in private.key -out private2.pem
When I compare private.pem and private2.pem they are different and obviously when I try to use private.pem it says it's not a valid file.
What step am I missing in order to properly convert this private key into the PEM format that I need? I can't use OpenSSL from within my program, otherwise I would simply add that function call. I have access to BouncyCastle libs in this program, so maybe it has a solution I'm overlooking.

You can use the PEMWriter class in Bouncycastle.

The fact that OpenSSL uses it's own format is really the only thing that makes this challenging. Thankfully the bouncy castle PEMWriter makes this easy, but the interface isn't very well documented. I found some code by searching through the mailing list. I've adapted it below:
KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
keyGen.initialize(2048);
KeyPair keyPair = keyGen.generateKeyPair();
StringWriter stringWriter = new StringWriter();
PEMWriter pemWriter = new PEMWriter(stringWriter);
pemWriter.writeObject( keyPair.getPrivate());
pemWriter.close();
privateKeyString = stringWriter.toString();

Use the header:
-----BEGIN PRIVATE KEY-----
… and the footer:
-----END PRIVATE KEY-----
Note that the "RSA" is left out—The Java code is using PKCS #8 encoding for the private key, and that encoding includes the algorithm.
The openssl command that you show is converting a standard PKCS #8 key in DER form to a proprietary OpenSSL key in PEM form. To keep the PKCS #8 format, but convert from DER to PEM, add the -topk8 option. Then the OpenSSL output should match what your Java code is producing.
If you need to produce the OpenSSL key, instead of PKCS #8, it's possible, but you'll have to create your own OpenSSL structure with the BouncyCastle ASN.1 library and encode that. Please clarify if that's what you need.

Related

How to work with OPENSSH PRIVATE KEY in Java?

I am generating a DSA key with the below command:
ssh-keygen -t dsa
Then I try to sign data using bouncycastle API like that:
KeyFactory keyFactory = KeyFactory.getInstance("DSA");
String privateKeyContent = // the content of the generated file
//init privateKey
byte[] pemContent = null;
PEMParser pemParser = new PEMParser(new StringReader(privateKeyContent));
Object pemObject = pemParser.readObject(); // throws
And getting this exception
java.io.IOException: unrecognised object: OPENSSH PRIVATE KEY
So I have been trying to convert the key file to PEM, using this example, and executing:
ssh-keygen -e -f key -m PEM > key.pem
But I am getting an error:
do_convert_to_pem: unsupported key type DSA
Any ideas on how to solve this?
There are a few things going on here.
You are generating keys using a pretty recent version of OpenSSH (which is good). These are now output in OpenSSH's new key format which the BouncyCastle API does not recognise as its a custom format.
You are generating a DSA key. OpenSSH deprecated use of DSA as it's not considered as secure as the other private key types provided like RSA, ECDSA, ED25519 etc. So whilst its letting you generate the key; its not letting you convert it.
I would recommend that you change the key type to an RSA key with 2048 bits (minimum). That will, however, not stop the BouncyCastle API error because it will still be in the new OpenSSH format.
It really depends on what you are doing with the key. If you not using it within an SSH API to authenticate to remote servers and simply want to sign data with BouncyCastle API then you would be better off generating the key using OpenSSL with the command
openssl genrsa -out private.pem 2048
This key should then be recognised by the BouncyCastle API.

Python equivalent code of javax.crypto.Cipher

I have java code to Encrypt the text using pem certificate as following
/* Load Public key from certificate file */
CertificateFactory fact = CertificateFactory.getInstance("X.509");
FileInputStream is = new FileInputStream("cert.pem");
Certificate cer = fact.generateCertificate(is);
PublicKey publicKey = cer.getPublicKey();
/* Encrypt data using public key */
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
String encryptedData = Base64.encodeBase64String(cipher.doFinal(data.getBytes("UTF-8")));
I am able to extract the public key from cert.pem using following steps in python:
# Extract public key from certificate file using openssl
openssl x509 -inform pem -in cert.pem -pubkey -noout > publickey.pem
# Load public key in python
from Crypto.PublicKey import RSA
pubkey = open('publickey.pem').read()
key = RSA.importKey(pubkey)
The Java code results in different encrypted string for the a text for each new run where as if I use following python code to encrypt the data it produces same encrypted string for the a text for each new run.
b64encode(key.encrypt(data, 32)[0])
Now I am not sure how to encrypt the data in python that gives same result as above Java code. Can you suggest something, I have searched all over internet and haven't found any correct solution ?

What is the openssl equivalent of using java's KeyPairGenerator?

Firstly I tried to ask this on security -- I got some upvotes but it seems it has had no answer for a week now. I understand that this is openssl related, however it stems from using a java KeyPairGenerator object so I feel it may be valid for stack overflow. Please see the code below:
I have been using java's KeyPairGenerator in order to generate public/private keys within a program so that I can encrypt and decrypt files(also using java encrypt/decrypt methods). I want to be able to move over to using openssl in order to generate these public private keypairs however I keep getting padding exceptions when I decrypt my files if i used command line generated openssl keys. For example instead of using java's KeyPairGenerator, I try and use openssl to generate keys:
openssl rsa -in keypair.pem -outform DEF -pubout -out public.der
openssl pkcs8 -topk8 -nocrypt -in keypair.pem -outform DER -out private.der
Where I try to use the DER files to encrypt/decrypt my files. Ultimately every key format I have tried seems to give me problems.
I'm assuming that this means that the format of the keys in my openssl commands do not match up how java's KeyPairGenerator works. Here is a snippet of my key generating code:
public void createPublicPrivateKeys() throws IOException, NoSuchAlgorithmException {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
logger.info("Password to encrypt the private key: ");
String password = in.readLine();
logger.info("Generating an RSA keypair.");
// Create an RSA key key pair
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(4096);
KeyPair keyPair = keyPairGenerator.genKeyPair();
logger.info("Done generating the keypair.\n");
// Write public key out to a file
String publicKeyFilename = "publicKey.der";
logger.info("Public key filename: " + publicKeyFilename);
// Get encoded form of the public key for future usage -- this is X.509 by default.
byte[] publicKeyBytes = keyPair.getPublic().getEncoded();
// Write the encoded public key
FileOutputStream fos = new FileOutputStream(publicKeyFilename);
fos.write(publicKeyBytes);
fos.close();
String privateKeyFilename = "privateKey.der";
logger.info("Private key filename: " + privateKeyFilename);
// Get the encoded form -- PKCS#8 by default.
byte[] privateKeyBytes = keyPair.getPrivate().getEncoded();
// Encrypt the password
byte[] encryptedPrivateKeyBytes = new byte[0];
try {
encryptedPrivateKeyBytes = passwordEncrypt(password.toCharArray(), privateKeyBytes);
} catch (Exception e) {
e.printStackTrace();
}
fos = new FileOutputStream(privateKeyFilename);
fos.write(encryptedPrivateKeyBytes);
fos.close();
}
What is the openssl equivalent command line statement of using java's standard KeyPairGenerator? Also note that using outside packages such as bouncy castle is not an option.
OpenSSL commandline rsa or pkcs8 do not generate a keypair; they only convert from one form to another and/or display. For RSA (only), genrsa generates a keypair, in OpenSSL's 'legacy' privatekey format which is not (easily) compatible with Java. Your rsa and pkcs8 commands do convert the legacy format to the "X.509" (SPKI) and PKCS#8 DER formats Java prefers. OpenSSL since 1.0.0 also has genpkey which generates keys, or where applicable parameters, for all supported asymmetric algorithms including RSA, and defaults to PKCS#8 output.
Your Java code has a passwordEncrypt step that is not standard Java and not explained. OpenSSL library supports password-based encryption of PKCS#8 according to that standard if that is what your passwordEncrypt does, but most OpenSSL commandline functions don't.
If you are trying to use the private.der of your example in Java which is trying to do any kind of password-based (or other) decryption on it, that won't work because it isn't encrypted; it isn't even in the PKCS#8 structure used for an encrypted key.
However that error would occur before you even try to decrypt any data, or more likely a working key.

How can I read a BouncyCastle private key PEM file using JCA? [duplicate]

This question already has an answer here:
convert openSSH rsa key to javax.crypto.Cipher compatible format
(1 answer)
Closed 6 years ago.
In one of our applications private keys are stored using BouncyCastle's PEMWriter. At the moment I am investigating if we can get rid of the BouncyCastle dependency since Java 7 seems to have everything we need. The only issue is that I can not read the private keys stored in the database as PEM-encoded strings (the certificates/public keys are fine).
If I save the PEM-encoded string of the private key from the database to a file I can run OpenSSL to convert the key to PKCS#8 format like this:
openssl pkcs8 -topk8 -inform PEM -outform DER \
-in private_key.pem -out private_key.der -nocrypt
The resulting output I can base64 encode and then read using this bit of Java/JCA code:
byte[] privateKeyBytes =
DatatypeConverter.parseBase64Binary(privateKeyDERcontents);
PrivateKey prKey =
KeyFactory.getInstance("RSA").
generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
This private key matches the public key stored as expected, i.e. I can round-trip from plaintext to ciphertext and back.
The question I have is: can I directly read the original PEM encoding somehow?
EDIT
Here is a bit of code that reads the strings in question using BouncyCastle:
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
}
PEMReader pemReader = new PEMReader(new StringReader(privateKeyPEM));
KeyPair keyPair = (KeyPair) pemReader.readObject();
PrivateKey key = keyPair.getPrivate();
The "privateKeyPEM" is the PEM encoded string in the database, otherwise this example is self-contained. Interestingly it already uses the JCA KeyPair object as output. To rephrase my original question: can I do the equivalent of the code above without depending on PEMReader (and in turn quite a few other BouncyCastle classes)?
Key inside of PEM file is already stored in PKCS#8 format, so if it is not encrypted with password you can just remove headers (-----BEGIN RSA PRIVATE KEY-----), Base64-decode input, and get the needed bytes.

Is there any java API to convert certificate from 'DER' format to pkcs#12 format

I need to convert certificate from DER format to pkcs#12 format. I know I can do this using openssl command. I am looking for java API / Class which can do this job.
Any help will be appreciated.
You can try to load the PKCS#12 container as a keystore:
java.security.KeyStore ks = java.security.KeyStore.getInstance("PKCS12");
ks.load(new java.io.FileInputStream("yourStore.p12"), "yourPassword".toCharArray());
Once loaded, you can enumerate the elements inside the container:
for(Enumeration enum = ks.aliases(); enum.hasMoreElements(); ) {
String alias = (String) enum.nextElement();
System.out.println("#:" + alias);
if (ks.isKeyEntry(alias)) {
System.out.println("return PrivateKey");
PrivateKey pk = (PrivateKey) ks.getKey(alias, password);
// ...
}
}
As I understand it, PEM is simply the Base64 encoded string of the DER content with the appropriate header and footer lines. To convert to Base64 you can use javax.mail.internet.MimeUtility for example.
This can convert pkcs from PEM
openssl pkcs12 -export -in pem-certificate-and-key-file -out pkcs-12-certificate and-key-file
So lets convert your DER to PEM first
openssl dsa -inform PEM|DER -outform DER|PEM -in pem-file|der-file -out der-file|pem-file
I've gotten a lot of mileage out of Portecle.
If you must do it programmatically, much of what you need is in the KeyStore class in Java. As for user friendliness, well, it's pretty selective about its friends. Open a store, add to it, save it. If you need cert chains, that'll be a bit more complicated.
As for retrieving the cert from the DER encoding, see the X509Certificate javadoc. Especially the references to CertificateFactory.

Categories