1

I am trying to generate PKCS12 public and private keys with OpenSSL on Windows 7 64-bit that can be used by both the Microsoft CryptoAPI in C as well as Java programs.

Here are the steps I have followed:

Downloaded installed Microsoft Visual C++ 2008 Redistributable Package (x64) de: http://www.microsoft.com/en-us/download/details.aspx?id=15336

Downloaded installed "Win64 OpenSSL v1.0.2a Light" de: http://slproweb.com/products/Win32OpenSSL.html

To encrypt/decrypt files of arbitrary size using asymmetric (public) key cryptography you need to use S/MIME encoding:

1) generate the key pair. This makes a 2048 bit public encryption key/certificate rsakpubcert.key and a matching private decryption key rsakpriv.key. The -days 10000 means keep it valid for a long time (27 years or so). You will be asked (twice) for a PEM passphrase to encrypt the private key. If you do not wish to encrypt it, pass the -nodes option. The public key can be distributed to anyone who wants to send you data.

md C:\OpenSSL-Win64\bin    
cd C:\OpenSSL-Win64\bin    
set OPENSSL_CONF=C:\OpenSSL-Win64\bin\openssl.cfg    
openssl req -x509 -days 10000 -newkey rsa:2048 -keyout c:\opensslkeys\rsakpriv.key -out c:\opensslkeys\rsakpubcert.key

// pass phrase used: mypassword

2) Create request for self-signed certificate

openssl req -new -key c:\opensslkeys\rsakpriv.key -out c:\opensslkeys\server.csr

3) Remove password from the private key

openssl rsa -in c:\opensslkeys\rsakpriv.key -out c:\opensslkeys\rsakprivnopassword.key

4) Self-sign the certificate request (-days is expiration in days)
Important: start the command prompt with "Run As Administrator". Otherwise you get the same: unable to write 'random state' error.

cd C:\OpenSSL-Win64\bin    
set OPENSSL_CONF=C:\OpenSSL-Win64\bin\openssl.cfg    
openssl x509 -req -days 365 -in c:\opensslkeys\server.csr -signkey c:\opensslkeys\rsakprivnopassword.key -out c:\opensslkeys\server.crt

5) Convert output to PKCS#12 format which we can use in code (-keysig parameter allows us to use key-pair for signing)

openssl pkcs12 -export -in c:\opensslkeys\server.crt -inkey c:\opensslkeys\rsakprivnopassword.key -out c:\opensslkeys\mypublicencryptionkey.p12    
// export password used: mypassword

At this point I can encrypt and decrypt files with openssl with these commands:

To encrypt:

openssl smime -encrypt -binary -aes-256-cbc -in c:\opensslkeys\todo.txt -out c:\opensslkeys\done.txt -outform DER c:\opensslkeys\server.crt

To decrypt:

openssl smime -decrypt -binary -in c:\opensslkeys\done.txt -inform DER -out c:\opensslkeys\redone.txt -inkey c:\opensslkeys\rsakprivnopassword.key

However when I try to use the keys in Java the program chokes, saying the key format is wrong.

Any help greatly appreciated! I promise to post the com,plete working answer for others.


Complete Java code:

/*
PrivatePublicKey.java   
*/
import java.io.FileInputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.Certificate;
import javax.crypto.Cipher;
/* for IBM JDK need to replace: */
//import java.util.Base64;
/* with: */
import org.apache.commons.codec.binary.Base64;

public class PrivatePublicKey 
{
    public static void main(String[] args) throws Exception 
    {
        try 
        {
            PublicKeyReader myPublic = new PublicKeyReader();
            PublicKey publicKey = myPublic.get("./rsakpubcert.key");

            PrivateKeyReader myPrivate = new PrivateKeyReader();
            PrivateKey privateKey = myPrivate.get("./rsakprivnopassword.key");

            // Let's encrypt with private and decrypt with public
            // Encrypt with private key
            String firstString = "Ishana";

            Cipher privateEncryptCipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
            privateEncryptCipher.init(Cipher.ENCRYPT_MODE, privateKey);

            byte[] encryptedFirstString = privateEncryptCipher.doFinal(firstString.getBytes());
            String encodedEncryptedFirstString = Base64.encodeBase64String(encryptedFirstString);

            System.out.println("Encoded encrypted String for Ishana: " + encodedEncryptedFirstString);

            // Decrypt with public key
            // First decode the string
            byte[] decodedEncryptedFirstString = Base64.decodeBase64(encodedEncryptedFirstString);

            Cipher publicDecryptCipher = Cipher
                .getInstance("RSA/ECB/PKCS1Padding");
            publicDecryptCipher.init(Cipher.DECRYPT_MODE, publicKey);
            byte[] decryptedFirstStringByte =     publicDecryptCipher.doFinal(decodedEncryptedFirstString);
            System.out.println("Decrypted String for Ishana: " + new String(decryptedFirstStringByte));
        }
        catch (Exception e) 
        {
            e.printStackTrace();
        }
    }
}

Edit, added helper code:

import java.io.*;
import java.security.*;
import java.security.spec.*;


public class PublicKeyReader {

  public static PublicKey get(String filename)
throws Exception {

    File f = new File(filename);
    FileInputStream fis = new FileInputStream(f);
    DataInputStream dis = new DataInputStream(fis);
    byte[] keyBytes = new byte[(int)f.length()];
    dis.readFully(keyBytes);
    dis.close();

    X509EncodedKeySpec spec =
      new X509EncodedKeySpec(keyBytes);
    KeyFactory kf = KeyFactory.getInstance("RSA");
    return kf.generatePublic(spec);
  }
}

and

import java.io.*;
import java.security.*;
import java.security.spec.*;

public class PrivateKeyReader {

  public static PrivateKey get(String filename)
  throws Exception {

    File f = new File(filename);
    FileInputStream fis = new FileInputStream(f);
    DataInputStream dis = new DataInputStream(fis);
    byte[] keyBytes = new byte[(int)f.length()];
    dis.readFully(keyBytes);
    dis.close();

    PKCS8EncodedKeySpec spec =
      new PKCS8EncodedKeySpec(keyBytes);
    KeyFactory kf = KeyFactory.getInstance("RSA");
    return kf.generatePrivate(spec);
  }
}
Bertrand_Szoghy
  • 880
  • 1
  • 11
  • 26
  • PublicKeyReader/PrivateKeyReader are not standard JRE classes. Which library do they belong to? – Robert Apr 23 '15 at 14:27
  • I see this was migrate from Server Fault. Also see [Where do I post questions about Dev Ops?](http://meta.stackexchange.com/q/134306). – jww Apr 23 '15 at 16:46
  • Java crypto (JCE) can use PKCS#12 directly; just `java.security.Keystore.getInstance("PKCS12")` and `.load` it from your file, then use the key and cert in it for the operations you want. (PKCS#12 in general can have multiple keys&certs in it, and Java supports this, and so does OpenSSL *library*, but not OpenSSL *commandline*.) – dave_thompson_085 Apr 24 '15 at 09:50

2 Answers2

1

How to generate public and private keys usable for both C and Java?

Generating the key pairs and representing them in memory is different than consuming them in Java or another framework/library, like OpenSSL.

Representing keys in memory is framework/library specific.

Encoding the keys so different frameworks/libraries can consume them is the trick.


However when I try to use the keys in Java the program chokes, saying the key format is wrong.

In Java, its usually enough to call getBytes() on the key. The key will be provided in a natural representation. For example calling it on a RSA public key will result in an ASN.1 encoding from PKCS 1.

Other formats you can consider for interoperability are PKCS8 for private keys and PKCS12.

There's also PEM, but its an older format. PEM is OK for public keys, but you should use PKCS8 or PKCS12 for private keys.


The private you generated with OpenSSL is probably in encrypted PEM format (you should state the encoding you are using for them). See java read pem encoded key and How to read a password encrypted key with java?. If its DER encoded, then see java read der encoded key

Community
  • 1
  • 1
jww
  • 97,681
  • 90
  • 411
  • 885
  • `openssl req -newkey` in the version OP identified (1.0.2) creates PKCS#8 encrypted (and PEM), which Java can't handle directly. `openssl rsa` removes the encryption, but uses "raw" PKCS#1. Doing `openssl pkey` instead would produce unencrypted PKCS#8, and adding `-outform der` would make it readable by Java KeyFactory of type RSA. PKCS#8 and especially PKCS#**12** are common file formats; PKCS#**11** is the generic interface to hardware or pseudo-hardware devices which generally don't use files in any format. – dave_thompson_085 Apr 24 '15 at 09:51
  • Thanks Dave. I knew I should have verified PKCS11 rather than depending upon memory.... – jww Apr 24 '15 at 09:52
0

I was finally able to make this work.

The starting point was: http://blogs.msdn.com/b/alejacma/archive/2008/01/28/how-to-generate-key-pairs-encrypt-and-decrypt-data-with-cryptoapi.aspx

Issues encountered

1) Microsoft public and private keys used by CryptoAPI use a proprietary binary format (generally referred to as PUBLICKEYBLOB and PRIVATEKEYBLOB) that cannot be used by Java. Conversion of keys is required;

2) The Crypto API encrypts in little-endian binary format while Java reads bytes in big-endian format. Ref: http://www.jensign.com/JavaScience/dotnet/RSAEncrypt/ and http://en.wikipedia.org/wiki/Endianness Binary conversion is required;

3) The Microsoft CryptoAPI pads the message when it encrypts it. This means to decrypt in Java, the exact implementation of the RSA algorithm is required which takes into consideration the padding scheme used.

On the Java decrypt side, I used Cipher.getInstance("RSA/NONE/PKCS1Padding");

Hope this is helpful to someone.

Bertrand_Szoghy
  • 880
  • 1
  • 11
  • 26
  • Hello. In regards to #1, can you give advice how to convert the BLOB to plaintext and back to BLOB format? – mikew Jan 22 '21 at 03:44