使用 Bouncycastle 存储 PKCS#12 容器 (pfx)
Store PKCS#12 Container (pfx) with Bouncycastle
我正在努力使用 Xamarin 和 BouncyCastle 创建 pfx 文件。
我有以下设置/规格
- .NET: PCL .Net Framework 4.5.1
- Xamarin: 4.5.0.476
- BouncyCastle:BouncyCastle 签名 1.7.0.1 (NuGet Package)
我想为我的移动客户端生成一个自签名证书,以针对我的服务器进行自我验证。使用 BouncyCastle 创作效果非常好。我的问题是,当我想将证书及其私钥存储为 PKCS#12 (pfx) 容器并从该容器恢复它以发送由它签名的 Web 请求时,抛出异常告诉我 Input data cannot be coded as a valide certificate
。
以下是我创建证书、密钥和存储 pfx 容器的步骤。
创建密钥对:
private AsymmetricCipherKeyPair GenerateKeyPair()
{
var random = new SecureRandom();
var keyGenerationParameter = new KeyGenerationParameters(random, 4096);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameter);
var keyPair = keyPairGenerator.GenerateKeyPair();
return keyPair;
}
创建证书,这将调用创建密钥和创建容器:
public void CreateCertificate()
{
var random = new SecureRandom();
var keyPair = this.GenerateKeyPair();
// generate certificate generator and set public key.
var generator = new X509V3CertificateGenerator();
generator.SetPublicKey(keyPair.Public);
// generate and set serial number
BigInteger serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random);
generator.SetSerialNumber(serialNumber);
// set signing algorithm.
generator.SetSignatureAlgorithm(Core.Constants.SignatureAlgorithmName);
// set name
string fullQualifiedName = $"CN=dummy,O=DummyOrg,OU=Dummy";
var name = new X509Name(fullQualifiedName);
generator.SetSubjectDN(name);
generator.SetIssuerDN(name);
// set valide time
generator.SetNotBefore(DateTime.UtcNow.AddHours(-1));
generator.SetNotAfter(DateTime.UtcNow.AddYears(10));
// add extensions.
var authorityKeyIdentifier = new AuthorityKeyIdentifier(
SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public).GetEncoded(),
new GeneralNames(new GeneralName(name)),
serialNumber);
var subjectKeyIdentifier = new SubjectKeyIdentifier(
SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public).GetEncoded());
generator.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, authorityKeyIdentifier);
generator.AddExtension(X509Extensions.SubjectKeyIdentifier, false, subjectKeyIdentifier);
generator.AddExtension(X509Extensions.BasicConstraints.Id, true, new BasicConstraints(0));
// generate and validate certificate.
var cert = generator.Generate(keyPair.Private, random);
cert.CheckValidity(DateTime.UtcNow);
cert.Verify(keyPair.Public);
// generate pem string
using (var stringWriter = new StringWriter())
{
var writer = new PemWriter(stringWriter);
var pog = new PemObject("CERTIFICATE", cert.GetEncoded());
writer.WriteObject(pog);
// pem value
var value = stringWriter.ToString();
this.StoreCertificate(value);
}
// store the private key
using (var stringWriter = new StringWriter())
{
var writer = new PemWriter(stringWriter);
PrivateKeyInfo info = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private);
var pog = new PemObject("RSA PRIVATE KEY", info.ToAsn1Object().GetEncoded());
writer.WriteObject(pog);
// pem value
var value = stringWriter.ToString();
this.StorePrivateKey(value);
}
try
{
this.CreatePfxFile();
}
catch (Exception ex)
{
throw;
}
}
创建 PFX 容器:
public void CreatePfxFile()
{
var certificate = this.GetCertificate();
var certEntry = new X509CertificateEntry(certificate);
string friendlyName = certificate.SubjectDN.ToString();
var privateKey = this.ReadPrivateKey();
PrivateKeyInfo info = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKey);
byte[] keyBytes = info.ToAsn1Object().GetEncoded();
var store = new Pkcs12StoreBuilder().Build();
var keyEntry = new AsymmetricKeyEntry(privateKey, );
////store.SetCertificateEntry(Core.Constants.CertificateAlias, certEntry);
store.SetKeyEntry(Core.Constants.PrivateKeyAlias, new AsymmetricKeyEntry(privateKey), new X509CertificateEntry[] { certEntry });
MemoryStream stream = new MemoryStream();
store.Save(stream, Core.Constants.Password.ToCharArray(), new SecureRandom());
var pfxBytes = stream.ToArray();
var base64 = Convert.ToBase64String(pfxBytes);
this.StorePfxContainer(base64);
}
方法GetCertificate()
和ReadPrivateKey()
将return一个X509Certificate
和一个AsymmetricKeyParameter
。
StorePfxContainer
方法只会将 base64 字符串存储到设置变量中。
注意:我删除了store.SetCertificateEntry(Core.Constants.CertificateAlias, certEntry);
行,因为它导致pfx容器两次包含证书。
请不要,因为现在我们只是在使用 BouncyCastle 的东西。
为了签署我的请求,我使用了以下代码。参数 cert
将通过从设置中检索 base64 字符串并使用 Convert.FromBase64String(settingsValue)
获取字节来创建。
private void SetCertificate(HttpWebRequest request, byte[] cert)
{
try
{
System.Security.Cryptography.X509Certificates.X509Certificate2 certificate =
new System.Security.Cryptography.X509Certificates.X509Certificate2(cert, Core.Constants.Password); // <-- throws the exception
request.ClientCertificates.Add(certificate);
}
catch (Exception ex)
{
string message = ex.Message;
}
}
现在我们正在使用来自 .Net 的证书 (System.Security.Cryptography.X509Certificates.X509Certificate2)。当我想加载证书时 x509Certifcate2
的构造函数抛出的异常是
Input data cannot be coded as a valide certificate
由 Mono.Security
抛出
我用 openssl 测试了证书和密钥,我也尝试从我的 pem 证书和 pem 私钥生成一个 pfx 容器,也工作正常。只有当我在我的代码中加载证书时,它才会起作用。所以我认为容器的创建有一个我现在还没有弄清楚的错误。
感谢您的帮助。
经过更多的尝试和错误,我弄清楚了我的实施错误是什么。
首先,我认为我遇到的行为是 PCL 的 Bouncy Castle 和 Mono 的组合。
我生成 pfx 容器的新方法如下所示。
private void CreatePfxFile(X509Certificate certificate, AsymmetricKeyParameter privateKey)
{
// create certificate entry
var certEntry = new X509CertificateEntry(certificate);
string friendlyName = certificate.SubjectDN.ToString();
// get bytes of private key.
PrivateKeyInfo keyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKey);
byte[] keyBytes = keyInfo.ToAsn1Object().GetEncoded();
var builder = new Pkcs12StoreBuilder();
builder.SetUseDerEncoding(true);
var store = builder.Build();
// create store entry
store.SetKeyEntry(Core.Constants.PrivateKeyAlias, new AsymmetricKeyEntry(privateKey), new X509CertificateEntry[] { certEntry });
byte[] pfxBytes = null;
var password = Guid.NewGuid().ToString("N");
using (MemoryStream stream = new MemoryStream())
{
store.Save(stream, password.ToCharArray(), new SecureRandom());
pfxBytes = stream.ToArray();
}
var result = Pkcs12Utilities.ConvertToDefiniteLength(pfxBytes);
this.StoreCertificate(Convert.ToBase64String(result));
}
第一个更改是将 MemoryStream
包装成 using
。我不太确定这会改变什么,但它摆脱了内部异常。
第二个更改是使用 Pkcs12StoreBuilder
并设置 SetUseDerEcnoding(true)
并以此为基础建立商店。
第三个变化是使用 Pkcs12Urilities.ConvertToDefiniteLength()
方法为数据设置确定的长度。
进行这些更改后,我的证书可以毫无问题地存储和恢复。
注:
还要确保您没有使用 string.Empty
作为保存容器的密码。我经常看到这是解决容器问题的方法。
我正在努力使用 Xamarin 和 BouncyCastle 创建 pfx 文件。 我有以下设置/规格
- .NET: PCL .Net Framework 4.5.1
- Xamarin: 4.5.0.476
- BouncyCastle:BouncyCastle 签名 1.7.0.1 (NuGet Package)
我想为我的移动客户端生成一个自签名证书,以针对我的服务器进行自我验证。使用 BouncyCastle 创作效果非常好。我的问题是,当我想将证书及其私钥存储为 PKCS#12 (pfx) 容器并从该容器恢复它以发送由它签名的 Web 请求时,抛出异常告诉我 Input data cannot be coded as a valide certificate
。
以下是我创建证书、密钥和存储 pfx 容器的步骤。
创建密钥对:
private AsymmetricCipherKeyPair GenerateKeyPair()
{
var random = new SecureRandom();
var keyGenerationParameter = new KeyGenerationParameters(random, 4096);
var keyPairGenerator = new RsaKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameter);
var keyPair = keyPairGenerator.GenerateKeyPair();
return keyPair;
}
创建证书,这将调用创建密钥和创建容器:
public void CreateCertificate()
{
var random = new SecureRandom();
var keyPair = this.GenerateKeyPair();
// generate certificate generator and set public key.
var generator = new X509V3CertificateGenerator();
generator.SetPublicKey(keyPair.Public);
// generate and set serial number
BigInteger serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random);
generator.SetSerialNumber(serialNumber);
// set signing algorithm.
generator.SetSignatureAlgorithm(Core.Constants.SignatureAlgorithmName);
// set name
string fullQualifiedName = $"CN=dummy,O=DummyOrg,OU=Dummy";
var name = new X509Name(fullQualifiedName);
generator.SetSubjectDN(name);
generator.SetIssuerDN(name);
// set valide time
generator.SetNotBefore(DateTime.UtcNow.AddHours(-1));
generator.SetNotAfter(DateTime.UtcNow.AddYears(10));
// add extensions.
var authorityKeyIdentifier = new AuthorityKeyIdentifier(
SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public).GetEncoded(),
new GeneralNames(new GeneralName(name)),
serialNumber);
var subjectKeyIdentifier = new SubjectKeyIdentifier(
SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(keyPair.Public).GetEncoded());
generator.AddExtension(X509Extensions.AuthorityKeyIdentifier, false, authorityKeyIdentifier);
generator.AddExtension(X509Extensions.SubjectKeyIdentifier, false, subjectKeyIdentifier);
generator.AddExtension(X509Extensions.BasicConstraints.Id, true, new BasicConstraints(0));
// generate and validate certificate.
var cert = generator.Generate(keyPair.Private, random);
cert.CheckValidity(DateTime.UtcNow);
cert.Verify(keyPair.Public);
// generate pem string
using (var stringWriter = new StringWriter())
{
var writer = new PemWriter(stringWriter);
var pog = new PemObject("CERTIFICATE", cert.GetEncoded());
writer.WriteObject(pog);
// pem value
var value = stringWriter.ToString();
this.StoreCertificate(value);
}
// store the private key
using (var stringWriter = new StringWriter())
{
var writer = new PemWriter(stringWriter);
PrivateKeyInfo info = PrivateKeyInfoFactory.CreatePrivateKeyInfo(keyPair.Private);
var pog = new PemObject("RSA PRIVATE KEY", info.ToAsn1Object().GetEncoded());
writer.WriteObject(pog);
// pem value
var value = stringWriter.ToString();
this.StorePrivateKey(value);
}
try
{
this.CreatePfxFile();
}
catch (Exception ex)
{
throw;
}
}
创建 PFX 容器:
public void CreatePfxFile()
{
var certificate = this.GetCertificate();
var certEntry = new X509CertificateEntry(certificate);
string friendlyName = certificate.SubjectDN.ToString();
var privateKey = this.ReadPrivateKey();
PrivateKeyInfo info = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKey);
byte[] keyBytes = info.ToAsn1Object().GetEncoded();
var store = new Pkcs12StoreBuilder().Build();
var keyEntry = new AsymmetricKeyEntry(privateKey, );
////store.SetCertificateEntry(Core.Constants.CertificateAlias, certEntry);
store.SetKeyEntry(Core.Constants.PrivateKeyAlias, new AsymmetricKeyEntry(privateKey), new X509CertificateEntry[] { certEntry });
MemoryStream stream = new MemoryStream();
store.Save(stream, Core.Constants.Password.ToCharArray(), new SecureRandom());
var pfxBytes = stream.ToArray();
var base64 = Convert.ToBase64String(pfxBytes);
this.StorePfxContainer(base64);
}
方法GetCertificate()
和ReadPrivateKey()
将return一个X509Certificate
和一个AsymmetricKeyParameter
。
StorePfxContainer
方法只会将 base64 字符串存储到设置变量中。
注意:我删除了store.SetCertificateEntry(Core.Constants.CertificateAlias, certEntry);
行,因为它导致pfx容器两次包含证书。
请不要,因为现在我们只是在使用 BouncyCastle 的东西。
为了签署我的请求,我使用了以下代码。参数 cert
将通过从设置中检索 base64 字符串并使用 Convert.FromBase64String(settingsValue)
获取字节来创建。
private void SetCertificate(HttpWebRequest request, byte[] cert)
{
try
{
System.Security.Cryptography.X509Certificates.X509Certificate2 certificate =
new System.Security.Cryptography.X509Certificates.X509Certificate2(cert, Core.Constants.Password); // <-- throws the exception
request.ClientCertificates.Add(certificate);
}
catch (Exception ex)
{
string message = ex.Message;
}
}
现在我们正在使用来自 .Net 的证书 (System.Security.Cryptography.X509Certificates.X509Certificate2)。当我想加载证书时 x509Certifcate2
的构造函数抛出的异常是
Input data cannot be coded as a valide certificate
由 Mono.Security
我用 openssl 测试了证书和密钥,我也尝试从我的 pem 证书和 pem 私钥生成一个 pfx 容器,也工作正常。只有当我在我的代码中加载证书时,它才会起作用。所以我认为容器的创建有一个我现在还没有弄清楚的错误。
感谢您的帮助。
经过更多的尝试和错误,我弄清楚了我的实施错误是什么。
首先,我认为我遇到的行为是 PCL 的 Bouncy Castle 和 Mono 的组合。
我生成 pfx 容器的新方法如下所示。
private void CreatePfxFile(X509Certificate certificate, AsymmetricKeyParameter privateKey)
{
// create certificate entry
var certEntry = new X509CertificateEntry(certificate);
string friendlyName = certificate.SubjectDN.ToString();
// get bytes of private key.
PrivateKeyInfo keyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(privateKey);
byte[] keyBytes = keyInfo.ToAsn1Object().GetEncoded();
var builder = new Pkcs12StoreBuilder();
builder.SetUseDerEncoding(true);
var store = builder.Build();
// create store entry
store.SetKeyEntry(Core.Constants.PrivateKeyAlias, new AsymmetricKeyEntry(privateKey), new X509CertificateEntry[] { certEntry });
byte[] pfxBytes = null;
var password = Guid.NewGuid().ToString("N");
using (MemoryStream stream = new MemoryStream())
{
store.Save(stream, password.ToCharArray(), new SecureRandom());
pfxBytes = stream.ToArray();
}
var result = Pkcs12Utilities.ConvertToDefiniteLength(pfxBytes);
this.StoreCertificate(Convert.ToBase64String(result));
}
第一个更改是将 MemoryStream
包装成 using
。我不太确定这会改变什么,但它摆脱了内部异常。
第二个更改是使用 Pkcs12StoreBuilder
并设置 SetUseDerEcnoding(true)
并以此为基础建立商店。
第三个变化是使用 Pkcs12Urilities.ConvertToDefiniteLength()
方法为数据设置确定的长度。
进行这些更改后,我的证书可以毫无问题地存储和恢复。
注:
还要确保您没有使用 string.Empty
作为保存容器的密码。我经常看到这是解决容器问题的方法。