从证书别名到PEM文件,其中包含使用Java的私钥

时间:2017-06-01 15:02:51

标签: java ssl x509certificate keystore pem

我有这段代码使用别名生成CER文件:

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;
    }
}

这会使用

创建cer文件
-----BEGIN CERTIFICATE-----
data
-----END CERTIFICATE-----

我希望能够创建包含私钥的PEM证书,是否可能?如果没有,为什么?

我不仅限于Java,而且可以免费使用任何Java API,但最好尽可能减少用户交互。

2 个答案:

答案 0 :(得分:4)

虽然我没有看到它的文档记录,但根据源代码,SunMSCAPI提供程序仅实现getEncoded的存根,而无法导出Windows私钥,因此您无法用JCA做到这一点。

您当然可以编写JNI或JNA来调用Windows CAPI,但这并不简单。

要在没有用户互动的情况下使用现有工具,您可以使用RuntimeProcessBuilder

  • 使用参数certutil

  • 运行-exportpfx -user -p password certid filename
  • 运行powershell并告诉它在cert:\currentuser\my中选择一个对象并调用Export('PFX','password')方法 - examples for machine rather than user cert here

  • 或(仅)(仅)最近的powershell使用Export-PFXCertificate cmdlet documentation here

在其中任何一个之后,使用openssl pkcs12从pkcs12提取到PEM,或者如果您更喜欢使用Java:

  • 加载PKCS12密钥库并获取PrivateKey条目

  • 调用getEncoded并将结果编码为折叠(MIME)base64,就像您对证书所做的那样,除了使用-----BEGIN/END PRIVATE KEY-----

警告:Java会生成未加密的(PKCS8)私钥,因此请确保没有未经授权的用户或程序可以访问此文件,磁盘/文件系统或任何备份。

答案 1 :(得分:1)

数字证书中没有私人密钥 (私钥不是证书字段的一部分)。证书和私钥是单独的实体,但它们相关(没有另一个就不能存在)。

如果您查看RFC 5280中的证书字段,您会发现只有公钥是其中的一部分:

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

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

subjectPublicKeyInfo是公钥,私钥没有字段。

这是因为证书是公开的(你可以详细了解他们为什么公开看看Public Key Infrastructure如何运作)。

虽然证书是公开的,但在某处始终存在相应的私钥,通常由证书所有者持有(理想情况下不由其他人持有)。

无论如何,您只有数字证书(但不是私钥)才能获得(带有 BEGIN CERTIFICATE END CERTIFICATE 标题)的文件。

如果您拥有私钥和相应的证书,则可以创建包含这两者的文件。此类文件最常见的格式为: JKS (也称为密钥库)和 PFX

还有另一种“格式”:Windows存储库(当您执行KeyStore.getInstance("Windows-MY")时正在阅读的存储库)。我不确切知道它的文件是什么格式,但KeyStore类抽象它。

如果存在私钥,它将与相应的证书一起放在同一别名中。您可以使用以下代码检查密钥是否存在:

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);
}

获得密钥后,可以使用以下代码将其保存到另一个密钥库:

// 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());

上面的代码创建了包含证书和私钥的文件outputfile.jks

如果您希望文件为 PFX ,则可以将上面的代码更改为:

// 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());