Java 代码生成 X.509 自签名证书

1. Java 代码生成 X.509 自签名证书

1.1. 版本说明

构件 版本
bcpkix-jdk18on 1.78.1
hutool-all 5.8.27

1.2. 概述

这里模拟以下业务流程:

flowchart TB generateCaKeyPair["生成根证书密钥对(私钥 + 公钥)"] --> generateCACertificate["生成根证书"] --> saveKeyAndCertificate["保存根证书及其密钥对到磁盘"] --> loadCaKeyAndCertificate["从磁盘加载根证书及其私钥"] --> generateSelfKeyPair["生成自签名证书密钥对(私钥 + 公钥)"] --> generateSelfSignedCCertificate["使用根证书签名,生成自签名证书"]

1.3. 什么是 X.509 证书

X.509 证书是代表用户、计算机、服务或设备的数字文档。证书颁发机构 (CA)、从属 CA 或注册机构负责颁发 X.509 证书。证书包含证书使用者的公钥。它们不包含使用者的私钥(必须安全存储)。RFC 5280 记录公钥证书,包括它们的字段和扩展名。公钥证书经过数字签名,通常包含以下信息:

  • 证书使用者的信息
  • 与使用者私钥相对应的公钥
  • 证书颁发机构的信息
  • 支持的加密和/或数字签名算法
  • 用于确定证书吊销和有效性状态的信息

1.4. 证书版本

证书字段
X.509 证书标准有三个增量版本,每个后续版本都向标准添加了证书字段:

  • 1988 年发布的版本 1 (v1),遵循证书的初始 X.509 标准。
  • 1993 年发布的版本 2 (v2),向版本 1 中包含的字段添加了两个字段。
  • 2008 年发布的版本 3 (v3),即 X.509 标准的当前版本。此版本添加了对证书扩展名的支持。

1.5. 证书字段

1.5.1. 版本 1 字段

名称 说明
版本 标识证书版本号的整数。
序列号 一个整数,表示证书颁发机构 (CA) 颁发的每个证书的唯一编号。
Signature CA 用来签署证书的加密算法的标识符。该值包括算法的标识符和该算法使用的任何可选参数(如果适用)。
颁发者 发证 CA 的证书的可分辨名称 (DN)。
有效期 证书有效的非独占时间段。
主题 证书使用者的可分辨名称 (DN)。
使用者公钥信息 证书使用者拥有的公钥。

1.5.2. 版本 2 字段

名称 说明
颁发者唯一 ID 表示发证 CA 的唯一标识符,由发证 CA 定义。
使用者唯一 ID 辨识证书使用者的唯一标识符,由发证 CA 定义。

1.5.3. 版本 3 字段

名称 说明
扩展 标准和 Internet 特定证书扩展名的集合。版本 3 中引入的证书扩展名提供了将更多属性与用户或公钥关联的方法,以及管理证书颁发机构之间的关系的方法。有关证书扩展名的详细信息,请参阅 RFC 5280 规范的证书扩展名部分。

1.6. 证书格式

格式 说明
二进制证书 使用可辨别编码规则 (DER) ASN.1 编码的原始格式二进制证书。
ASCII PEM 格式 PEM 证书 (.pem) 文件包含以 -----BEGIN CERTIFICATE----- 开头且以 -----END CERTIFICATE----- 结尾的 Base64 编码证书。
ASCII PEM 密钥 包含 Base64 编码的 DER 密钥,可以选择包含有关用于密码保护的算法的其他元数据。
PKCS #7 证书 一种用于传输已签名或已加密数据的格式。它可以包括整个证书链。RFC 2315 定义此格式。
PKCS #8 密钥 私钥存储的格式。RFC 5208 定义此格式。
PKCS #12 密钥和证书 一种复杂的格式,可以存储和保护密钥和整个证书链。它通常与 .p12 或 .pfx 扩展名一起使用。PKCS #12 等同于 PFX 格式。RFC 7292 定义此格式。

1.7. 密钥对生成算法

参考 Oracle 官方文档

Algorithm Name Description
DiffieHellman Generates keypairs for the Diffie-Hellman KeyAgreement algorithm. Note: key.getAlgorithm() will return "DH" instead of "DiffieHellman".
DSA Generates keypairs for the Digital Signature Algorithm.
RSA Generates keypairs for the RSA algorithm (Signature/Cipher).
RSASSA-PSS Generates keypairs for the RSASSA-PSS signature algorithm.
EC Generates keypairs for the Elliptic Curve algorithm.

1.8. 签名算法

参考 Oracle 官方文档

Algorithm Name Description
NONEwithRSA The RSA signature algorithm, which does not use a digesting algorithm (for example, MD5/SHA1) before performing the RSA operation. For more information about the RSA Signature algorithms, see PKCS #1 v2.2.
MD2withRSA The MD2/MD5 with RSA Encryption signature algorithm, which uses the MD2/MD5 digest algorithm and RSA to create and verify RSA digital signatures as defined in PKCS #1.
MD5withRSA ^
SHA1withRSA The signature algorithm with SHA-* and the RSA encryption algorithm as defined in the OSI Interoperability Workshop, using the padding conventions described in PKCS #1.
SHA224withRSA ^
SHA256withRSA ^
SHA384withRSA ^
SHA512withRSA ^
SHA512/224withRSA ^
SHA512/256withRSA ^
RSASSA-PSS The signature algorithm that uses the RSASSA-PSS signature scheme as defined in PKCS #1 v2.2. Note that this signature algorithm needs parameters such as a digesting algorithm, salt length and MGF1 algorithm, to be supplied before performing the RSA operation.
NONEwithDSA The Digital Signature Algorithm as defined in FIPS PUB 186-4. The data must be exactly 20 bytes in length. This algorithm is also known as rawDSA.
SHA1withDSA The DSA signature algorithms that use these digest algorithms to create and verify digital signatures as defined in FIPS PUB 186-4.
SHA224withDSA ^
SHA256withDSA ^
SHA384withDSA ^
SHA512withDSA ^
NONEwithECDSA (ECDSA) The ECDSA signature algorithms as defined in ANSI X9.62.
Note:"ECDSA" is an ambiguous name for the "SHA1withECDSA" algorithm and should not be used. The formal name "SHA1withECDSA" should be used instead.
SHA1withECDSA ^
SHA224withECDSA ^
SHA256withECDSA ^
SHA384withECDSA ^
SHA512withECDSA ^
<digest>with<encryption> Use this to form a name for a signature algorithm with a particular message digest (such as MD2 or MD5) and algorithm (such as RSA or DSA), just as was done for the explicitly defined standard names in this section (MD2withRSA, and so on).
For the new signature schemes defined in PKCS #1 v2.2, for which the with form is insufficient, withand can be used to form a name. Here, should be replaced by a mask generation function such as MGF1.
Example: MD5withRSAandMGF1.

KeyStore 类型

参考 Oracle 官方文档

Type Description
jceks The proprietary keystore implementation provided by the SunJCE provider.
jks The proprietary keystore implementation provided by the SUN provider.
dks A domain keystore is a collection of keystores presented as a single logical keystore. It is specified by configuration data whose syntax is described in DomainLoadStoreParameter.
pkcs11 A keystore backed by a PKCS #11 token.
pkcs12 The transfer syntax for personal identity information as defined in PKCS #12: Personal Information Exchange Syntax v1.1.

1.9. 定义常量

public class X509CertificateConstants {
    /**
     * 密钥算法
     */
    public static final String KEY_ALGORITHM = "RSA";
    /**
     * 签名算法
     */
    public static final String SIGN_ALGORITHM = "SHA256withRSA";
    /**
     * 私钥类型
     */
    public static final String PRIVATE_KEY_TYPE = "PRIVATE KEY";
    /**
     * 公钥类型
     */
    public static final String PUBLIC_KEY_TYPE = "PUBLIC KEY";
    /**
     * 证书类型
     */
    public static final String CERTIFICATE_TYPE = "CERTIFICATE";
}

1.10. 定义证书信息实体

@Data
public class CertificateInfo {
    /**
     * 证书序列号
     */
    private BigInteger serial;
    /**
     * 颁发者
     */
    private X500Name issuer;
    /**
     * 主体
     */
    private X500Name subject;
    /**
     * 颁发时间
     */
    private Date notBefore;
    /**
     * 到期时间
     */
    private Date notAfter;
    /**
     * 加密算法
     */
    private String keyAlgorithm;
    /**
     * 签名算法
     */
    private String signAlgorithm;

1.11. 定义证书 Subject 构造者

public class SubjectBuilder {
    /**
     * 常用名称(Common Name)
     */
    private String cn;
    /**
     * 企业名称(Organization)
     */
    private String o;
    /**
     * 部门(Organizational Unit)
     */
    private String ou;
    /**
     * 国家(Country)
     */
    private String c;
    /**
     * 省份(State)
     */
    private String st;
    /**
     * 城市(Locality)
     */
    private String l;

    public SubjectBuilder setCn(String cn) {
        this.cn = cn;
        return this;
    }

    public SubjectBuilder setO(String o) {
        this.o = o;
        return this;
    }

    public SubjectBuilder setOu(String ou) {
        this.ou = ou;
        return this;
    }

    public SubjectBuilder setC(String c) {
        this.c = c;
        return this;
    }

    public SubjectBuilder setSt(String st) {
        this.st = st;
        return this;
    }

    public SubjectBuilder setL(String l) {
        this.l = l;
        return this;
    }

    public X500Name build() {
        X500NameBuilder x500NameBuilder = new X500NameBuilder();
        if (StrUtil.isNotBlank(cn)) {
            x500NameBuilder.addRDN(BCStyle.CN, cn);
        }
        if (StrUtil.isNotBlank(o)) {
            x500NameBuilder.addRDN(BCStyle.O, o);
        }
        if (StrUtil.isNotBlank(ou)) {
            x500NameBuilder.addRDN(BCStyle.OU, ou);
        }
        if (StrUtil.isNotBlank(c)) {
            x500NameBuilder.addRDN(BCStyle.C, c);
        }
        if (StrUtil.isNotBlank(st)) {
            x500NameBuilder.addRDN(BCStyle.ST, st);
        }
        if (StrUtil.isNotBlank(l)) {
            x500NameBuilder.addRDN(BCStyle.L, l);
        }
        return x500NameBuilder.build();
    }
}

1.12. 定义证书及密钥对生成结果实体

@Data
public class KeyAndCertificate {
    /**
     * 私钥
     */
    private PrivateKey privateKey;
    /**
     * 公钥
     */
    private PublicKey publicKey;
    /**
     * 证书
     */
    private X509Certificate certificate;

    public KeyAndCertificate(PrivateKey privateKey, PublicKey publicKey, X509Certificate certificate) {
        this.privateKey = privateKey;
        this.publicKey = publicKey;
        this.certificate = certificate;
    }
}

1.13. 编写证书生成工具类

public class CertificateUtils {
    /**
     * 生成密钥对及证书
     * @param certificateInfo 证书信息
     * @param caPrivateKey 根证书私钥,用于给证书签名
     * @return
     * @throws Throwable
     */
    public static KeyAndCertificate generateCertificate(CertificateInfo certificateInfo, PrivateKey caPrivateKey) throws Throwable {
        // 生成证书所需密钥对,RSA 算法,密钥长度 2048 字节
        KeyPair keyPair = KeyUtil.generateKeyPair(certificateInfo.getKeyAlgorithm(), 2048);
        PrivateKey privateKey = keyPair.getPrivate();
        PublicKey publicKey = keyPair.getPublic();
        //证书颁发者信息
        X500Name issuer = certificateInfo.getIssuer();
        //证书主题信息
        X500Name subject = certificateInfo.getSubject();
        SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(publicKey.getEncoded());
        X509v3CertificateBuilder x509v3CertificateBuilder = new X509v3CertificateBuilder(
                issuer,
                certificateInfo.getSerial(),
                certificateInfo.getNotBefore(),
                certificateInfo.getNotAfter(),
                subject,
                subjectPublicKeyInfo
        );
        JcaContentSignerBuilder jcaContentSignerBuilder = new JcaContentSignerBuilder(certificateInfo.getSignAlgorithm());
        //如果没有根证书私钥,自己给自己签名,在生成根证书时会有这种情况
        if(Objects.isNull(caPrivateKey)) {
            caPrivateKey = privateKey;
        }
        //签名者
        ContentSigner contentSigner = jcaContentSignerBuilder.build(caPrivateKey);
        //生成证书
        X509CertificateHolder x509CertificateHolder = x509v3CertificateBuilder.build(contentSigner);
        //X509CertificateHolder 转 X509Certificate
        JcaX509CertificateConverter jcaX509CertificateConverter = new JcaX509CertificateConverter();
        //转换成 X509Certificate 证书对象
        X509Certificate x509Certificate = jcaX509CertificateConverter.getCertificate(x509CertificateHolder);
        return new KeyAndCertificate(privateKey, publicKey, x509Certificate);
    }

    /**
     * 保存私钥到磁盘
     * @param privateKey
     * @param filePath
     * @throws Throwable
     */
    public static void save(PrivateKey privateKey, String filePath) throws Throwable {
        save(privateKey.getEncoded(), PRIVATE_KEY_TYPE, filePath);
    }

    /**
     * 保存公钥到磁盘
     * @param publicKey
     * @param filePath
     * @throws Throwable
     */
    public static void save(PublicKey publicKey, String filePath) throws Throwable {
        save(publicKey.getEncoded(), PUBLIC_KEY_TYPE, filePath);
    }

    /**
     * 保存证书到磁盘
     * @param certificate
     * @param filePath
     * @throws Throwable
     */
    public static void save(Certificate certificate, String filePath) throws Throwable {
        save(certificate.getEncoded(), CERTIFICATE_TYPE, filePath);
    }

    /**
     * 以 PEM 格式保存密钥、证书到磁盘
     * @param encodedBytes
     * @param type
     * @param filePath
     * @throws Throwable
     */
    public static void save(byte[]  encodedBytes, String type, String filePath) throws Throwable {
        FileUtil.mkParentDirs(filePath);
        PemObject pemObject = new PemObject(type, encodedBytes);
        try(FileWriter fileWriter = new FileWriter(filePath); PemWriter pemWriter = new PemWriter(fileWriter)) {
            pemWriter.writeObject(pemObject);
        }
    }
}

1.14. 测试

public class X509CertificateDemo {

    /**
     * 自签名根证书私钥保存路径
     */
    private static final String CA_PRIVATE_KEY_FILE_PATH = StrUtil.format("{}/x509-certificate-demo/ca/ca-private.key", System.getProperty("user.home"));
    /**
     * 自签名根证书公钥保存路径
     */
    private static final String CA_PUBLIC_KEY_FILE_PATH = StrUtil.format("{}/x509-certificate-demo/ca/ca-public.key", System.getProperty("user.home"));
    /**
     * 自签名根证书保存路径
     */
    private static final String CA_CERTIFICATE_FILE_PATH = StrUtil.format("{}/x509-certificate-demo/ca/ca.cer", System.getProperty("user.home"));
    /**
     * 自签名证书私钥保存路径
     */
    private static final String SELF_SIGNED_PRIVATE_KEY_FILE_PATH = StrUtil.format("{}/x509-certificate-demo/self/self-private.key", System.getProperty("user.home"));
    /**
     * 自签名证书公钥保存路径
     */
    private static final String SELF_SIGNED_PUBLIC_KEY_FILE_PATH = StrUtil.format("{}/x509-certificate-demo/self/self-public.key", System.getProperty("user.home"));
    /**
     * 自签名证书保存路径
     */
    private static final String SELF_SIGNED_CERTIFICATE_FILE_PATH = StrUtil.format("{}/x509-certificate-demo/self/self.cer", System.getProperty("user.home"));
    /**
     * pkcs12 格式证书保存路径
     */
    private static final String SELF_SIGNED_PKCS12_FILE_PATH = StrUtil.format("{}/x509-certificate-demo/self/self.p12", System.getProperty("user.home"));
    /**
     * pkcs12 格式证书密码
     */
    private static final String SELF_SIGNED_PKCS12_CERTIFICATE_PASSWORD = "KtYBnbVEotFvYEFm";

    public static void main(String[] args) throws Throwable {
        generateCaCertificate();
        generateSelfSignedCertificate();
        generatePkcs12Store();
    }

    /**
     * 生成根证书及其密钥对
     * @throws Throwable
     */
    public static void generateCaCertificate() throws Throwable {
        //根证书主题信息
        X500Name subject = new SubjectBuilder()
                .setCn("My CA common name")
                .setO("My CA organization")
                .setOu("My CA organizational unit name")
                .setC("My CA country code")
                .setSt("My CA state, or province name")
                .setL("My CA locality name")
                .build();

        //根证书颁发者信息,即自身
        X500Name issuer = subject;

        //根证书有效期
        DateTime notBefore = DateUtil.yesterday();
        DateTime notAfter = DateUtil.offset(notBefore, DateField.YEAR, 10);

        CertificateInfo certificateInfo = new CertificateInfo();
        certificateInfo.setSerial(BigInteger.valueOf(RandomUtil.randomLong(1L, Long.MAX_VALUE)));
        certificateInfo.setIssuer(issuer);
        certificateInfo.setSubject(subject);
        certificateInfo.setNotBefore(notBefore);
        certificateInfo.setNotAfter(notAfter);
        certificateInfo.setKeyAlgorithm(KEY_ALGORITHM);
        certificateInfo.setSignAlgorithm(SIGN_ALGORITHM);

        //生成根证书及其密钥对
        KeyAndCertificate keyAndCertificate = CertificateUtils.generateCertificate(certificateInfo, null);
        //保存根证书及其密钥对到磁盘
        CertificateUtils.save(keyAndCertificate.getPrivateKey(), CA_PRIVATE_KEY_FILE_PATH);
        CertificateUtils.save(keyAndCertificate.getPublicKey(), CA_PUBLIC_KEY_FILE_PATH);
        CertificateUtils.save(keyAndCertificate.getCertificate(), CA_CERTIFICATE_FILE_PATH);
    }

    /**
     * 生成自签名证书及其密钥对
     * @throws Throwable
     */
    public static void generateSelfSignedCertificate() throws Throwable {
        //根证书私钥
        PrivateKey caPrivateKey;
        //从磁盘加载根证书私钥
        try(FileInputStream fileInputStream = new FileInputStream(CA_PRIVATE_KEY_FILE_PATH);) {
            caPrivateKey = PemUtil.readPemPrivateKey(fileInputStream);
        }

        //从磁盘加载根证书
        X509Certificate caCertificate;
        try(FileInputStream fileInputStream = new FileInputStream(CA_CERTIFICATE_FILE_PATH);) {
            caCertificate = (X509Certificate) KeyUtil.readX509Certificate(fileInputStream);
        }

        //证书颁发者信息
        X500Name issuer = new X500Name(caCertificate.getIssuerX500Principal().getName());

        //证书主题信息
        X500Name subject = new SubjectBuilder()
                .setCn("My common name")
                .setO("My organization")
                .setOu("My organizational unit name")
                .setC("My country code")
                .setSt("My state, or province name")
                .setL("My locality name")
                .build();

        //证书有效期
        DateTime notBefore = DateUtil.yesterday();
        DateTime notAfter = DateUtil.offset(notBefore, DateField.YEAR, 10);

        CertificateInfo certificateInfo = new CertificateInfo();
        certificateInfo.setSerial(BigInteger.valueOf(RandomUtil.randomLong(1L, Long.MAX_VALUE)));
        certificateInfo.setIssuer(issuer);
        certificateInfo.setSubject(subject);
        certificateInfo.setNotBefore(notBefore);
        certificateInfo.setNotAfter(notAfter);
        certificateInfo.setKeyAlgorithm(KEY_ALGORITHM);
        certificateInfo.setSignAlgorithm(SIGN_ALGORITHM);

        //生成自签名证书及其密钥对
        KeyAndCertificate keyAndCertificate = CertificateUtils.generateCertificate(certificateInfo, caPrivateKey);
        //保存自签名证书及其密钥对到磁盘
        CertificateUtils.save(keyAndCertificate.getPrivateKey(), SELF_SIGNED_PRIVATE_KEY_FILE_PATH);
        CertificateUtils.save(keyAndCertificate.getPublicKey(), SELF_SIGNED_PUBLIC_KEY_FILE_PATH);
        CertificateUtils.save(keyAndCertificate.getCertificate(), SELF_SIGNED_CERTIFICATE_FILE_PATH);
    }

    /**
     * 生成 pkcs12 格式证书
     * @throws Exception
     */
    public static void generatePkcs12Store() throws Exception {
        char[] password = SELF_SIGNED_PKCS12_CERTIFICATE_PASSWORD.toCharArray();
        //证书私钥
        PrivateKey privateKey;
        //从磁盘加载证书私钥
        try(FileInputStream fileInputStream = new FileInputStream(SELF_SIGNED_PRIVATE_KEY_FILE_PATH)) {
            privateKey = PemUtil.readPemPrivateKey(fileInputStream);
        }
        //证书
        Certificate certificate;
        //从磁盘加载证书
        try(FileInputStream fileInputStream = new FileInputStream(SELF_SIGNED_CERTIFICATE_FILE_PATH)) {
            certificate = KeyUtil.readX509Certificate(fileInputStream);
        }
        KeyStore pkcs12 = KeyStore.getInstance("pkcs12");
        pkcs12.load(null, password);
        pkcs12.setKeyEntry("self signed key", privateKey, password, new Certificate[] {certificate});
        try(FileOutputStream fileOutputStream = new FileOutputStream(SELF_SIGNED_PKCS12_FILE_PATH)) {
            pkcs12.store(fileOutputStream, password);
        }
    }
}

启动程序,主目录下将生成以下证书及密钥对文件:

.
├── ca
│   ├── ca-private.key
│   ├── ca-public.key
│   └── ca.cer
└── self
    ├── self-private.key
    ├── self-public.key
    ├── self.cer
    └── self.p12
posted @ 2024-10-28 16:44  Jason207010  阅读(720)  评论(0编辑  收藏  举报