Cuthbert's Blog

X509Certificate General Method

Published on
Published on
/8 mins read/---

依赖配置

    <!-- https://mvnrepository.com/artifact/org.bouncycastle/bcpkix-jdk18on -->
    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcpkix-jdk18on</artifactId>
        <version>1.77</version>
    </dependency>

工具类代码

/**
 * @author cxhello
 * @date 2025/5/13
 */
public class KeyInfo {
 
    /**
     * 公钥
     */
    private String publicKeyStr;
 
    /**
     * 私钥
     */
    private String privateKeyStr;
 
    public String getPublicKeyStr() {
        return publicKeyStr;
    }
 
    public void setPublicKeyStr(String publicKeyStr) {
        this.publicKeyStr = publicKeyStr;
    }
 
    public String getPrivateKeyStr() {
        return privateKeyStr;
    }
 
    public void setPrivateKeyStr(String privateKeyStr) {
        this.privateKeyStr = privateKeyStr;
    }
 
}
/**
 * @author cxhello
 * @date 2025/5/13
 */
public class CertPathInfo {
 
    /**
     * 公钥地址
     */
    private String publicKeyPath;
 
    /**
     * 私钥地址
     */
    private String privateKeyPath;
 
    /**
     * 证书地址
     */
    private String certPath;
 
    public String getPublicKeyPath() {
        return publicKeyPath;
    }
 
    public void setPublicKeyPath(String publicKeyPath) {
        this.publicKeyPath = publicKeyPath;
    }
 
    public String getPrivateKeyPath() {
        return privateKeyPath;
    }
 
    public void setPrivateKeyPath(String privateKeyPath) {
        this.privateKeyPath = privateKeyPath;
    }
 
    public String getCertPath() {
        return certPath;
    }
 
    public void setCertPath(String certPath) {
        this.certPath = certPath;
    }
 
}
/**
 * @author cxhello
 * @date 2025/5/13
 */
public class CertException extends RuntimeException {
 
    public CertException(Throwable cause) {
        super(cause);
    }
 
}
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.springframework.web.multipart.MultipartFile;
import top.cxhello.monitor.exception.CertException;
 
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.naming.InvalidNameException;
import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn;
import javax.security.auth.x500.X500Principal;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.Date;
 
/**
 * @author cxhello
 * @date 2025/5/13
 */
public class CertUtils {
 
    private static final String ALGORITHM = "RSA";
 
    private static final Integer RSA_LENGTH = 2048;
 
    private static final String SIGN_ALGORITHM = "SHA256WITHRSA";
 
    private static final String ALGORITHM_SECURE_MODE_PADDING_SCHEME = "RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING";
 
    public static final String PUBLIC_KEY_PATH = "public_key.pem";
 
    public static final String PRIVATE_KEY_PATH = "private_key.der";
 
    public static final String CERT_PATH = ".crt";
 
    private CertUtils() {
 
    }
 
    /**
     * 生成公钥&私钥
     * @return
     */
    public static KeyInfo generate() {
        // 生成RSA密钥对
        KeyPairGenerator keyPairGenerator = null;
        try {
            keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM);
        } catch (NoSuchAlgorithmException e) {
            throw new CertException(e);
        }
        keyPairGenerator.initialize(RSA_LENGTH, new SecureRandom());
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        // 获取公钥和私钥
        PublicKey publicKey = keyPair.getPublic();
        PrivateKey privateKey = keyPair.getPrivate();
        KeyInfo keyInfo = new KeyInfo();
        keyInfo.setPublicKeyStr(Base64.getEncoder().encodeToString(publicKey.getEncoded()));
        keyInfo.setPrivateKeyStr(Base64.getEncoder().encodeToString(privateKey.getEncoded()));
        return keyInfo;
    }
 
    /**
     * 生成证书
     * @param filePath
     * @param name
     * @param fileName
     * @param serialNumber
     * @param publicKey
     * @param privateKey
     * @return
     */
    public static CertPathInfo generate(String filePath, String name, String fileName, BigInteger serialNumber, PublicKey publicKey, PrivateKey privateKey) {
        X500Name x500Name = new X500NameBuilder(BCStyle.INSTANCE)
                .addRDN(BCStyle.CN, name)
                //.addRDN(BCStyle.OU, "spring-boot-monitor")
                .addRDN(BCStyle.O, "CXHELLO Ltd.")
                .addRDN(BCStyle.L, "Beijing")
                .addRDN(BCStyle.ST, "Beijing")
                .addRDN(BCStyle.C, "CN")
                .build();
        X500Principal dnName = null;
        try {
            // 使用X.509标准
            dnName = new X500Principal(x500Name.getEncoded());
        } catch (IOException e) {
            throw new CertException(e);
        }
        // 证书的有效期限
        Date notBefore = new Date(System.currentTimeMillis());
        Date notAfter = new Date(System.currentTimeMillis() + 365L * 24 * 60 * 60 * 1000); // 有效期1年
        // Date notAfter = DateUtil.parseDate("2099-12-31 23:59:59");
 
        // 使用Java的证书生成类
        JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(dnName, serialNumber, notBefore, notAfter, dnName, publicKey);
        CertPathInfo certPathInfo = new CertPathInfo();
        certPathInfo.setPublicKeyPath(filePath + PUBLIC_KEY_PATH);
        certPathInfo.setPrivateKeyPath(filePath + PRIVATE_KEY_PATH);
        certPathInfo.setCertPath(filePath + fileName + CERT_PATH);
        try {
            // 签名算法
            ContentSigner contentSigner = new JcaContentSignerBuilder(SIGN_ALGORITHM).build(privateKey);
            // 生成证书
            X509CertificateHolder certHolder = certBuilder.build(contentSigner);
            X509Certificate certificate = new JcaX509CertificateConverter().setProvider(new BouncyCastleProvider()).getCertificate(certHolder);
            // 保存证书
            saveToFile(certPathInfo.getPublicKeyPath(), publicKey.getEncoded());
            saveToFile(certPathInfo.getPrivateKeyPath(), privateKey.getEncoded());
            saveToFile(certPathInfo.getCertPath(), certificate.getEncoded());
        } catch (OperatorCreationException | CertificateException | IOException e) {
            throw new CertException(e);
        }
        return certPathInfo;
    }
 
    /**
     * 加载公钥
     * @param filename
     * @return
     */
    public static PublicKey loadPublicKey(String filename) {
        try {
            // 读取文件内容
            byte[] keyBytes = Files.readAllBytes(Paths.get(filename));
            // 使用KeyFactory将其转换为PublicKey对象
            X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
            return keyFactory.generatePublic(spec);
        } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new CertException(e);
        }
    }
 
    /**
     * 加载公钥-通过字符串
     * @param publicKeyStr
     * @return
     */
    public static PublicKey loadPublicKeyStr(String publicKeyStr) {
        try {
            byte[] keyBytes = Base64.getDecoder().decode(publicKeyStr);
            // 使用KeyFactory将其转换为PublicKey对象
            X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
            return keyFactory.generatePublic(spec);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new CertException(e);
        }
    }
 
    /**
     * 加载私钥-通过文件路径
     * @param filename
     * @return
     */
    public static PrivateKey loadPrivateKey(String filename) {
        try {
            byte[] keyBytes = Files.readAllBytes(Paths.get(filename));
            PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
            return keyFactory.generatePrivate(spec);
        } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new CertException(e);
        }
    }
 
    /**
     * 加载私钥-通过字符串
     * @param privateKeyStr
     * @return
     */
    public static PrivateKey loadPrivateKeyStr(String privateKeyStr) {
        try {
            byte[] decodedKey = Base64.getDecoder().decode(privateKeyStr);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(decodedKey);
            KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
            return keyFactory.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
            throw new CertException(e);
        }
    }
 
    /**
     * 加载证书
     * @param filename
     * @return
     */
    public static X509Certificate load(String filename) {
        X509Certificate cert;
        try (FileInputStream fis = new FileInputStream(filename)) {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            // 加载证书文件
            cert = (X509Certificate) cf.generateCertificate(fis);
            cert.checkValidity();
        } catch (IOException | CertificateException e) {
            throw new CertException(e);
        }
        return cert;
    }
 
    /**
     * 加载证书
     * @param file
     * @return
     */
    public static X509Certificate load(MultipartFile file) {
        X509Certificate cert;
        try (InputStream is = file.getInputStream()) {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            // 通过MultipartFile获取输入流来加载证书文件
            cert = (X509Certificate) cf.generateCertificate(is);
            cert.checkValidity();
        } catch (IOException | CertificateException e) {
            throw new CertException(e);
        }
        return cert;
    }
 
    /**
     * 获取证书通用名称
     * @param x509Certificate
     * @return
     */
    public static String getCommonName(X509Certificate x509Certificate) {
        LdapName ln = null;
        try {
            ln = new LdapName(x509Certificate.getSubjectX500Principal().getName());
        } catch (InvalidNameException e) {
            throw new CertException(e);
        }
        String commonName = null;
        for (Rdn rdn : ln.getRdns()) {
            if (rdn.getType().equalsIgnoreCase("CN")) {
                commonName = rdn.getValue().toString();
                break;
            }
        }
        return commonName;
    }
 
    /**
     * 使用公钥加密数据并返回Base64编码的字符串
     * @param data
     * @param publicKey
     * @return
     */
    public static String encryptString(String data, PublicKey publicKey) {
        try {
            Cipher cipher = Cipher.getInstance(ALGORITHM_SECURE_MODE_PADDING_SCHEME);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            byte[] encryptedBytes = cipher.doFinal(data.getBytes());
            return Base64.getEncoder().encodeToString(encryptedBytes);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException |
                 BadPaddingException e) {
            throw new CertException(e);
        }
    }
 
    /**
     * 使用私钥解密Base64编码的加密字符串
     * @param encryptedData
     * @param privateKey
     * @return
     */
    public static String decryptString(String encryptedData, PrivateKey privateKey) {
        try {
            Cipher cipher = Cipher.getInstance(ALGORITHM_SECURE_MODE_PADDING_SCHEME);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] decryptedBytes = cipher.doFinal(Base64.getDecoder().decode(encryptedData));
            return new String(decryptedBytes);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException | IllegalBlockSizeException |
                 BadPaddingException e) {
            throw new CertException(e);
        }
    }
 
    /**
     * 使用私钥签名数据并返回Base64编码的字符串
     * @param data
     * @param privateKey
     * @return
     */
    public static String sign(String data, PrivateKey privateKey) {
        try {
            Signature signature = Signature.getInstance(SIGN_ALGORITHM);
            signature.initSign(privateKey);
            signature.update(data.getBytes());
            byte[] sign = signature.sign();
            return Base64.getEncoder().encodeToString(sign);
        } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
            throw new CertException(e);
        }
    }
 
    /**
     * 使用公钥验签
     * @param data
     * @param publicKey
     * @param sign
     * @return
     */
    public static boolean verify(String data, PublicKey publicKey, String sign) {
        try {
            Signature signature = Signature.getInstance(SIGN_ALGORITHM);
            signature.initVerify(publicKey);
            signature.update(data.getBytes());
            return signature.verify(Base64.getDecoder().decode(sign));
        } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
            throw new CertException(e);
        }
    }
 
    /**
     * 保存文件
     * @param fileName
     * @param bytes
     * @throws IOException
     */
    private static void saveToFile(String fileName, byte[] bytes) throws IOException {
        Path path = Paths.get(fileName).getParent();
        // 检查父目录是否存在,如果不存在则创建
        if (path != null && !Files.exists(path)) {
            Files.createDirectories(path);
        }
        try (FileOutputStream fos = new FileOutputStream(fileName)) {
            fos.write(bytes);
        }
    }
 
}

单元测试

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import top.cxhello.monitor.util.CertPathInfo;
import top.cxhello.monitor.util.KeyInfo;
 
import java.math.BigInteger;
import java.nio.file.Path;
import java.security.cert.X509Certificate;
 
import static org.junit.jupiter.api.Assertions.assertTrue;
import static top.cxhello.monitor.util.CertUtils.*;
 
/**
 * @author cxhello
 * @date 2025/5/13
 */
class CertUtilsTest {
 
    private static final String TEST_DATA = "data";
 
    @Test
    void testSign(@TempDir Path tempDir) {
        String name = "test";
        KeyInfo keyInfo = generate();
        CertPathInfo certPathInfo = generate(tempDir.toString() + "/", name, name, BigInteger.valueOf(System.currentTimeMillis()),
                loadPublicKeyStr(keyInfo.getPublicKeyStr()), loadPrivateKeyStr(keyInfo.getPrivateKeyStr()));
        String sign = sign(TEST_DATA, loadPrivateKey(certPathInfo.getPrivateKeyPath()));
        X509Certificate x509Certificate = load(certPathInfo.getCertPath());
        assertTrue(verify(TEST_DATA, x509Certificate.getPublicKey(), sign));
    }
 
}