【问题标题】:Self signed X509 Certificate with Bouncy Castle in Java使用 Java 中的 Bouncy Castle 自签名 X509 证书
【发布时间】:2015-07-03 08:11:27
【问题描述】:

我需要使用 Java 中的 Bouncy Castle 创建自签名 X509 证书,但我尝试包含的每个类都已弃用。 我该如何解决这个问题?是否还有其他课程要包括在内? 谢谢

【问题讨论】:

    标签: java cryptography certificate bouncycastle x509


    【解决方案1】:

    使用 Bouncycastle 最新版本 - 1.55 1.66

    更新@Bewusstsein 的答案。自此答案(2017 年 5 月 11 日)起,最新版本不推荐使用 bouncycastle 类。如果您使用的是 1.55 或更高版本:

    public static Certificate selfSign(KeyPair keyPair, String subjectDN) throws OperatorCreationException, CertificateException, IOException
    {
        Provider bcProvider = new BouncyCastleProvider();
        Security.addProvider(bcProvider);
    
        long now = System.currentTimeMillis();
        Date startDate = new Date(now);
    
        X500Name dnName = new X500Name(subjectDN);
        BigInteger certSerialNumber = new BigInteger(Long.toString(now)); // <-- Using the current timestamp as the certificate serial number
    
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(startDate);
        calendar.add(Calendar.YEAR, 1); // <-- 1 Yr validity
    
        Date endDate = calendar.getTime();
    
        String signatureAlgorithm = "SHA256WithRSA"; // <-- Use appropriate signature algorithm based on your keyPair algorithm.
    
        ContentSigner contentSigner = new JcaContentSignerBuilder(signatureAlgorithm).build(keyPair.getPrivate());
    
        JcaX509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(dnName, certSerialNumber, startDate, endDate, dnName, keyPair.getPublic());
    
        // Extensions --------------------------
    
        // Basic Constraints
        BasicConstraints basicConstraints = new BasicConstraints(true); // <-- true for CA, false for EndEntity
    
        certBuilder.addExtension(new ASN1ObjectIdentifier("2.5.29.19"), true, basicConstraints); // Basic Constraints is usually marked as critical.
    
        // -------------------------------------
    
        return new JcaX509CertificateConverter().setProvider(bcProvider).getCertificate(certBuilder.build(contentSigner));
    }
    

    【讨论】:

    • 学习经验:使用SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo.getInstance(keyPair.getPublic().getEncoded()); 否则您的X.509 证书将与Java 密钥库不兼容。问题是公钥的密钥生成算法与签名算法本身不同。好笑,直接尝试AlgorithmIdentifier algorithmIdentifier = new AlgorithmIdentifier(PKCSObjectIdentifiers.rsaEncryption, DERNull.INSTANCE)失败了。
    • 重用了你的代码here。请注意,它会因您的原始代码而失败。尽管如此,感谢您确保我不必自己从基础上解决这个问题。要求对此答案进行投票,希望您能得到更多的代表。
    • @Bodewes 你是对的,我没有通过存储在密钥库中来测试它。感谢您对其进行测试。更正后的代码现在应该可以与 Java 密钥库一起使用。您可以在其他答案中使用更正后的代码。
    【解决方案2】:

    这是 BouncyCastle 用于生成 X.509 证书的代码。 您将需要来自 BC 的 thisthis 库才能使用它。 有关如何使用它的更多详细信息,请查看this question (Main class)。

        public class BCCertGen {
        public static String _country = "Westeros",
                             _organisation = "Targaryen",
                             _location = "Valyria",
                             _state = "Essos",
                             _issuer = "Some Trusted CA";
    
        public BCCertGen(String country, String organisation, String location, String state, String issuer){
            _country = country;
            _organisation = organisation;
            _location = location;
            _state = state;
            _issuer = issuer;
        }
        public static X509Certificate generate(PrivateKey privKey, PublicKey pubKey, int duration, String signAlg, boolean isSelfSigned) throws Exception{
            Provider BC = new BouncyCastleProvider();
    
            // distinguished name table.
            X500NameBuilder builder = createStdBuilder();
    
            // create the certificate
            ContentSigner sigGen = new JcaContentSignerBuilder(signAlg).build(privKey);
            X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(
                    new X500Name("cn="+_issuer),    //Issuer
                    BigInteger.valueOf(1),      //Serial
                    new Date(System.currentTimeMillis() - 50000),   //Valid from
                    new Date((long)(System.currentTimeMillis() + duration*8.65*Math.pow(10,7))),    //Valid to
                    builder.build(),    //Subject
                    pubKey              //Publickey to be associated with the certificate
            );
    
            X509Certificate cert = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certGen.build(sigGen));
    
            cert.checkValidity(new Date());
    
            if (isSelfSigned) {
                // check verifies in general
                cert.verify(pubKey);
                // check verifies with contained key
                cert.verify(cert.getPublicKey());
            }
    
            ByteArrayInputStream bIn = new ByteArrayInputStream(cert.getEncoded());
            CertificateFactory fact = CertificateFactory.getInstance("X.509", BC);
    
            return (X509Certificate) fact.generateCertificate(bIn);
        }
    
        private static X500NameBuilder createStdBuilder() {
            X500NameBuilder builder = new X500NameBuilder(RFC4519Style.INSTANCE);
    
            builder.addRDN(RFC4519Style.c, _country);
            builder.addRDN(RFC4519Style.o, _organisation);
            builder.addRDN(RFC4519Style.l, _location);
            builder.addRDN(RFC4519Style.st, _state);
    
            return builder;
        }
    }
    

    编辑:我不记得我是从哪个 BC 测试中准确获得的,但这里有类似的东西 https://github.com/bcgit/bc-java/blob/master/prov/src/test/java/org/bouncycastle/pqc/jcajce/provider/test/KeyStoreTest.java

    【讨论】:

    • 我刚刚注意到我投了反对票 - 我不知道这是怎么发生的,它一定是点击错误。除非对问题进行了编辑,否则我无法撤消此操作-也许您可以通过评论对其进行编辑,以说明您从何处获取该代码-然后我可以撤消我的否决票。很抱歉,以前从未发生过!
    • @Rodney 没问题,谢谢你的回答,我编辑一下
    【解决方案3】:

    注意:此答案使用带有 11 CVEs 的旧版本库。

    这是我正在使用的(使用 BouncyCastle v1.38):

    import java.math.BigInteger;
    import java.security.InvalidKeyException;
    import java.security.KeyPair;
    import java.security.KeyPairGenerator;
    import java.security.NoSuchAlgorithmException;
    import java.security.NoSuchProviderException;
    import java.security.SecureRandom;
    import java.security.Security;
    import java.security.SignatureException;
    import java.util.Date;
    
    import javax.security.auth.x500.X500Principal;
    
    import java.security.cert.CertificateEncodingException;
    import java.security.cert.X509Certificate;
    
    import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
    import org.bouncycastle.asn1.x509.KeyPurposeId;
    import org.bouncycastle.asn1.x509.X509Extensions;
    import org.bouncycastle.asn1.x509.X509Name;
    import org.bouncycastle.jce.provider.BouncyCastleProvider;
    import org.bouncycastle.x509.X509V3CertificateGenerator;
    
    public class BouncyCastle {
    
        public static void main(String[] args) throws CertificateEncodingException, InvalidKeyException, IllegalStateException, NoSuchProviderException, NoSuchAlgorithmException, SignatureException {
            X509Certificate selfSignedX509Certificate = new BouncyCastle().generateSelfSignedX509Certificate();
            System.out.println(selfSignedX509Certificate);
        }
    
        public X509Certificate generateSelfSignedX509Certificate() throws CertificateEncodingException, InvalidKeyException, IllegalStateException,
                NoSuchProviderException, NoSuchAlgorithmException, SignatureException {
            addBouncyCastleAsSecurityProvider();
    
            // generate a key pair
            KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC");
            keyPairGenerator.initialize(4096, new SecureRandom());
            KeyPair keyPair = keyPairGenerator.generateKeyPair();
    
            // build a certificate generator
            X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
            X500Principal dnName = new X500Principal("cn=example");
    
            // add some options
            certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
            certGen.setSubjectDN(new X509Name("dc=name"));
            certGen.setIssuerDN(dnName); // use the same
            // yesterday
            certGen.setNotBefore(new Date(System.currentTimeMillis() - 24 * 60 * 60 * 1000));
            // in 2 years
            certGen.setNotAfter(new Date(System.currentTimeMillis() + 2 * 365 * 24 * 60 * 60 * 1000));
            certGen.setPublicKey(keyPair.getPublic());
            certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
            certGen.addExtension(X509Extensions.ExtendedKeyUsage, true,
                    new ExtendedKeyUsage(KeyPurposeId.id_kp_timeStamping));
    
            // finally, sign the certificate with the private key of the same KeyPair
            X509Certificate cert = certGen.generate(keyPair.getPrivate(), "BC");
            return cert;
        }
    
        public void addBouncyCastleAsSecurityProvider() {
            Security.addProvider(new BouncyCastleProvider());
        }
    }
    

    要使certGen.generate(keyPair.getPrivate(), "BC"); 工作,必须将 BouncyCastle 添加为安全提供程序。

    我确认它适用于这个 maven 依赖项:

    <dependency>
        <groupId>org.bouncycastle</groupId>
        <artifactId>bcprov-jdk16</artifactId>
        <version>1.38</version>
    </dependency>
    

    【讨论】:

    • 根据证书的预期用途,您可能希望添加更多扩展(使用 addExtension 如答案所示)。 Bouncycastle for Java 的当前 2015 年 3 月版本是 1.52
    • 您可以将此代码转换为 SSCCE 吗?我找不到你使用的依赖项
    • @Dipu Done,我刚刚测试过,它对我有用。
    【解决方案4】:

    这是一个完整的自签名 ECDSA 证书生成器,它创建可用于客户端和服务器端的 TLS 连接的证书。它使用 BouncyCastle 1.57 进行了测试。类似的代码可用于创建 RSA 证书。

    SecureRandom random = new SecureRandom();
    
    // create keypair
    KeyPairGenerator keypairGen = KeyPairGenerator.getInstance("EC");
    keypairGen.initialize(256, random);
    KeyPair keypair = keypairGen.generateKeyPair();
    
    // fill in certificate fields
    X500Name subject = new X500NameBuilder(BCStyle.INSTANCE)
        .addRDN(BCStyle.CN, "stackoverflow.com")
        .build();
    byte[] id = new byte[20];
    random.nextBytes(id);
    BigInteger serial = new BigInteger(160, random);
    X509v3CertificateBuilder certificate = new JcaX509v3CertificateBuilder(
        subject,
        serial,
        Date.from(LocalDate.of(2000, 1, 1).atStartOfDay(ZoneOffset.UTC).toInstant()),
        Date.from(LocalDate.of(2035, 1, 1).atStartOfDay(ZoneOffset.UTC).toInstant()),
        subject,
        keypair.getPublic());
    certificate.addExtension(Extension.subjectKeyIdentifier, false, id);
    certificate.addExtension(Extension.authorityKeyIdentifier, false, id);
    BasicConstraints constraints = new BasicConstraints(true);
    certificate.addExtension(
        Extension.basicConstraints,
        true,
        constraints.getEncoded());
    KeyUsage usage = new KeyUsage(KeyUsage.keyCertSign | KeyUsage.digitalSignature);
    certificate.addExtension(Extension.keyUsage, false, usage.getEncoded());
    ExtendedKeyUsage usageEx = new ExtendedKeyUsage(new KeyPurposeId[] {
        KeyPurposeId.id_kp_serverAuth,
        KeyPurposeId.id_kp_clientAuth
    });
    certificate.addExtension(
        Extension.extendedKeyUsage,
        false,
        usageEx.getEncoded());
    
    // build BouncyCastle certificate
    ContentSigner signer = new JcaContentSignerBuilder("SHA256withECDSA")
        .build(keypair.getPrivate());
    X509CertificateHolder holder = certificate.build(signer);
    
    // convert to JRE certificate
    JcaX509CertificateConverter converter = new JcaX509CertificateConverter();
    converter.setProvider(new BouncyCastleProvider());
    X509Certificate x509 = converter.getCertificate(holder);
    
    // serialize in DER format
    byte[] serialized = x509.getEncoded();
    

    【讨论】:

    • 有效!我不得不用keypairGen.initialize(512, random);替换"EC""RSA",用"SHA256withECDSA"替换"SHA256withRSA"
    • @EvgeniyPhilippov 请不要在没有警告的情况下不要发布不安全的参数(RSA 要求密钥大小为 2048 位或更多)。
    猜你喜欢
    • 1970-01-01
    • 2017-12-04
    • 1970-01-01
    • 2017-08-14
    • 1970-01-01
    • 2014-05-28
    • 2016-10-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多