如何使用 java 代码将证书和私钥导出为单个文件?

How to export Certificate and private key as a single file using java code?

我的要求是结合证书和私钥,然后使用 PEMwriter 导出文件。

我用bouncycastle生成了私钥和csr。然后,我向CA提交了CSR并获得了证书。 以下代码用于生成密钥对

KeyPair idPair = KeyPairGenerator.getInstance("RSA").genKeyPair();
PublicKey publicKey = idPair.getPublic();
PrivateKey privkey = idPair.getPrivate();

以下代码用于接收证书并将其导出到 .cer 文件。

CertStore store = response.getCertStore();
            Collection<? extends Certificate> certs = store
                    .getCertificates(null);
            Certificate[] chain = new Certificate[certs.size()];

            int i = 0;
            for (Certificate certificate : certs) {
                chain[i++] = certificate;
            }

            FileOutputStream os = new FileOutputStream("cert.cer");
            os.write("-----BEGIN CERTIFICATE-----\n".getBytes("US-ASCII"));
            os.write(Base64.encodeBase64(chain[0].getEncoded(), true));
            os.write("-----END CERTIFICATE-----\n".getBytes("US-ASCII"));
            os.close();

现在,我的目标是当我打开导出的文件时,系统必须提示我输入 install/view 证书的密码。我对这部分真的很困惑。之前我将密码添加到密钥库,如下所示

KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
        keyStore.load(null, null);

        keyStore.setKeyEntry("mykey", (Key) keyPair.getPrivate(), "Password1!".toCharArray(), certz);

        ByteArrayOutputStream bout = new ByteArrayOutputStream();
        keyStore.store(bout, "Password1!".toCharArray()); // this is the password to open the .p12

        byte[] keystore = bout.toByteArray();
        bout.close();

但是,现在,我需要用密码保护证书。这可能吗?如果是这样,文件扩展名是什么?请在编码部分 (PemWriter) 指导我。 谢谢

THe following code is for receiving the certificate and exporting it to .cer file.

        CertStore store = response.getCertStore();
        Collection<? extends Certificate> certs = store
                .getCertificates(null);
        Certificate[] chain = new Certificate[certs.size()];
        int i = 0;
        for (Certificate certificate : certs) {
            chain[i++] = certificate;
        }

旁白:您不需要那个循环来将 Collection 转换为数组,只需调用 .toArray (new T[optionally_correct_size]) 即可。另一方面,由于您只使用第一个元素,所以根本不需要数组,只需 Collection<T>.iterator().next().

更重要的是,在第三方面,许多——但绝对不是全部——使用私钥完成的操作也需要完整的证书链,而不仅仅是 leaf/end-entity 证书,才能正常工作。例如,一些签名者可以在没有链的情况下计算签名,但在某些情况下,签名无法验证,因此会被接收者拒绝。由于您不知道导出文件的使用方式、用途和用途,因此无法就此提供任何建议。如果需要 完整链,那么您是否可以只使用一系列 individual/separate PEM 证书或需要其他东西也会有所不同。

我不知道你用的是什么 Base64 编码器;该签名与 BouncyCastle 签名不匹配。我希望它能正确生成换行符。 PEM 格式不仅仅是 BEGIN-line、base64、END-line;它是 BEGIN-line,base64 ,带有换行符 ,END-line。 有些 软件会工作,有时,即使没有换行符,但有些软件有时会失败。参见 RFC 7468 sec 2 near the end

Now, my goal is when I open the exported file, I must be prompted to input password to install/view the certificate. ... But, now, I need to protect the certificate with password. Is this even possible?

您需要区分私钥和证书。尽管在重要方面相关,但它们是不同的东西,私钥设计为 private/secret,而证书设计为 public。特别是,PEM 格式证书未加密,因此任何人都可以查看它并将其用于 public 密钥操作(加密或验证),或者用它做任何他们喜欢的事情。

但是要执行私钥操作(解密或签名),您需要私钥——通常链接到如上所述的证书或链——以及私钥可以是password-encrypted(至少)两种不同的习惯PEM格式,因此需要密码'open'它。早在 1990 年代的 OpenSSL de-facto 就定义了一个 PEM 加密方案,它可以用于它的几种 'legacy' 或 'traditional' 私钥格式,这些格式对于每个算法,并在 BEGIN-line 中的 PEM 类型中指明,因此 -----BEGIN RSA PRIVATE KEY----------BEGIN DSA PRIVATE KEY----------BEGIN EC PRIVATE KEY----- 之后 PKCS8 defined a generic privatekey format for all algorithms, used by much other software including Java crypto (JCA), including an encrypted variant designated by -----BEGIN ENCRYPTED PRIVATE KEY----- (note no algorithm name). The PKCS8 version (only) has been made official by RFC7468 sec 11。 (OpenSSH、PuTTY 和 PGP 使用的私钥还有其他完全不同的 PEM-like 格式,但其中 none 使用 X.509 类型的证书。虽然 GnuPG 现在实现了 PGP 和 S/MIME,并且确实使用 X.509/PKIX for S/MIME。SO 上有许多现有的问题,一些在 security.SX 或 crypto.SX,如果您对这些感兴趣。) bcpkix 实现 OpenSSL-legacy 形式 PKCS8 形式。 (Java 单独没有 BouncyCastle 实现 PKCS8 未加密。)

类似地,'legacy' Java 密钥库(JKS 和 JCEKS)password-encrypt 私钥,但不是证书。 PKCS12 标准实际上非常灵活(也很复杂),但是 通常实施的 (包括您的 BC-keystore 示例)对私钥使用强密码加密,并且非常证书的弱易破密码加密;我一直不太清楚为什么,因为在不提供任何安全优势的情况下这不太方便。

If so, what will be the file extension?

这主要取决于您,或者这些文件的所有人(如果有的话)。没有标准要求文件扩展名与文件内容相匹配,反之亦然,尽管人们通常这样做是因为这样更方便。官方 .cer 应该是 DER 证书,并且 RFC7468 sec 5.3 建议 使用 .crt 作为 PEM证书。如果您将密钥写入单独的文件,使用 .key.pem 似乎很常见,但我知道没有标准(当然也没有 IANA 注册)对此进行指定。 (RFC5958 注册 .p8 实际上是 DER PKCS8 未加密,RFC8351 注册 DER PKCS8 加密但未指定 any 扩展名。)如果您编写密钥和same 文件的证书,PEM 格式支持但并非所有程序都支持,除了 .pem.

我什至没有看到常见的做法

所以终于可以回答你的问题了:-) 假设您的提供者列表(和类路径)中有 BC 提供者,类路径中有 bcpkix,并且 kv 中有一个 PrivateKey 对象:

import org.bouncycastle.openssl.PKCS8Generator;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.openssl.jcajce.JcaPKCS8Generator;
import org.bouncycastle.openssl.jcajce.JceOpenSSLPKCS8EncryptorBuilder;
import org.bouncycastle.openssl.jcajce.JcePEMEncryptorBuilder;
import org.bouncycastle.util.io.pem.PemWriter;

        // for the OpenSSL legacy form
        JcaPEMWriter x = new JcaPEMWriter (new OutputStreamWriter(System.out)); // or whatever 
        x.writeObject(kv, new JcePEMEncryptorBuilder("DES-EDE3-CBC").build("password".toCharArray()) );
        // can substitute AES-{128,192,256}-CBC if desired, for more see source
        x.close(); // or flush to keep underlying writer/stream

        // for the PKCS8 form
        PemWriter y = new PemWriter (new OutputStreamWriter(System.out)); // or whatever 
        y.writeObject(new JcaPKCS8Generator (kv, new JceOpenSSLPKCS8EncryptorBuilder(
                PKCS8Generator.DES3_CBC).setPasssword("password".toCharArray()).build() ) );
        // or AES_{128,192,256}_CBC, others will use PBES1 which is deprecated
        y.close(); // or flush to keep underlying writer/stream

如上所述,对于正常使用,您不需要使用密码来保护证书。但是,如果您想将一些证书存储在安全的地方,您可以在 password-protected 密钥库文件中进行(仅 java.security 代码,不需要 PemWriter)。

            KeyStore ks = KeyStore.getInstance("PKCS12");
            ks.load(null, null);

            Certificate cert = getX509Cert(); // load certificate
            ks.setCertificateEntry("myCertAlias", cert);

            FileOutputStream fos = null;
            try {
                fos = new FileOutputStream(PATH + "newKeyStore.p12");
                char[] password = PASSWORD_.toCharArray();
                ks.store(fos, password);
            } finally {
                if (fos != null) {
                    fos.close();
                }
            }