From certificate Alias to PEM File with private key included using Java

廉价感情. 提交于 2019-12-11 07:32:34

问题


I have this code to generate a CER file using the alias:

public class TestFromAliasToCER {

    public static final int KEY_SIZE = 1024;
    public static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----";
    public static final String END_CERT = "-----END CERTIFICATE-----";
    public final static String LINE_SEPARATOR = System.getProperty("line.separator");

    public static void main(String[] args) throws FileNotFoundException, IOException, NoSuchAlgorithmException, NoSuchProviderException, KeyStoreException, CertificateException {


           KeyStore keyStore = KeyStore.getInstance ("Windows-MY");
           keyStore.load (null, null);         
           Enumeration<String> aux = keyStore.aliases();
           String alias = aux.nextElement();
           X509Certificate  certificate = (X509Certificate) keyStore.getCertificate (alias);
           String certString = formatCrtFileContents(certificate);         
           PrintWriter out = new PrintWriter("cert.CER");
           out.println(certString);
           out.close();

    }

    public static String formatCrtFileContents(final Certificate certificate) throws CertificateEncodingException { 

        final Base64.Encoder encoder = Base64.getMimeEncoder(64, LINE_SEPARATOR.getBytes());
        final byte[] rawCrtText = certificate.getEncoded();
        final String encodedCertText = new String(encoder.encode(rawCrtText));
        final String prettified_cert = BEGIN_CERT + LINE_SEPARATOR + encodedCertText + LINE_SEPARATOR + END_CERT;
        return prettified_cert;
    }
}

This creates the cer file with

-----BEGIN CERTIFICATE-----
data
-----END CERTIFICATE-----

I want to be able to create a PEM Certificate with the private key included, is it possible? If not, why?

I'm not restricted to Java only and free to use any Java API, but preferable with the least user interaction as possible.


回答1:


Although I don't see it documented, according to the source the SunMSCAPI provider implements only a stub for getEncoded and cannot export Windows privatekey so you can't do this with JCA.

You could of course write JNI or JNA to call Windows CAPI, but that's not simple.

To use existing tools without user interaction you can use Runtime or ProcessBuilder to

  • run certutil with arguments -exportpfx -user -p password certid filename

  • run powershell and tell it to select an object in cert:\currentuser\my and invoke the Export('PFX','password') method -- examples for machine rather than user cert here

  • or in (only) recent powershell use Export-PFXCertificate cmdlet documentation here

and after any of these, extract from pkcs12 to PEM with openssl pkcs12, or if you prefer with Java by:

  • load the PKCS12 keystore and get the PrivateKey entry

  • call getEncoded and encode the result in folded (MIME) base64 like you did for the certificate except use -----BEGIN/END PRIVATE KEY-----

Warning: Java produces an unencrypted (PKCS8) privatekey, so make certain no unauthorized user or program ever has access to this file, your disk/filesystem or any backup(s).




回答2:


A digital certificate doesn't have the private key inside it (the private key is not part of the certificate fields). The certificate and the private key are separate entities, although they're related (one can't exist without the other).

If you take a look at the certificate fields in RFC 5280, you'll see that only the public key is part of it:

Certificate  ::=  SEQUENCE  {
    tbsCertificate       TBSCertificate,
    signatureAlgorithm   AlgorithmIdentifier,
    signatureValue       BIT STRING  }

TBSCertificate  ::=  SEQUENCE  {
      ... lots of fields
    subjectPublicKeyInfo SubjectPublicKeyInfo,
      ... lots of other fields
    }

The subjectPublicKeyInfo is the public key, and there's no field for the private key.

That's because certificates are meant to be public (you can have more details on why they're public taking a look at how a Public Key Infrastructure works).

Although the certificate is public, there's always a correspondent private key somewhere, usually held by the certificate's owner (and ideally by no one else).


Anyway, the file you've got (with BEGIN CERTIFICATE and END CERTIFICATE headers) in only the digital certificate (but not the private key).

If you have the private key and the corresponding certificate, you can create a file that contains both. The most common formats for such file are: JKS (also known as Keystore) and PFX.

There are also another "format": the Windows repository (the one you're reading when you do KeyStore.getInstance("Windows-MY")). I don't know exactly in what format its files are, but the KeyStore class abstracts it.

If the private key is present, it will be together with its corresponding certificate, in the same alias. You can check if the key is present with this code:

String alias = aux.nextElement();
if (keyStore.isKeyEntry(alias)) { // alias contains a private key
    Key key = keyStore.getKey(alias, "password".toCharArray()); // need to know the password
    // key is the private key

    // cert is the key's corresponding certificate
    X509Certificate cert = (X509Certificate) keyStore.getCertificate(alias);
} else if (keyStore.isCertificateEntry(alias)) { // alias doesn't contain a key
    X509Certificate cert = (X509Certificate) keyStore.getCertificate(alias);
}

After having the key, you can save it to another keystore with the following code:

// create another keystore
KeyStore output = KeyStore.getInstance("JKS");

// "alias" - choose to whatever name you want
// privateKey is the object you've got from keyStore.getKey()
// "password" is the password for this alias
// cert will be stored in the same alias
output.setKeyEntry("alias", privateKey, "password".toCharArray(), new Certificate[] { cert });

// save the keystore to a file
output.store(new FileOutputStream("outputfile.jks"), "keystore password".toCharArray());

The code above creates the file outputfile.jks containing the certificate and the private key.

If you want the file to be a PFX, you can change the code above to:

// PKCS12 == PFX format
KeyStore output = KeyStore.getInstance("PKCS12");

// alternative: in pfx, I think that alias can't have specific passwords
// so you can use this as it doesn't require a password for the alias entry
output.setKeyEntry("alias", privateKey.getEncoded(), new Certificate[] { cert });

// change file extension to ".pfx"
output.store(new FileOutputStream("outputfile.pfx"), "keystore password".toCharArray());


来源:https://stackoverflow.com/questions/44311086/from-certificate-alias-to-pem-file-with-private-key-included-using-java

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!