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

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

我有这段代码可以使用别名生成 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,但最好尽可能减少用户交互。

数字证书里面没有私钥(私钥不是证书字段的一部分)。证书和私钥是独立的实体,尽管它们相关(缺一不可)。

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

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

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

subjectPublicKeyInfo 是 public 密钥,没有私钥字段。

那是因为证书本来就是 ​​public(您可以详细了解为什么 public 查看 Public Key Infrastructure 的工作原理)。

虽然证书是 public,但在某处总有对应的私钥,通常由证书所有者持有(最好不是其他人)。


无论如何,您获得的文件(带有 BEGIN CERTIFICATEEND CERTIFICATE headers)只有数字格式证书(但不是私钥)。

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

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

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

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

虽然我没有看到它的文档,但根据消息来源,SunMSCAPI 提供程序只为 getEncoded 实现了一个存根并且 无法导出 Windows 私钥 所以你不能用 JCA 做到这一点。

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

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

  • 运行 certutil 带参数 -exportpfx -user -p password certid filename

  • 运行 powershell 并告诉它 select 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 密钥库并获取私钥条目

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

警告:Java 生成未加密的 (PKCS8) 私钥,因此请确保没有未经授权的用户或程序访问此文件、您的 disk/filesystem 或任何备份。