是否可以仅使用 C# 以编程方式生成 X509 证书?
我们尝试使用 C# 和 BouncyCastle 库以编程方式生成 X509 证书(包括私钥)。我们尝试使用 Felix Kollmann 的示例中的一些代码 但证书的私钥部分返回 null。代码和单元测试如下:
using System;
using System.Collections;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;
namespace MyApp
{
public class CertificateGenerator
{
/// <summary>
///
/// </summary>
/// <remarks>Based on <see cref="http://www.fkollmann.de/v2/post/Creating-certificates-using-BouncyCastle.aspx"/></remarks>
/// <param name="subjectName"></param>
/// <returns></returns>
public static byte[] GenerateCertificate(string subjectName)
{
var kpgen = new RsaKeyPairGenerator();
kpgen.Init(new KeyGenerationParameters(new SecureRandom(new CryptoApiRandomGenerator()), 1024));
var kp = kpgen.GenerateKeyPair();
var gen = new X509V3CertificateGenerator();
var certName = new X509Name("CN=" + subjectName);
var serialNo = BigInteger.ProbablePrime(120, new Random());
gen.SetSerialNumber(serialNo);
gen.SetSubjectDN(certName);
gen.SetIssuerDN(certName);
gen.SetNotAfter(DateTime.Now.AddYears(100));
gen.SetNotBefore(DateTime.Now.Subtract(new TimeSpan(7, 0, 0, 0)));
gen.SetSignatureAlgorithm("MD5WithRSA");
gen.SetPublicKey(kp.Public);
gen.AddExtension(
X509Extensions.AuthorityKeyIdentifier.Id,
false,
new AuthorityKeyIdentifier(
SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(kp.Public),
new GeneralNames(new GeneralName(certName)),
serialNo));
gen.AddExtension(
X509Extensions.ExtendedKeyUsage.Id,
false,
new ExtendedKeyUsage(new ArrayList() { new DerObjectIdentifier("1.3.6.1.5.5.7.3.1") }));
var newCert = gen.Generate(kp.Private);
return DotNetUtilities.ToX509Certificate(newCert).Export(System.Security.Cryptography.X509Certificates.X509ContentType.Pkcs12, "password");
}
}
}
单元测试:
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace MyApp
{
[TestClass]
public class CertificateGeneratorTests
{
[TestMethod]
public void GenerateCertificate_Test_ValidCertificate()
{
// Arrange
string subjectName = "test";
// Act
byte[] actual = CertificateGenerator.GenerateCertificate(subjectName);
// Assert
var cert = new X509Certificate2(actual, "password");
Assert.AreEqual("CN=" + subjectName, cert.Subject);
Assert.IsInstanceOfType(cert.PrivateKey, typeof(RSACryptoServiceProvider));
}
}
}
We're trying to generate an X509 certificate (including the private key) programmatically using C# and the BouncyCastle library. We've tried using some of the code from this sample by Felix Kollmann but the private key part of the certificate returns null. Code and unit test are as below:
using System;
using System.Collections;
using Org.BouncyCastle.Asn1;
using Org.BouncyCastle.Asn1.X509;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Crypto.Prng;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.X509;
namespace MyApp
{
public class CertificateGenerator
{
/// <summary>
///
/// </summary>
/// <remarks>Based on <see cref="http://www.fkollmann.de/v2/post/Creating-certificates-using-BouncyCastle.aspx"/></remarks>
/// <param name="subjectName"></param>
/// <returns></returns>
public static byte[] GenerateCertificate(string subjectName)
{
var kpgen = new RsaKeyPairGenerator();
kpgen.Init(new KeyGenerationParameters(new SecureRandom(new CryptoApiRandomGenerator()), 1024));
var kp = kpgen.GenerateKeyPair();
var gen = new X509V3CertificateGenerator();
var certName = new X509Name("CN=" + subjectName);
var serialNo = BigInteger.ProbablePrime(120, new Random());
gen.SetSerialNumber(serialNo);
gen.SetSubjectDN(certName);
gen.SetIssuerDN(certName);
gen.SetNotAfter(DateTime.Now.AddYears(100));
gen.SetNotBefore(DateTime.Now.Subtract(new TimeSpan(7, 0, 0, 0)));
gen.SetSignatureAlgorithm("MD5WithRSA");
gen.SetPublicKey(kp.Public);
gen.AddExtension(
X509Extensions.AuthorityKeyIdentifier.Id,
false,
new AuthorityKeyIdentifier(
SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(kp.Public),
new GeneralNames(new GeneralName(certName)),
serialNo));
gen.AddExtension(
X509Extensions.ExtendedKeyUsage.Id,
false,
new ExtendedKeyUsage(new ArrayList() { new DerObjectIdentifier("1.3.6.1.5.5.7.3.1") }));
var newCert = gen.Generate(kp.Private);
return DotNetUtilities.ToX509Certificate(newCert).Export(System.Security.Cryptography.X509Certificates.X509ContentType.Pkcs12, "password");
}
}
}
Unit test:
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace MyApp
{
[TestClass]
public class CertificateGeneratorTests
{
[TestMethod]
public void GenerateCertificate_Test_ValidCertificate()
{
// Arrange
string subjectName = "test";
// Act
byte[] actual = CertificateGenerator.GenerateCertificate(subjectName);
// Assert
var cert = new X509Certificate2(actual, "password");
Assert.AreEqual("CN=" + subjectName, cert.Subject);
Assert.IsInstanceOfType(cert.PrivateKey, typeof(RSACryptoServiceProvider));
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
需要澄清的是,X.509 证书不包含私钥。证书一词有时被误用来表示证书和私钥的组合,但它们是两个不同的实体。使用证书的全部目的是或多或少公开地发送它们,而不发送必须保密的私钥。
X509Certificate2
对象可能有一个与其关联的私钥(通过其PrivateKey
属性),但这只是此类设计的一部分,以方便使用。在您的第一个 BouncyCastle 代码示例中,
newCert
实际上只是证书和DotNetUtilities.ToX509Certificate(newCert)
仅从证书构建。考虑到 PKCS#12 格式需要存在私钥,我很惊讶以下部分甚至可以工作(考虑到您在不可能知道私钥的证书上调用它):
(
gen.Generate(kp.Private)
使用私钥对证书进行签名,但不会将私钥放入证书中,这是没有意义的。)如果您希望您的方法同时返回证书和私钥你可以要么:
X509Certificate2
对象,您已在其中初始化了PrivateKey
属性byte[]
内容 (就像它是一个文件一样)。 您发送的链接中的第 3 步 (<一个href="http://web.archive.org/web/20100504192226/http://www.fkollmann.de/v2/post/Creating-certificates-using-BouncyCastle.aspx" rel="noreferrer">镜像) 解释了如何构建 PKCS#12 存储。返回 X.509 证书本身的
byte[]
(DER) 结构将不包含私钥。如果您主要关心的问题(根据您的测试用例)是检查证书是否是从 RSA 密钥对构建的,则您可以检查其公钥的类型。
Just to clarify, an X.509 certificate does not contain the private key. The word certificate is sometimes misused to represent the combination of the certificate and the private key, but they are two distinct entities. The whole point of using certificates is to send them more or less openly, without sending the private key, which must be kept secret. An
X509Certificate2
object may have a private key associated with it (via itsPrivateKey
property), but that's only a convenience as part of the design of this class.In your first BouncyCastle code example,
newCert
is really just the certificate andDotNetUtilities.ToX509Certificate(newCert)
is built from the certificate only.Considering that the PKCS#12 format requires the presence of a private key, I'm quite surprised that the following part even works (considering you're calling it on a certificate which can't possibly know the private key):
(
gen.Generate(kp.Private)
signs the certificate using the private key, but doesn't put the private key in the certificate, which wouldn't make sense.)If you want your method to return both the certificate and the private key you could either:
X509Certificate2
object in which you've initialized thePrivateKey
propertybyte[]
content (as if it was a file). Step 3 in the link you've sent (mirror) explains how to build a PKCS#12 store.Returning the
byte[]
(DER) structure for the X.509 certificate itself will not contain the private key.If your main concern (according to your test case) is to check that the certificate was built from an RSA key-pair, you can check the type of its public key instead.
我意识到这是一篇旧帖子,但我发现这些优秀的文章经历了这个过程:
使用 .NET 中的 Bouncy Castle
I realise this is an old post but I found these excellent articles which go through the process:
Using Bouncy Castle from .NET