寻求Java原生替代OpenSSL实现证书创建与签名的方案
好的,完全懂你想摆脱调用OpenSSL进程、纯用Java实现证书创建与签名的需求!下面我会给出对应你OpenSSL命令的Java实现方案,主要用到Java标准安全库+BouncyCastle(它能大幅简化X509v3扩展比如SAN的处理,比纯标准库省心太多)。
先准备依赖
首先需要引入BouncyCastle的依赖,如果你用Maven,在pom.xml里加:
<dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcpkix-jdk15on</artifactId> <version>1.70</version> </dependency> <dependency> <groupId>org.bouncycastle</groupId> <artifactId>bcprov-jdk15on</artifactId> <version>1.70</version> </dependency>
Gradle的话就在build.gradle里加:
implementation 'org.bouncycastle:bcpkix-jdk15on:1.70' implementation 'org.bouncycastle:bcprov-jdk15on:1.70'
步骤1:生成RSA密钥对(对应openssl genrsa 2048)
这一步用Java标准库就能搞定,生成服务器的私钥和公钥:
import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.Security; import org.bouncycastle.jce.provider.BouncyCastleProvider; // 先注册BouncyCastle Provider static { Security.addProvider(new BouncyCastleProvider()); } public static KeyPair generateRSAKeyPair() throws Exception { KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", "BC"); keyGen.initialize(2048); // 对应2048位密钥 return keyGen.generateKeyPair(); }
步骤2:创建并签名X509证书(对应openssl req + openssl ca)
这一步是核心,要处理Subject DN、Subject Alternative Name(SAN,包括DNS和Email),并用你的CA证书+私钥签名:
import java.math.BigInteger; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.X509Certificate; import java.util.Date; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralNames; import org.bouncycastle.asn1.x509.SubjectAlternativeName; public static X509Certificate signCertificate( PrivateKey caPrivateKey, X509Certificate caCert, PublicKey serverPublicKey, KeyPair serverKeyPair, String cname, String altname, String email, String country, String state, String org, String orgUnit, int daysValid) throws Exception { // 1. 构建证书主体DN X500Name subject = new X500Name( "C=" + country + ", ST=" + state + ", O=" + org + ", OU=" + orgUnit + ", CN=" + cname); // 2. 设置证书有效期 Date startDate = new Date(); Date endDate = new Date(startDate.getTime() + (long) daysValid * 24 * 60 * 60 * 1000); // 3. 生成证书序列号(唯一即可,这里用当前时间戳) BigInteger serial = BigInteger.valueOf(System.currentTimeMillis()); // 4. 构建X509v3证书,添加SAN扩展 JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder( new X500Name(caCert.getSubjectDN().getName()), serial, startDate, endDate, subject, serverPublicKey); // 添加SAN扩展:DNS + Email GeneralNames generalNames = new GeneralNames( new GeneralName(GeneralName.dNSName, altname), new GeneralName(GeneralName.rfc822Name, email)); certBuilder.addExtension(Extension.subjectAlternativeName, false, generalNames); // 5. 用CA私钥签名证书(用SHA256withRSA算法,和OpenSSL默认一致) ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA") .setProvider("BC") .build(caPrivateKey); // 6. 生成最终证书 return new JcaX509CertificateConverter() .setProvider("BC") .getCertificate(certBuilder.build(signer)); }
另外,你需要先加载CA的私钥和证书,这里给个加载示例:
import java.io.FileInputStream; import java.security.KeyStore; import java.security.PrivateKey; import java.security.cert.X509Certificate; // 从PKCS12格式的CA密钥库加载CA私钥和证书 public static Object[] loadCA(String caPfxPath, String caPfxPassword) throws Exception { KeyStore caKeyStore = KeyStore.getInstance("PKCS12"); try (FileInputStream fis = new FileInputStream(caPfxPath)) { caKeyStore.load(fis, caPfxPassword.toCharArray()); } // 假设CA证书的别名是"ca",如果不确定可以遍历密钥库找别名 String alias = "ca"; PrivateKey caPrivateKey = (PrivateKey) caKeyStore.getKey(alias, caPfxPassword.toCharArray()); X509Certificate caCert = (X509Certificate) caKeyStore.getCertificate(alias); return new Object[]{caPrivateKey, caCert}; }
步骤3:生成PKCS12文件(对应openssl pkcs12 -export)
把服务器私钥、服务器证书、CA证书打包成PKCS12格式:
import java.io.FileOutputStream; import java.security.KeyStore; import java.security.cert.Certificate; public static void generatePKCS12( KeyPair serverKeyPair, X509Certificate serverCert, X509Certificate caCert, String pfxPath, String pfxPassword) throws Exception { KeyStore pkcs12 = KeyStore.getInstance("PKCS12"); pkcs12.load(null, null); // 导入服务器私钥和证书链(服务器证书 + CA证书) Certificate[] certChain = new Certificate[]{serverCert, caCert}; pkcs12.setKeyEntry("server", serverKeyPair.getPrivate(), pfxPassword.toCharArray(), certChain); // 写入文件 try (FileOutputStream fos = new FileOutputStream(pfxPath)) { pkcs12.store(fos, pfxPassword.toCharArray()); } }
整体调用示例
把上面的方法串起来使用:
public static void main(String[] args) throws Exception { // 1. 加载CA私钥和证书 Object[] caInfo = loadCA("path/to/ca.pfx", "ca-password"); PrivateKey caPrivateKey = (PrivateKey) caInfo[0]; X509Certificate caCert = (X509Certificate) caInfo[1]; // 2. 生成服务器密钥对 KeyPair serverKeyPair = generateRSAKeyPair(); // 3. 签名服务器证书 X509Certificate serverCert = signCertificate( caPrivateKey, caCert, serverKeyPair.getPublic(), serverKeyPair, "your-cname", "your-altname.com", "admin@your-altname.com", "CN", "Beijing", "YourCompany", "ITDept", 365); // 有效期365天 // 4. 生成PKCS12文件 generatePKCS12(serverKeyPair, serverCert, caCert, "path/to/server.pfx", "pfx-password"); }
注意事项
- 确保BouncyCastle Provider被正确注册,不然会报算法找不到的错误。
- 如果你需要保存服务器的私钥到单独的PEM文件,可以用Apache Commons Codec或者BouncyCastle的PEMWriter来处理,不过一般PKCS12已经包含私钥和证书,足够用了。
- 证书序列号要保证唯一,示例里用了时间戳,你也可以用自增的数字或者随机数。
内容的提问来源于stack exchange,提问作者Mister Lamp
相关产品推荐
相关产品推荐

