+ * 支持: + * - JWT RSA密钥对生成 + * - CA ECDSA证书生成 + * - 节点ECDSA证书生成(由CA签发) + *
+ * + * @author ruoyi + */ +public class KeyCertGeneratorUtil { + + private static final String RSA_ALGORITHM = "RSA"; + private static final String EC_ALGORITHM = "EC"; + private static final String SIGNATURE_ALGORITHM_RSA = "SHA256withRSA"; + private static final String SIGNATURE_ALGORITHM_EC = "SHA256withECDSA"; + + // ECDSA P-256 曲线名称 + private static final String EC_CURVE_NAME = "secp256r1"; + + /** + * 生成完整的密钥证书集合 + * + * @param jwtKeySize JWT RSA密钥长度(如2048) + * @param caCommonName CA证书通用名称 + * @param nodeCommonName 节点证书通用名称 + * @param certYears 证书有效期(年) + * @return 密钥证书结果 + */ + public static KeyCertResult generateAll(int jwtKeySize, String caCommonName, String nodeCommonName, int certYears) throws Exception { + KeyCertResult result = new KeyCertResult(); + + // 1. 生成JWT RSA密钥对 + KeyPair jwtKeyPair = generateRsaKeyPair(jwtKeySize); + result.setJwtPublicKey(exportPublicKeyToPem(jwtKeyPair.getPublic())); + result.setJwtPrivateKey(exportPrivateKeyToPem(jwtKeyPair.getPrivate())); + + // 2. 生成CA ECDSA密钥对和自签名证书 + KeyPair caKeyPair = generateEcKeyPair(); + X509Certificate caCert = generateCaCertificate(caKeyPair, caCommonName, certYears); + result.setCaCertPem(exportCertificateToPem(caCert)); + result.setCaPrivateKey(exportPrivateKeyToPem(caKeyPair.getPrivate())); + + // 3. 生成节点ECDSA密钥对,并由CA签发证书 + KeyPair nodeKeyPair = generateEcKeyPair(); + X509Certificate nodeCert = generateNodeCertificate(nodeKeyPair, caKeyPair.getPrivate(), caCert, nodeCommonName, certYears); + result.setNodeCertPem(exportCertificateToPem(nodeCert)); + result.setNodePrivateKey(exportPrivateKeyToPem(nodeKeyPair.getPrivate())); + + return result; + } + + /** + * 生成完整的密钥证书集合(使用默认参数) + * + * @param commonNamePrefix 通用名称前缀 + * @return 密钥证书结果 + */ + public static KeyCertResult generateAll(String commonNamePrefix) throws Exception { + String uniqueId = generateUniqueId(); + String caCommonName = commonNamePrefix + "_" + uniqueId; + String nodeCommonName = commonNamePrefix + "_" + uniqueId; + return generateAll(2048, caCommonName, nodeCommonName, 3); + } + + /** + * 生成完整的密钥证书集合(使用默认参数和随机前缀) + * + * @return 密钥证书结果 + */ + public static KeyCertResult generateAll() throws Exception { + return generateAll("node"); + } + + // ==================== RSA密钥对生成 ==================== + + /** + * 生成RSA密钥对 + * + * @param keySize 密钥长度(如2048) + * @return RSA密钥对 + */ + public static KeyPair generateRsaKeyPair(int keySize) throws NoSuchAlgorithmException { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance(RSA_ALGORITHM); + keyGen.initialize(keySize, new SecureRandom()); + return keyGen.generateKeyPair(); + } + + /** + * 生成RSA 2048密钥对 + * + * @return RSA密钥对 + */ + public static KeyPair generateRsaKeyPair() throws NoSuchAlgorithmException { + return generateRsaKeyPair(2048); + } + + // ==================== ECDSA密钥对生成 ==================== + + /** + * 生成ECDSA P-256密钥对 + * + * @return ECDSA密钥对 + */ + public static KeyPair generateEcKeyPair() throws NoSuchAlgorithmException, InvalidAlgorithmParameterException { + KeyPairGenerator keyGen = KeyPairGenerator.getInstance(EC_ALGORITHM); + keyGen.initialize(new java.security.spec.ECGenParameterSpec(EC_CURVE_NAME), new SecureRandom()); + return keyGen.generateKeyPair(); + } + + // ==================== 证书生成 ==================== + + /** + * 生成CA自签名证书 + * + * @param caKeyPair CA密钥对 + * @param commonName 通用名称 + * @param years 有效期(年) + * @return CA证书 + */ + public static X509Certificate generateCaCertificate(KeyPair caKeyPair, String commonName, int years) throws Exception { + Instant now = Instant.now(); + // Instant不支持YEARS,使用天数计算(1年=365天) + Instant notAfter = now.plus(365L * years, ChronoUnit.DAYS); + + X500Name issuer = new X500Name("CN=" + commonName); + BigInteger serial = BigInteger.valueOf(System.currentTimeMillis()); + + X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder( + issuer, + serial, + Date.from(now), + Date.from(notAfter), + issuer, + caKeyPair.getPublic() + ); + + // 添加基本约束(CA证书) + certBuilder.addExtension( + Extension.basicConstraints, + true, + new BasicConstraints(true) + ); + + // 添加密钥用法 + certBuilder.addExtension( + Extension.keyUsage, + true, + new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign) + ); + + JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM_EC); + ContentSigner signer = signerBuilder.build(caKeyPair.getPrivate()); + X509CertificateHolder certHolder = certBuilder.build(signer); + + return new JcaX509CertificateConverter().getCertificate(certHolder); + } + + /** + * 生成节点证书(由CA签发) + * + * @param nodeKeyPair 节点密钥对 + * @param caPrivateKey CA私钥 + * @param caCertificate CA证书 + * @param commonName 节点通用名称 + * @param years 有效期(年) + * @return 节点证书 + */ + public static X509Certificate generateNodeCertificate(KeyPair nodeKeyPair, PrivateKey caPrivateKey, + X509Certificate caCertificate, String commonName, int years) throws Exception { + Instant now = Instant.now(); + // Instant不支持YEARS,使用天数计算(1年=365天) + Instant notAfter = now.plus(365L * years, ChronoUnit.DAYS); + + X500Name issuer = X500Name.getInstance(caCertificate.getSubjectX500Principal().getEncoded()); + X500Name subject = new X500Name("CN=" + commonName); + BigInteger serial = BigInteger.valueOf(System.currentTimeMillis() + (long)(Math.random() * 10000)); + + X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder( + issuer, + serial, + Date.from(now), + Date.from(notAfter), + subject, + nodeKeyPair.getPublic() + ); + + // 添加基本约束(非CA证书) + certBuilder.addExtension( + Extension.basicConstraints, + true, + new BasicConstraints(false) + ); + + // 添加密钥用法 + certBuilder.addExtension( + Extension.keyUsage, + true, + new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment) + ); + + JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(SIGNATURE_ALGORITHM_EC); + ContentSigner signer = signerBuilder.build(caPrivateKey); + X509CertificateHolder certHolder = certBuilder.build(signer); + + return new JcaX509CertificateConverter().getCertificate(certHolder); + } + + // ==================== PEM导出 ==================== + + /** + * 导出公钥为PEM格式 + * + * @param publicKey 公钥 + * @return PEM格式字符串 + */ + public static String exportPublicKeyToPem(PublicKey publicKey) { + byte[] encoded = publicKey.getEncoded(); + String base64 = java.util.Base64.getEncoder().encodeToString(encoded); + return "-----BEGIN PUBLIC KEY-----\n" + base64 + "\n-----END PUBLIC KEY-----"; + } + + /** + * 导出私钥为PEM格式(PKCS#8) + * + * @param privateKey 私钥 + * @return PEM格式字符串 + */ + public static String exportPrivateKeyToPem(PrivateKey privateKey) { + byte[] encoded = privateKey.getEncoded(); + String base64 = java.util.Base64.getEncoder().encodeToString(encoded); + return "-----BEGIN PRIVATE KEY-----\n" + base64 + "\n-----END PRIVATE KEY-----"; + } + + /** + * 导出证书为PEM格式 + * + * @param certificate 证书 + * @return PEM格式字符串 + */ + public static String exportCertificateToPem(X509Certificate certificate) throws Exception { + byte[] encoded = certificate.getEncoded(); + String base64 = java.util.Base64.getEncoder().encodeToString(encoded); + return "-----BEGIN CERTIFICATE-----\n" + base64 + "\n-----END CERTIFICATE-----"; + } + + // ==================== 辅助方法 ==================== + + /** + * 生成唯一ID + * + * @return 唯一ID字符串 + */ + private static String generateUniqueId() { + String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + StringBuilder sb = new StringBuilder(); + SecureRandom random = new SecureRandom(); + for (int i = 0; i < 16; i++) { + sb.append(chars.charAt(random.nextInt(chars.length()))); + } + return sb.toString(); + } + + /** + * 密钥证书结果 + */ + @Data + public static class KeyCertResult { + /** + * JWT公钥(PEM格式) + */ + private String jwtPublicKey; + + /** + * JWT私钥(PEM格式) + */ + private String jwtPrivateKey; + + /** + * CA证书(PEM格式) + */ + private String caCertPem; + + /** + * CA私钥(PEM格式) + */ + private String caPrivateKey; + + /** + * 节点证书(PEM格式) + */ + private String nodeCertPem; + + /** + * 节点私钥(PEM格式) + */ + private String nodePrivateKey; + } +} diff --git a/ruoyi-modules/ruoyi-system/src/main/test/KeyCertGeneratorUtilTest.java b/ruoyi-modules/ruoyi-system/src/main/test/KeyCertGeneratorUtilTest.java new file mode 100644 index 000000000..d6f2df45b --- /dev/null +++ b/ruoyi-modules/ruoyi-system/src/main/test/KeyCertGeneratorUtilTest.java @@ -0,0 +1,23 @@ +import cn.hutool.json.JSONUtil; +import org.dromara.net.util.KeyCertGeneratorUtil; + +public class KeyCertGeneratorUtilTest { + public static void main(String[] args) throws Exception { + + // 使用默认参数生成 + KeyCertGeneratorUtil.KeyCertResult result = KeyCertGeneratorUtil.generateAll(); + + System.out.println("=== 生成的密钥证书 ==="); + System.out.println(JSONUtil.toJsonPrettyStr(result)); + + // 验证所有字段都不为空 + assert result.getJwtPublicKey() != null && !result.getJwtPublicKey().isEmpty(); + assert result.getJwtPrivateKey() != null && !result.getJwtPrivateKey().isEmpty(); + assert result.getCaCertPem() != null && !result.getCaCertPem().isEmpty(); + assert result.getCaPrivateKey() != null && !result.getCaPrivateKey().isEmpty(); + assert result.getNodeCertPem() != null && !result.getNodeCertPem().isEmpty(); + assert result.getNodePrivateKey() != null && !result.getNodePrivateKey().isEmpty(); + + System.out.println("\n=== 验证通过 ==="); + } +}