在.NET中将私钥与X509Certificate2类关联

40

我正在编写一些代码,用于创建X509证书和公钥/私钥对。将公钥添加到证书中,并将其发送给CA进行签名。

然后通过System.Security.Cryptography.X509Certificates.X509Certificate2类访问返回的证书。现在我想使用此证书与其他客户端建立安全连接。因此,我使用SslStream类。为了开始SSL握手,我使用以下方法:

server.AssociatedSslStream.AuthenticateAsServer(
                        MyCertificate,                      // Client Certificate
                        true,                               // Require Certificate from connecting Peer
                        SslProtocols.Tls,                   // Use TLS 1.0
                        false                               // check Certificate revocation
                    );

这种方法需要将私钥与证书关联。当然,由CA返回的证书不包含私钥。但它作为.key文件存储在硬盘上。X509Certificate2类有一个名为PrivateKey的属性,我猜它会将私钥与证书关联起来,但我找不到设置此属性的方法。

有没有办法将私钥与.NET X509类关联起来?


这个回答解决了你的问题吗?在HttpClient C#中提供EC私钥以用于证书 - Jim G.
6个回答

52

您可以将私钥与证书存储在 pfx/pkcs#12 文件中,这样就避免了复制粘贴所有代码的麻烦:

openssl pkcs12 -export -in my.cer -inkey my.key -out mycert.pfx

你需要提供一个密码,将其传递给X509Certificate2的构造函数:

X509Certificate2 cert = new X509Certificate2("mycert.pfx","password");

这真是救了我!我创建了PFX文件,但没有意识到我还需要将该密码提供给构造函数。 - Dan Hastings
非常出色的答案!我已经使用它来避免通过MMC插件注册证书的麻烦。 - Sean
3
您可以通过在openSSL命令中添加"-passout pass:"来避免使用密码。 - Nate Hammond
这应该被标记为真正的答案,因为这是做到它的真正方式。 - Chris Jensen
我在哪里可以获取OpenSSL? - user1034912

31

对于其他遇到同样问题的人,我找到了一个非常棒的代码片段,可以让你完美地实现这一点:

http://www.codeproject.com/Articles/162194/Certificates-to-DB-and-Back

byte[] certBuffer = Helpers.GetBytesFromPEM(publicCert, PemStringType.Certificate);
byte[] keyBuffer  = Helpers.GetBytesFromPEM(privateKey, PemStringType.RsaPrivateKey);

X509Certificate2 certificate = new X509Certificate2(certBuffer, password);

RSACryptoServiceProvider prov = Crypto.DecodeRsaPrivateKey(keyBuffer);
certificate.PrivateKey = prov;

编辑: Helper 方法的代码如下(否则需要codeproject登录):

public static byte[] GetBytesFromPEM(string pemString, PemStringType type)
{
    string header; string footer;
    switch (type)
    {
        case PemStringType.Certificate:
            header = "-----BEGIN CERTIFICATE-----";
            footer = "-----END CERTIFICATE-----";
            break;
        case PemStringType.RsaPrivateKey:
            header = "-----BEGIN RSA PRIVATE KEY-----";
            footer = "-----END RSA PRIVATE KEY-----";
            break;
        default:
            return null;
    }

    int start = pemString.IndexOf(header) + header.Length;
    int end = pemString.IndexOf(footer, start) - start;
    return Convert.FromBase64String(pemString.Substring(start, end));
}

更新

从.NET 5开始,您可以简单地使用CreateFromPem(ReadOnlySpan, ReadOnlySpan)

从RFC 7468 PEM编码的证书和私钥内容创建新的X509证书。

示例:

X509Certificate2 cert = X509Certificate2.CreateFromPem(
                certPem, // The text of the PEM-encoded X509 certificate.
                keyPem // The text of the PEM-encoded private key.
            );

或者,如果您有一个包含证书和私钥的字符串,您可以将其同时传递给cert参数和key参数:

X509Certificate2 cert = X509Certificate2.CreateFromPem(
                certPem, // The text of the PEM-encoded X509 certificate.
                keyPem // The text of the PEM-encoded private key.
            );

1
这是一种比将私钥和证书捆绑在一起更好的方式,我已经使用了这个选项并发现它是一个更优越的选择。 - Alejandro
1
如果您能够友好地向我们解释一下这种优越性的本质,我们将不胜感激。 - sschober
2
请包含辅助方法的代码,因为在外部网站上创建用户以获取访问权限非常麻烦。 - Poul K. Sørensen
11
想要使用这个,但是(当然)没有加密类(在System.Web.Helpers中有一个 - 很确定不是它...)。这里缺少了一个 using 吗? - Traderhut Games
6
在尝试在 .NET Core 2 控制台应用程序中设置证书的私钥时,会引发一个“PlatformNotSupportedException”异常。 - Peter Waher
显示剩余4条评论

9

我的解决方案

 byte[] PublicCertificate = Encoding.Unicode.GetBytes("-----BEGIN CERTIFICATE----- ... -----END CERTIFICATE-----");
 var publicCertificate = new X509Certificate2(PublicCertificate );
 byte[] PrivateKey = Convert.FromBase64String("MIIEvQIBA...=");
 using var rsa = RSA.Create();
 rsa.ImportPkcs8PrivateKey(PrivateKey, out _);
 publicCertificate = publicCertificate.CopyWithPrivateKey(rsa);
 publicCertificate = new X509Certificate2(publicCertificate.Export(X509ContentType.Pkcs12));

 var client = new RestClient("api_url");
 client.ClientCertificates = new X509Certificate2Collection();
 client.ClientCertificates.Add(publicCertificate);

1
ImportPkcs8PrivateKey 是 .NET Core 的函数,不在 .NET Framework 4.8 中可用? - Jaans
@Jaans https://learn.microsoft.com/pt-br/dotnet/api/system.security.cryptography.rsa.importpkcs8privatekey?view=net-5.0 - lin
2
@lin,您的解决方案帮助我消除了我的https://stackoverflow.com/q/71343850/7038630错误。我所做的唯一更改是将“ImportPkcs8PrivateKey”替换为“ImportRSAPrivateKey”。这里https://dev59.com/hMLra4cB1Zd3GeqPTd_Z#70132607是我更改“ImportPkcs8PrivateKey”为“ImportRSAPrivateKey”的答案。 - lukaszFD


1

对于.NET Framework 4.8版本

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;

...

private X509Certificate2 GetCert(string clientCertFile, string privateKeyFile) {
    var tempCertificate = new X509Certificate2(clientCertFile);

    StreamReader reader = new StreamReader(privateKeyFile);
    PemReader pemReader = new PemReader(reader);
    AsymmetricCipherKeyPair keyPair = (AsymmetricCipherKeyPair)pemReader.ReadObject();
    AsymmetricKeyParameter privateKey = keyPair.Private;
    RSA rsa = DotNetUtilities.ToRSA((RsaPrivateCrtKeyParameters) privateKey);
            
    tempCertificate = tempCertificate.CopyWithPrivateKey(rsa);

    return new X509Certificate2(tempCertificate.Export(X509ContentType.Pkcs12));
}

0

感谢Lee Taylor和Neano,我的.NET 4.5解决方案得以完成。

using Org.BouncyCastle.OpenSsl;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto.Parameters;
using System.IO;
using System.Security.Cryptography.X509Certificates;

private X509Certificate2 GetCert(string  certPath,string keyPath)
{
  X509Certificate2 cert = new X509Certificate2(certPath);
  StreamReader reader = new StreamReader(keyPath);
  PemReader pemReader = new PemReader(reader);
  RsaPrivateCrtKeyParameters keyPair=(RsaPrivateCrtKeyParameters)pemReader.ReadObject();
  RSA rsa = DotNetUtilities.ToRSA(keyPair);
  cert.PrivateKey = rsa;
  return new X509Certificate2(cert.Export(X509ContentType.Pfx));
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接