Java中使用Bouncy Castle如何覆盖X500Name的CN字段?
我之前刚好解决过这个问题——Bouncy Castle的X500Name是不可变类,没法直接调用update方法修改字段,不过我们可以通过克隆原CSR的主体RDN列表,替换掉CN字段来实现你的需求。下面是具体的实现思路和代码:
核心思路
- 从CSR中获取原始的
X500Name主体信息 - 遍历原始的RDN(相对辨别名)列表,保留除CN外的所有字段,替换或添加自定义CN
- 用处理后的RDN列表生成新的
X500Name - 使用这个新的主体信息来签署证书
完整代码示例
import org.bouncycastle.asn1.x500.RDN; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.style.BCStyle; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import java.io.StringReader; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.Security; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Date; import java.util.List; public class CsrSignerWithCustomCN { public static void main(String[] args) throws Exception { // 注册Bouncy Castle安全提供者 Security.addProvider(new BouncyCastleProvider()); // 示例:解析PEM格式的CSR(实际场景中可从文件/流读取) String csrPem = "-----BEGIN CERTIFICATE REQUEST-----\n" + "MIICtzCCAX8CAQAwgY0xCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDTjELMAkGA1UE\n" + "BxMCQ04xDzANBgNVBAoTBlRlc3RDbzEPMA0GA1UECxMGVGVzdENvMRgwFgYDVQQD\n" + "Ex90ZXN0LmNvbS5leGFtcGxlLmNvbTEeMBwGCSqGSIb3DQEJARYPYWRtaW5AZXhh\n" + "bXBsZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDf9Kd8z6+\n" + "你的CSR内容...\n" + "-----END CERTIFICATE REQUEST-----"; PEMParser pemParser = new PEMParser(new StringReader(csrPem)); org.bouncycastle.pkcs.PKCS10CertificationRequest csr = (org.bouncycastle.pkcs.PKCS10CertificationRequest) pemParser.readObject(); pemParser.close(); // 1. 获取CSR原始主体 X500Name originalSubject = csr.getSubject(); // 2. 处理RDN列表,替换CN字段 RDN[] originalRdns = originalSubject.getRDNs(); List<RDN> newRdnList = new ArrayList<>(); boolean cnReplaced = false; for (RDN rdn : originalRdns) { if (rdn.getFirst().getType().equals(BCStyle.CN)) { // 替换原CN为自定义值 newRdnList.add(new RDN(BCStyle.CN, "mycustomcn")); cnReplaced = true; } else { // 保留其他所有字段 newRdnList.add(rdn); } } // 处理原主体没有CN的情况,直接添加自定义CN if (!cnReplaced) { newRdnList.add(new RDN(BCStyle.CN, "mycustomcn")); } // 生成新的主体X500Name X500Name newSubject = new X500Name(newRdnList.toArray(new RDN[0])); // 3. 准备CA密钥对(实际场景请使用真实CA的证书和私钥) KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA", "BC"); keyGen.initialize(2048); KeyPair caKeyPair = keyGen.generateKeyPair(); X500Name caSubject = new X500Name("CN=MyRootCA, O=MyOrganization"); // 4. 构建并签署证书 Date startDate = new Date(); Date endDate = new Date(startDate.getTime() + 365L * 24 * 60 * 60 * 1000); // 1年有效期 X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder( caSubject, java.math.BigInteger.valueOf(System.currentTimeMillis()), startDate, endDate, newSubject, // 使用修改后的主体 csr.getSubjectPublicKeyInfo() ); // 使用CA私钥签名 ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA") .setProvider("BC") .build(caKeyPair.getPrivate()); X509Certificate signedCert = new JcaX509CertificateConverter() .setProvider("BC") .getCertificate(certBuilder.build(signer)); // 验证结果:输出证书主体,确认CN已替换 System.out.println("签署后证书主体: " + signedCert.getSubjectX500Principal().getName()); } }
关键细节说明
- 不可变的X500Name:Bouncy Castle的
X500Name设计为不可变,所以必须通过重新构建RDN列表来修改字段,不能直接编辑 - 兼容无CN的场景:代码中加入了判断,如果原CSR主体没有CN字段,会自动添加自定义CN,避免遗漏
- 保留原有字段:遍历过程中保留了除CN外的所有原始RDN,确保其他主体信息(如O、OU、C等)完全继承自CSR
内容的提问来源于stack exchange,提问作者Robert Daniels




