简介

日常过程中,有时候需要用到自签名的证书,故特意把生成则证书写成一个工具类,方便随时需要的时候生成。

添加依赖

        <dependency>
            <groupId>org.bouncycastle</groupId>
            <artifactId>bcpkix-jdk18on</artifactId>
            <version>1.78.1</version>
        </dependency>

执行工具类

创建CAUtil

package com.topinfo.basic.ca;

import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.util.io.pem.PemObject;

import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.Date;

/**
 * @ClassName: CAUtil
 * @Description: 证书 工具类
 * @Author: 杨攀
 * @Date: 2024/4/28 15:52
 * @Copyright: 2024 www.tuxun.net Inc. All rights reserved.
 */
public class CAUtil {

    public static void main(String[] args) throws Exception {

        // 生成 密钥对,为CA生成一个RSA密钥对
        KeyPair keyPair = generatorKeyPair();

        String dirName = "CN=tuxun.com, O=topinfo, L=zhejiang, ST=hangzhou, C=CN";

        // 创建自签名的CA证书, 构建X509证书
        X509Certificate certificate = generatorCA(keyPair, dirName);

        // 私钥
        PrivateKey caPrivateKey = keyPair.getPrivate();
        // 别名
        String alias = "topinfo-ca";
        // 密码
        char[] password = "1234qwer".toCharArray();
        // jks 存储路径
        String jksPath = "D:/upload/topinfo_certificate.jks";

        // CA跟证书 保存为JKS格式
        saveAsJKS(certificate, caPrivateKey, alias, password, jksPath);

        // p12 存储路径
        String p12Path = "D:/upload/topinfo_certificate.p12";
        // CA跟证书 保存为P12格式
        saveAsPKCS12(certificate, caPrivateKey, alias, password, p12Path);

        // 证书转换为PEM格式并写入文件
        String pemPath = "D:/upload/topinfo_certificate.pem";

        // PrivateKey(私钥)转换为PEM格式
        String pemKeyPath = "D:/upload/topinfo_certificate_key.pem";

        // 保持PEM格式(Nginx支持该格式)
        saveAsPEM(certificate, caPrivateKey, pemPath, pemKeyPath);
    }

    /**
     * 生成密钥对, 在生成CA证书之前,我们需要首先生成一个密钥对,其中包含一个私钥和一个公钥。私钥用于签署证书,而公钥将被包含在证书中,供其他方验证。
     *
     * @param
     * @return java.security.KeyPair
     * @author 杨攀
     * @date 2024/4/28 15:58
     */
    private static KeyPair generatorKeyPair() throws NoSuchAlgorithmException {

        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
        // 例如,使用2048位长度的密钥
        keyPairGen.initialize(2048, new SecureRandom());
        KeyPair caKeyPair = keyPairGen.generateKeyPair();

        // 私钥
        PrivateKey caPrivateKey = caKeyPair.getPrivate();
        String privateKey = Base64.getEncoder().encodeToString(caPrivateKey.getEncoded());
        System.out.println("私钥:" + privateKey);

        // 公钥
        PublicKey caPublicKey = caKeyPair.getPublic();
        String publicKey = Base64.getEncoder().encodeToString(caPublicKey.getEncoded());
        System.out.println("公钥:" + publicKey);

        return caKeyPair;
    }


    /**
     * 生成CA证书, 生成密钥对后,我们可以使用该密钥对生成CA证书
     *
     * @param keyPair 秘钥对
     * @param dirName 证书属性
     * @return void
     * @author 杨攀
     * @date 2024/4/28 15:58
     */
    private static X509Certificate generatorCA(KeyPair keyPair, String dirName) throws IOException, OperatorCreationException, CertificateException {


        // 设置CA证书属性
        X500Name dnName = new X500Name(dirName);

        long now = System.currentTimeMillis();
        Date startDate = new Date(now);
        // 有效期二十年
        Date endDate = new Date(now + 20 * 365 * 24 * 60 * 60 * 1000);

        // 公钥
        PublicKey caPublicKey = keyPair.getPublic();

        // 私钥
        PrivateKey caPrivateKey = keyPair.getPrivate();

        // 构建证书
        X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
                // 发行人与主体相同,因为是自签名
                dnName,
                // 序列号
                BigInteger.probablePrime(64, new SecureRandom()),
                startDate,
                endDate,
                dnName,
                caPublicKey);

        ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSAEncryption").build(caPrivateKey);

        // 使用CA私钥签署证书
        X509CertificateHolder certHolder = certBuilder.build(signer);
        X509Certificate certificate = new JcaX509CertificateConverter().getCertificate(certHolder);

        return certificate;
    }


    /**
     * 保存为JKS格式
     *
     * @param certificate 根证书
     * @param privateKey  PrivateKey实例
     * @param alias       别名
     * @param password    KeyStore的密码, 如:"1234qwer".toCharArray();
     * @param filePath    JKS文件保存路径, 如: D:/upload/server.jks
     * @return void
     * @author 杨攀
     * @date 2024/4/30 9:45
     */
    public static void saveAsJKS(X509Certificate certificate, PrivateKey privateKey, String alias, char[] password, String filePath) throws Exception {

        // 创建一个新的KeyStore
        KeyStore keyStore = KeyStore.getInstance("JKS");
        keyStore.load(null, password); // 初始化KeyStore,没有密钥库则传null

        // 将私钥和证书添加到KeyStore
        keyStore.setKeyEntry(alias, privateKey, password, new Certificate[]{certificate});

        // 保存KeyStore到文件
        try (FileOutputStream fos = new FileOutputStream(filePath)) {
            keyStore.store(fos, password);
        }

        System.out.println("JKS:" + filePath);
    }


    /**
     * 保存为PKCS12格式
     *
     * @param certificate 根证书
     * @param privateKey  PrivateKey实例
     * @param alias       别名
     * @param password    KeyStore的密码, 如:"1234qwer".toCharArray();
     * @param filePath    JKS文件保存路径, 如: D:/upload/server.p12
     * @return void
     * @author 杨攀
     * @date 2024/4/30 9:53
     */
    public static void saveAsPKCS12(X509Certificate certificate, PrivateKey privateKey, String alias, char[] password, String filePath) throws Exception {

        KeyStore.ProtectionParameter protection = new KeyStore.PasswordProtection(password);

        // 使用Builder创建PKCS12类型的KeyStore
        KeyStore.ProtectionParameter protParam = new KeyStore.PasswordProtection(password);
        KeyStore.Builder builder = KeyStore.Builder.newInstance("PKCS12", null, protParam);
        builder.getKeyStore().setEntry(alias, new KeyStore.PrivateKeyEntry(privateKey, new Certificate[]{certificate}), protParam);

        // 保存KeyStore到文件
        try (FileOutputStream fos = new FileOutputStream(filePath)) {
            builder.getKeyStore().store(fos, password);
        }
        System.out.println("PKCS12:" + filePath);
    }


    /**
     * 保存为PEM格式
     *
     * @param certificate 根证书
     * @param caPrivateKey PrivateKey实例
     * @param pemPath  PEM格式文件保存路径,如:D:/upload/topinfo_certificate.pem
     * @param pemKeyPath  PEM格式Key文件保存路径,如: D:/upload/topinfo_certificate_key.pem
     * @return void
     * @author 杨攀
     * @date 2024/4/30 12:22
     */
    private static void saveAsPEM(X509Certificate certificate, PrivateKey caPrivateKey, String pemPath, String pemKeyPath) {

        // 对于X509Certificate(证书)转换为PEM格式并写入文件:
        try (JcaPEMWriter pemWriter = new JcaPEMWriter(new FileWriter(pemPath))) {
            pemWriter.writeObject(new PemObject("CERTIFICATE", certificate.getEncoded()));
        } catch (IOException | CertificateEncodingException e) {
            System.err.println("保存为PEM格式错误: ");
            e.printStackTrace();
        }

        // 对于PrivateKey(私钥)转换为PEM格式并写入文件:
        try (JcaPEMWriter pemWriter = new JcaPEMWriter(new FileWriter(pemKeyPath))) {
            pemWriter.writeObject(new PemObject("PRIVATE KEY", caPrivateKey.getEncoded()));
        } catch (IOException e) {
            System.err.println("EM格式Key文件错误: ");
            e.printStackTrace();
        }

    }


}

具体说明代码注释都有说明,一个mian方法执行即可。


妞见妞爱
44 声望15 粉丝

时光,不会辜负每一个平静努力的人...