使用Java和BouncyCastle生成X509证书

8

这是我现在用来生成数字证书的代码。现在,我能够生成一个带有私钥密码保护的数字证书。

public static void main(String[] args) throws Exception {
    Security.addProvider(new BouncyCastleProvider());
    testKeyStore();
}

public static void testKeyStore() throws Exception {
    try {
        String storeName = "d://suresh_test.cer";
        java.security.KeyPairGenerator keyPairGenerator = KeyPairGenerator
                .getInstance("RSA");
        keyPairGenerator.initialize(2048);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        PublicKey publicKey = keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate();
        X509Certificate trustCert = createCertificate("CN=CA", "CN=CA",
                publicKey, privateKey);
        java.security.cert.Certificate[] outChain = {
                createCertificate("CN=Client", "CN=CA", publicKey,
                        privateKey), trustCert };
        KeyStore outStore = KeyStore.getInstance("PKCS12");
        outStore.load(null, "suresh_".toCharArray());
        outStore.setKeyEntry("mykey", privateKey, "suresh_".toCharArray(),
                outChain);
        OutputStream outputStream = new FileOutputStream(storeName);
        outStore.store(outputStream, "suresh_".toCharArray());
        outputStream.flush();
        outputStream.close();

        KeyStore inStore = KeyStore.getInstance("PKCS12");
        inStore.load(new FileInputStream(storeName),
                "suresh_".toCharArray());
    } catch (Exception e) {
        e.printStackTrace();
        throw new AssertionError(e.getMessage());
    }
}

private static X509Certificate createCertificate(String dn, String issuer,
        PublicKey publicKey, PrivateKey privateKey) throws Exception {
    X509V3CertificateGenerator certGenerator = new X509V3CertificateGenerator();
    certGenerator.setSerialNumber(BigInteger.valueOf(Math.abs(new Random()
            .nextLong())));
    certGenerator.setIssuerDN(new X509Name(dn));
    certGenerator.setSubjectDN(new X509Name(dn));
    certGenerator.setIssuerDN(new X509Name(issuer)); // Set issuer!
    certGenerator.setNotBefore(Calendar.getInstance().getTime());
    certGenerator.setNotAfter(Calendar.getInstance().getTime());
    certGenerator.setPublicKey(publicKey);
    certGenerator.setSignatureAlgorithm("SHA1WithRSAEncryption");
    X509Certificate certificate = (X509Certificate) certGenerator.generate(
            privateKey, "BC");
    return certificate;
}

如何自签名?

我一点头绪都没有。

我该怎么进行呢?

谢谢任何提示。


1
你的问题没有解释你为解决问题所做的努力;它目前看起来像是一个请求代码。请分享你尝试过的实现,并解释它如何未能满足你的要求。 - Duncan Jones
@DuncanJones 好的,我已经生成了证书。但是不知道如何使用Bouncy Castle API签名,也没有在他们的网站上找到任何相关信息。感谢您的帮助。 - Suresh Atta
2个回答

4

您已经拥有生成自签名证书所需的全部代码。您只需要确保您的证书链仅包含一个证书即可。

public static void testKeyStore() throws Exception {
  try {
    String storeName = "path/to/store";
    java.security.KeyPairGenerator keyPairGenerator = KeyPairGenerator
        .getInstance("RSA");
    keyPairGenerator.initialize(2048);
    KeyPair keyPair = keyPairGenerator.generateKeyPair();
    PublicKey publicKey = keyPair.getPublic();
    PrivateKey privateKey = keyPair.getPrivate();
    X509Certificate selfCert = createCertificate("CN=Client", "CN=Client",
        publicKey, privateKey);

    // Note: if you just want to store this certificate then write the
    // contents of selfCert.getEncoded() to file

    java.security.cert.Certificate[] outChain = { selfCert };
    KeyStore outStore = KeyStore.getInstance("PKCS12");
    outStore.load(null, PASSWORD.toCharArray());
    outStore.setKeyEntry("mykey", privateKey, PASSWORD.toCharArray(),
        outChain);
    OutputStream outputStream = new FileOutputStream(storeName);
    outStore.store(outputStream, PASSWORD.toCharArray());
    outputStream.flush();
    outputStream.close();

    KeyStore inStore = KeyStore.getInstance("PKCS12");
    inStore.load(new FileInputStream(storeName), PASSWORD.toCharArray());
  } catch (Exception e) {
    e.printStackTrace();
    throw new AssertionError(e.getMessage());
  }
}

我建议你不要抛出AssertionError。它只应该由Java自身用来指示assert语句是假的。

1
谢谢Jones,我有一个小问题。outStore.load(null, PASSWORD.toCharArray());outStore.setKeyEntry("mykey", privateKey, PASSWORD.toCharArray(), outChain); 为什么我们使用了两个密码?这对密钥库文件是必需的吗?这两个密码可以不同吗? - Suresh Atta
@sᴜʀᴇsʜᴀᴛᴛᴀ 首先,这些代码行在我的示例代码中是因为它们在您的原始代码中。请查看KeyStore文档以了解它们的含义。 - Duncan Jones
是的,我也在做同样的事情。:) 谢谢你的帮助。如果你有时间,请看一下这个链接:http://stackoverflow.com/questions/19421571/providing-key-usage-to-x509certificate-generated-with-with-java-bouncycastle - Suresh Atta

3

在把我们的BC版本从143迁移到154时,经过了周末,我总结了一些经验教训,希望能为未来的某个人节省时间。

1)BC中的PKI扩展API已经移动到了自己的jar包中。我曾经费尽心思在bcprov-jdk15on-154.jar中找PemParser。实现PemParser的是bcpkix-jdk15on-154.jar。毋庸置疑,pkix jar依赖于核心bc jar。

2)PEMReader类在最新的154版本中不可用。这已被PemParser替代。

3)从磁盘上的文件读取公共证书:

Security.addProvider(new BouncyCastleProvider());
File file = new File("c:/mycert.crt");
X509Certificate cert = null;
PEMParser pemParser = new PEMParser(new FileReader(file));
Object object = pemParser.readObject();
if (object instanceof X509CertificateHolder) {
    X509CertificateHolder holder = (X509CertificateHolder)object;
    cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(holder);
}
if (cert == null) {
    throw new Exception("mycert.crt" + " doesn't contain X509Certificate!");
}
return cert;
//If you need publicKey use cert.getPublicKey() method.

4) 从磁盘读取受密码保护的私钥:

Security.addProvider(new BouncyCastleProvider());
KeyPair keyPair = null;
File file = new File("c:/myprivate.key");
PEMParser pemParser = new PEMParser(new FileReader(file));
Object object = pemParser.readObject();
if (object instanceof PEMEncryptedKeyPair) {
    JcaPEMKeyConverter converter = new JcaPEMKeyConverter().setProvider("BC");
    PEMEncryptedKeyPair ckp = (PEMEncryptedKeyPair) object;
    PEMDecryptorProvider decProv =
            new JcePEMDecryptorProviderBuilder().build("strongpasswordfor_myprivate.key".toCharArray());
    keyPair = converter.getKeyPair(ckp.decryptKeyPair(decProv));
}
return keyPair;
//Once we have the keypair, we can get keyPair.getPrivate() [PrivateKey.class] 
//or keyPair.getPublic() [PublicKey.class]  

5) 读取基于字符串的证书,通常情况下,我们希望进行SSL双向认证,并让Web服务器将客户端证书转发到应用服务器的HTTP请求头中:

Security.addProvider(new BouncyCastleProvider());
X509Certificate cert = null;
    String myClientCert = "-----BEGIN CERTIFICATE----- CERTCONTENTS -----END CERTIFICATE-----"
    String cert1 = myClientCert.replaceAll("-----BEGIN CERTIFICATE-----", "").replaceAll("-----END CERTIFICATE-----", "").replaceAll(" ", System.lineSeparator());
    int ind = cert1.lastIndexOf(System.lineSeparator());
    cert1 = new StringBuilder(cert1).replace(ind, ind + System.lineSeparator().length(), "").toString();
    cert1 = BEGIN_CERT  + cert1 + END_CERT;
    PEMParser pemParser = new PEMParser(new StringReader(cert1));
    Object object = pemParser.readObject();
    if (object instanceof X509CertificateHolder) {
        X509CertificateHolder holder = (X509CertificateHolder)object;
        cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(holder);
    }
    return cert;

6) 不用说,按照您的喜好添加错误处理、异常管理和清理。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接