服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - 基于Java Swing和BouncyCastle的证书生成工具

基于Java Swing和BouncyCastle的证书生成工具

2023-11-26 01:15未知服务器之家 Java教程

"Almost no one will remember what he had just not interested." - Nobody “几乎没有人会记得他所丝毫不感兴趣的事情。” —— 佚名 0x00 大纲 目录 0x00 大纲 0x01 前言 0x02 技术选型 0x03 需求分析 目标用户 用户故事 功能需求 安全需求 兼容需求 性能

"Almost no one will remember what he had just not interested." - Nobody

“几乎没有人会记得他所丝毫不感兴趣的事情。” —— 佚名

0x00 大纲

目录
  • 0x00 大纲
  • 0x01 前言
  • 0x02 技术选型
  • 0x03 需求分析
    • 目标用户
    • 用户故事
    • 功能需求
    • 安全需求
    • 兼容需求
    • 性能需求
  • 0x04 原型设计
    • 主窗体
    • RSA根证书生成页面
    • ECC根证书生成页面
    • RSA证书生成页面
    • ECC证书生成页面
  • 0x05 架构设计
  • 0x06 概要设计
  • 0x07 详细设计
  • 0x08 编码实现
    • RSA根证书生成(节选关键部分)
    • ECC根证书生成
  • 0x09 小结
    • 运行效果

0x01 前言

随着这些年人们对于信息安全的愈加重视,工作中经常用到各种数字证书,最主要的用途就是加密和签名。但是反复生成用于测试的数字证书是一件麻烦事,虽然感谢有强大的OpenSSL工具,但是问就是记不住命令,问就是不想看文档。为了提高工作效率,降低苦痛指数,遂决定开发一个证书生成小工具。

0x02 技术选型

我们需要一个简单的GUI,在Java的领域中,可以选择使用JavaFX或着Swing来实现,鉴于我们不需要太花哨的东西,只需要几个简单的布局和控件,这里选择了使用Swing来开发图形界面。小声BB:甲方是我,乙方也是我,能用就行,要啥自行车。

我们需要用到RSA或着国密的数字证书,凑巧BouncyCastle就是一个提供了很多哈希算法和加密算法的第三方库。老牌子,易使用。

初步估计工期为一周,太长容易从入门到放弃,太短则对心脏不友好。

0x03 需求分析

目标用户

  • 给开发人员用
  • 给业务人员用
  • 给测试人员用

用户故事

作为使用者:

  • 我需要工具同时支持生成RSA和国密的证书;
  • 我想简单点几下按钮就能生成自己想要的证书,我不想记一大串又臭又长的命令行;
  • 我不希望到处搜索一堆奇奇怪怪的参数,最好什么都不用我填,什么都不用我自己思考;
  • 什么YES or NO统统不要,问就是YES......
  • 我希望你界面尽量不要太花哨,时间长了看的我眼花;
  • 最好支持批量操作,免得折了我鼠标的寿。

功能需求

  • 使用GUI操作的小工具,包含四个功能菜单:
    • RSA根证书生成
    • RSA证书生成
    • ECC根证书生成,支持批量操作;
    • ECC证书生成,支持批量操作;
    • 生成的同时通过文件选择对话框选择保存路径;
  • RSA根证书签发:
    • 可以根据选定的密钥算法和签名算法,生成RSA和国密的非对称密钥对,默认使用123456对私钥进行加密,可指定密码,也可一键生成随机密码;
    • 证书
    • 对于同一对密钥,需要同时导出:
      • DER编码的PKCS#8加密私钥,文件后缀名为.key;
      • PEM编码的PKCS#8加密私钥,文件后缀名为.pri.pem;
      • PEM编码的X.509公钥,文件后缀名为.pub.pem;
      • DER编码的X.509公钥,文件后缀名为.cer;
      • 同时包含X.509公钥和PKCS#8加密私钥的PKCS#12证书,文件后缀名为.pfx;
      • 包含私钥密码的文本文件,文件后缀为.txt;
        以上文件在一个压缩包中打包导出,压缩文件后缀为.zip;
  • RSA证书签发:
    • 使用选定的根证书,签发中间证书或叶子证书;
    • 导出要求同上;
  • ECC证书签发:
    • 同RSA根证书签发;
  • ECC证书签发:
    • 使用国密sm2p256v1曲线,其余同RSA证书签发;
  • 对于RSA证书,拟支持2048(首选)、1024、4096密钥位数,支持SHA1withRSA、SHA256withRSA、MD5withRSA签名算法;
  • 对于ECC证书,拟支持sm2p256v1曲线,支持SM3withSM2、SHA256withSM2签名算法;
  • 证书颁发者信息至少包含:
    • country code (C)
    • locality name (L)
    • state/province name (ST)
    • organization (O)
    • common name (CN)
    • organizational unit name (OU)
      最好都有默认值,能不填就尽量不要填,免得浪费我喝咖啡的时间;
  • 证书使用者信息默认同颁发者信息;
  • 证书有效期默认十年(部分系统不允许过长的证书有效期,第一个版本不考虑这么多);

安全需求

略,见功能需求加密部分。

兼容需求

应与操作系统和其他软硬加密机生成的证书互相兼容。

性能需求

能用就行。

0x04 原型设计

有了大体上的功能需求分析,就可以进入设计阶段了,既然是GUI程序,那就先从界面入手,简单做一下原型。正好idea上带有Swing UI Designer,所见即所得,原型可以直接转化为后期工程。

主窗体

使用JTabbedPane作为容器,承载四个主要功能菜单,简化交互设计。再加上四个功能界面功能上高度相似,也意味着GUI元素的高度相似,只要设计出一个,其他几个直接Ctrl+C,Ctrl+V即可。

为了减少动态窗体大小带来的布局调整和元素Resize,直接使用固定大小窗体,初步定为 640*560 像素。

RSA根证书生成页面

大部分GUI元素都是按钮(JButton),下拉框(JComboBox)和输入框(JTextField or JPasswordField),因此可以考虑左边为标签(JLabel),右边为交互元素的表单结构,首选布局即为Grid Layout。剩下的就是往JPanel上扔控件了。经过一番努力,我们得到了这样的窗体设计:

基于Java Swing和BouncyCastle的证书生成工具

于是通过复制粘贴,很快可以完成剩余的三个界面:

ECC根证书生成页面

基于Java Swing和BouncyCastle的证书生成工具

RSA证书生成页面

基于Java Swing和BouncyCastle的证书生成工具

ECC证书生成页面

基于Java Swing和BouncyCastle的证书生成工具

0x05 架构设计

由于证书生成工具的定位是C端程序,区别于B/S模型的应用,它更适合使用传统老三层模型(USL、BLL、DAL),而不是Web应用上经常使用的MVC分层模式。

通过前面的原型设计,已经基本完成了USL表示层中的视图部分,接下来需要实现USL的控制部分和BLL层的业务逻辑(其实就是实现各个控件需要关注的事件监听以及对应的事件处理),由于我们的数据持久方式就是文件,所以数据访问层反而比较简单了。

DAL层虽然简单,但是我们保持开放修改的后路,万一哪天要用数据库呢?嘿嘿~

0x06 概要设计

通过需求分析和原型设计已经大致可以划分出功能模块了,核心部分就是几个按钮的事件监听和处理。

它们都可以统合到一个流程模型中:

参数录入->点击按钮->获取参数->验证参数->生成数据->更新UI

以生成RSA根证书处理流程为例:

  1. 参数录入:界面上选择密钥位数、签名算法、证书密码(可选)、证书颁发者信息(可选);
  2. 点击按钮:监听按钮点击事件;
  3. 获取参数:获取UI和后台的相关参数;
  4. 验证参数:进行参数合法性检查,包括但不限于长度,格式,关联性等;
  5. 生成数据:根据参数,调用BouncyCastle库生成RSA密钥对;适配和打包输出压缩文件;
  6. 更新UI:提示生成结果,选择文件存储路径等;

异常处理:

  1. 界面弹出提示框;
  2. 后台记录日志等;

0x07 详细设计

将主业务流程的各个节点展开并详细描述其实现,必要时借助伪代码进行逻辑表示;这里就不写了,问就是懒……

0x08 编码实现

RSA根证书生成(节选关键部分)

public final class RSA {
    public static final String MD5_WITH_RSA = "MD5withRSA";
    public static final String SHA1_WITH_RSA = "SHA1withRSA";
    public static final String SHA256_WITH_RSA = "SHA256withRSA";
    public static final int KEY_SIZE_1024 = 1024;
    public static final int KEY_SIZE_2048 = 2048;
    public static final int KEY_SIZE_4096 = 4096;

    private RSA() {
    }

    public static KeyPair generateKeyPair(int keySize) throws NoSuchAlgorithmException {
        if ((keySize & keySize - 1) != 0) {
            throw new InvalidParameterException("非法RSA密钥位数" + keySize);
        }
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(Global.ALGO_NAME_RSA);
        keyPairGenerator.initialize(keySize);
        return keyPairGenerator.generateKeyPair();
    }
}
private ActionListener rsaRootGenerateButtonListener() {
    return e -> {
        try {
            checkRSARootCertificateConfig();
            JFileChooser fileChooser = makeSaveFileChooser();
            if (JFileChooser.APPROVE_OPTION == fileChooser.showSaveDialog(mainFrame)) {
                String saveDir = fileChooser.getSelectedFile().getAbsolutePath();
                char[] passwordChars = rsaRootPasswordField.getPassword();
                X500Name issuer = obtainIssuerEntry(rsaIssuerCField, rsaIssuerLField, rsaIssuerSTField, rsaIssuerOField, rsaIssuerCNField, rsaIssuerOUField).getValue();
                KeyPair caKeyPair = RSA.generateKeyPair(rsaRootKeySizeBox.getItemAt(rsaRootKeySizeBox.getSelectedIndex()));
                X509Certificate caCertificate = CertificateUtil.generateCertificate(rsaRootSignAlgoBox.getItemAt(rsaRootSignAlgoBox.getSelectedIndex()), caKeyPair, issuer);
                caCertificate.checkValidity(caCertificate.getNotBefore());
                caCertificate.checkValidity(caCertificate.getNotAfter());
                caCertificate.verify(caKeyPair.getPublic());
                PKCS8EncryptedPrivateKeyInfo caPrivateKeyInfo = KeyUtil.makePKCS8EncryptedKey(caKeyPair.getPrivate(), passwordChars);
                PKCS12PfxPdu caPfxPdu = KeyUtil.makePfx(caCertificate, caKeyPair.getPrivate(), passwordChars);
                saveCertificateFiles(saveDir, rsaIssuerCNField.getText(), caPrivateKeyInfo, caCertificate, caPfxPdu, passwordChars);
            }
        } catch (RuntimeException | IOException | GeneralSecurityException | OperatorCreationException | PKCSException ex) {
            JOptionPane.showMessageDialog(mainFrame, ex.getLocalizedMessage(), "错误提示", JOptionPane.ERROR_MESSAGE);
        }
    };
}
public static X509Certificate generateCertificate(String signatureAlgorithm, KeyPair keyPair, X500Name subject) throws OperatorCreationException, IOExcepti
    return generateCertificate(signatureAlgorithm, keyPair, subject, Duration.ofDays(365L * 10));
}

public static X509Certificate generateCertificate(String signatureAlgorithm, KeyPair keyPair, X500Name subject, Duration duration) throws OperatorCreationE
    ContentSigner contentSigner = makeContentSigner(signatureAlgorithm, keyPair.getPrivate());
    PKCS10CertificationRequest csr = generateCsr(contentSigner, keyPair, subject, true);
    BcX509ExtensionUtils extUtils = new BcX509ExtensionUtils();
    final Date notBefore = new Date();
    X509v3CertificateBuilder v3CertificateBuilder = new X509v3CertificateBuilder(
            csr.getSubject(),
            BigInteger.valueOf(UUID.randomUUID().getMostSignificantBits() & Long.MAX_VALUE),
            notBefore,
            new Date(notBefore.getTime() + duration.toMillis()),
            Locale.CHINA,
            csr.getSubject(),
            csr.getSubjectPublicKeyInfo());
    v3CertificateBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(true));
    v3CertificateBuilder.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign));
    v3CertificateBuilder.addExtension(Extension.subjectKeyIdentifier, false, extUtils.createSubjectKeyIdentifier(csr.getSubjectPublicKeyInfo()));
    v3CertificateBuilder.addExtension(Extension.authorityKeyIdentifier, false, extUtils.createAuthorityKeyIdentifier(csr.getSubjectPublicKeyInfo()));
    return new JcaX509CertificateConverter().setProvider(BouncyCastleProvider.PROVIDER_NAME).getCertificate(v3CertificateBuilder.build(contentSigner));
}
public static PKCS8EncryptedPrivateKeyInfo makePKCS8EncryptedKey(PrivateKey privateKey, char[] passwordChars) {
    PKCS8EncryptedPrivateKeyInfoBuilder privateKeyInfoBuilder = new JcaPKCS8EncryptedPrivateKeyInfoBuilder(privateKey);
    return privateKeyInfoBuilder.build(new BcPKCS12PBEOutputEncryptorBuilder(
            PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC,
            new CBCBlockCipher(new DESedeEngine())).build(passwordChars));
}

public static PKCS12PfxPdu makePfx(X509Certificate x509Certificate, PrivateKey privateKey, char[] passwordChars) throws IOException, PKCSException {
    PKCS12SafeBagBuilder certBagBuilder = new JcaPKCS12SafeBagBuilder(x509Certificate);
    certBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString("Forge CA Certification"));
    certBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, ASN1Primitive.fromByteArray(x509Certificate.getExtensionValue(Extension.subjectKeyIdentifier.getId())));
    PKCS12SafeBagBuilder keyBagBuilder = new JcaPKCS12SafeBagBuilder(privateKey,
            new BcPKCS12PBEOutputEncryptorBuilder(
                    PKCSObjectIdentifiers.pbeWithSHAAnd3_KeyTripleDES_CBC,
                    new CBCBlockCipher(new DESedeEngine())).build(passwordChars));
    keyBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString("Forge CA Key"));
    keyBagBuilder.addBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, ASN1Primitive.fromByteArray(x509Certificate.getExtensionValue(Extension.subjectKeyIdentifier.getId())));
    PKCS12PfxPduBuilder pfxPduBuilder = new PKCS12PfxPduBuilder();
    PKCS12SafeBag[] certs = new PKCS12SafeBag[1];
    certs[0] = certBagBuilder.build();
    pfxPduBuilder.addEncryptedData(new BcPKCS12PBEOutputEncryptorBuilder(
                    PKCSObjectIdentifiers.pbeWithSHAAnd40BitRC2_CBC,
                    new CBCBlockCipher(new RC2Engine())).build(passwordChars),
            certs);
    pfxPduBuilder.addData(keyBagBuilder.build());
    return pfxPduBuilder.build(new BcPKCS12MacCalculatorBuilder(), passwordChars);
}

ECC根证书生成

略,与RSA大同小异。

0x09 小结

运行效果

基于Java Swing和BouncyCastle的证书生成工具

基于Java Swing和BouncyCastle的证书生成工具

基于Java Swing和BouncyCastle的证书生成工具

基于Java Swing和BouncyCastle的证书生成工具

基于Java Swing和BouncyCastle的证书生成工具

基于Java Swing和BouncyCastle的证书生成工具

基于Java Swing和BouncyCastle的证书生成工具

基于Java Swing和BouncyCastle的证书生成工具

基于Java Swing和BouncyCastle的证书生成工具

完成任务。

延伸 · 阅读

精彩推荐