如何在C#中为DSA密钥对创建带有自签名证书的PKCS12 p12文件?

时间:2021-07-25 18:24:52

I need to generate my own DSA keypair and store it as a bundle of private key and certificate in .p12 file using C#.

我需要生成自己的DSA密钥对,并使用C#将其作为一组私钥和证书存储在.p12文件中。

This question

这个问题

How do I create a PKCS12 .p12 file in C#?

如何在C#中创建PKCS12 .p12文件?

seems to be very similar, but it does not help me, unfortunately, because there are some significant differences (RSA vs DSA, etc.)

似乎非常相似,但不幸的是,它对我没有帮助,因为存在一些显着差异(RSA与DSA等)

I am trying to generate the keypair using System.Security.Cryptography.DSACryptoServiceProvider and then to generate a X509 certificate using Bouncy Castle:

我正在尝试使用System.Security.Cryptography.DSACryptoServiceProvider生成密钥对,然后使用Bouncy Castle生成X509证书:

using (DSACryptoServiceProvider csp = new DSACryptoServiceProvider(1024))
{
       privKeyDSA = csp.ExportParameters(true);
       pubKeyDSA = csp.ExportParameters(false);
       var keypair = DotNetUtilities.GetDsaKeyPair(privKeyDSA);

       var gen = new X509V3CertificateGenerator();

       var CN = new X509Name("CN=" + "TEST");
       var SN = BigInteger.ProbablePrime(120, new Random());

       gen.SetSerialNumber(SN);
       gen.SetSubjectDN(CN);
       gen.SetIssuerDN(CN);
       gen.SetNotAfter(DateTime.MaxValue);
       gen.SetNotBefore(DateTime.Now.Subtract(new TimeSpan(7, 0, 0, 0)));
       gen.SetSignatureAlgorithm("sha1WithDSA");
       gen.SetPublicKey(DotNetUtilities.GetDsaPublicKey(pubKeyDSA));
       var newCert = gen.Generate(keypair.Private);


       certificateDSA = new X509Certificate2(DotNetUtilities.ToX509Certificate((Org.BouncyCastle.X509.X509Certificate)newCert));

       certificateDSA.PrivateKey = csp;
       StringBuilder builder = new StringBuilder();

        builder.AppendLine("-----BEGIN CERTIFICATE-----");
        builder.AppendLine(Convert.ToBase64String(certificateDSA.Export(X509ContentType.Cert), Base64FormattingOptions.InsertLineBreaks));
        builder.AppendLine("-----END CERTIFICATE-----");

        string result = builder.ToString();
        byte[] pkcsData = certificateDSA.Export(X509ContentType.Pfx, "changeit");
}

However, the line certificateDSA.PrivateKey = csp; throws a CryptographicUnexpectedOperationException with message: "The public key of the certificate does not match the value specified."

但是,行证书DSA.PrivateKey = csp;抛出CryptographicUnexpectedOperationException并显示消息:“证书的公钥与指定的值不匹配。”

I really do not understand what is happening. What am I doing wrong? Thanks!

我真的不明白发生了什么。我究竟做错了什么?谢谢!

1 个解决方案

#1


2  

Got interested by this and here is my little investigation. When you set PrivateKey of certificateDSA, .NET code does roughly this:

对此感兴趣,这是我的小调查。当您设置certificateDSA的PrivateKey时,.NET代码大致如下:

byte[] numArray1 = ((ICspAsymmetricAlgorithm) certificateDSA.PublicKey.Key).ExportCspBlob(false);
byte[] numArray2 = csp.ExportCspBlob(false);
// And then those two blobs are compared byte by byte

Those blobs are different starting at position 420 (they have length of 444). So something is wrong in csp parameters. It's not easy to compare raw bytes, so let's convert them to readable xml with:

从位置420开始,这些斑点是不同的(它们的长度为444)。所以csp参数有问题。比较原始字节并不容易,所以让我们将它们转换为可读的xml:

var xml1 = certificateDSA.PublicKey.Key.ToXmlString(false);
var xml2 = csp.ToXmlString(false);

What we will get is this:

我们将得到的是:

<DSAKeyValue> <!--this is parameters of cert public key-->
    <P>2arEQPD3/tKm7pJF1y4gN0/4WzSGfkgFwdmtmoUf/gHoXpdBetRH/5j98qo4k1ybePxM4om4y6n9vhxijocMw5LaeQPceGyNOEScWXXrNKAcUsK74klQmiPOoI2qI1zU5v2HrilKmkOELH81U8/Qmmjmg7ouOdOHqlZAxW9Sv8M=</P>
    <Q>lzRdUtp56eZHIgxRemvdHciGIfc=</Q>
    <G>Z/2T+jXvv0ZLswbuMd9DxrHldakJxZ8JNGRf1QzN09B2VO9WYAzUy0S+J8hbYQjP/jzWbmL5LaK57v+MUOmOHzFwNqfVMe9OUglUfF3nN990ur9hp6csu8+vCEQt3EoI8Wmh/b2yqhtKRN6U494vf33WKo1NCNQapB+iWVQ/egQ=</G>
    <Y>ykcPXFIxWvYDDbbY05oD3hD6LsM5rk76FakUY8YiCo8ZwWbMIlQw+v5nOYS9vpQaZAzUqxx9OXIGSTUGItruTARkDqZ0nGKL0r94Zhog1Y0wU2AVKJh8Vjq/dLFyDDGZZsxBZtmI8TDyKGJbZqvzGbdGLhoRxRFmNi1fVsADv+U=</Y>
</DSAKeyValue>

<DSAKeyValue> <!-- this is paramteres of original DSACryptoServiceProvider-->
    <P>2arEQPD3/tKm7pJF1y4gN0/4WzSGfkgFwdmtmoUf/gHoXpdBetRH/5j98qo4k1ybePxM4om4y6n9vhxijocMw5LaeQPceGyNOEScWXXrNKAcUsK74klQmiPOoI2qI1zU5v2HrilKmkOELH81U8/Qmmjmg7ouOdOHqlZAxW9Sv8M=</P>
    <Q>lzRdUtp56eZHIgxRemvdHciGIfc=</Q>
    <G>Z/2T+jXvv0ZLswbuMd9DxrHldakJxZ8JNGRf1QzN09B2VO9WYAzUy0S+J8hbYQjP/jzWbmL5LaK57v+MUOmOHzFwNqfVMe9OUglUfF3nN990ur9hp6csu8+vCEQt3EoI8Wmh/b2yqhtKRN6U494vf33WKo1NCNQapB+iWVQ/egQ=</G>
    <Y>ykcPXFIxWvYDDbbY05oD3hD6LsM5rk76FakUY8YiCo8ZwWbMIlQw+v5nOYS9vpQaZAzUqxx9OXIGSTUGItruTARkDqZ0nGKL0r94Zhog1Y0wU2AVKJh8Vjq/dLFyDDGZZsxBZtmI8TDyKGJbZqvzGbdGLhoRxRFmNi1fVsADv+U=</Y>
    <Seed>1hiZoCQFivF9xDZdQEGue65oObA=</Seed>
    <PgenCounter>Og==</PgenCounter>
</DSAKeyValue>

You see that original DSACryptoServiceProvider includes Seed and PgenCounter, while after generating certificate with Bouncy Castle, certificate's public key does not contain them. Those parameters are optional (in a sense that public key may not contain then), but if they are present, they should be present on both sides (private and public). How can we workaround this? Here is the code:

您看到原始DSACryptoServiceProvider包含Seed和PgenCounter,而在使用Bouncy Castle生成证书后,证书的公钥不包含它们。这些参数是可选的(在某种意义上公钥可能不包含),但如果它们存在,它们应该存在于双方(私人和公共)。我们如何解决这个问题?这是代码:

using (DSACryptoServiceProvider csp = new DSACryptoServiceProvider(1024)) {
            var parameters = csp.ExportParameters(true);                
            var keypair = DotNetUtilities.GetDsaKeyPair(parameters);
            var gen = new X509V3CertificateGenerator();
            var CN = new X509Name("CN=" + "TEST");
            var SN = BigInteger.ProbablePrime(120, new Random());
            gen.SetSerialNumber(SN);
            gen.SetSubjectDN(CN);
            gen.SetIssuerDN(CN);
            gen.SetNotAfter(DateTime.Now.AddDays(1));
            gen.SetNotBefore(DateTime.Now.Subtract(new TimeSpan(7, 0, 0, 0)));
            gen.SetSignatureAlgorithm("sha1WithDSA");
            gen.SetPublicKey(keypair.Public);
            var newCert = gen.Generate(keypair.Private);

            var certificateDSA = new X509Certificate2(DotNetUtilities.ToX509Certificate(newCert));
            // added block
            parameters.Seed = new byte[20];
            unchecked {
                parameters.Counter = (int) 0xFFFFFFFF;
            }
            csp.ImportParameters(parameters);
            // end of added block
            certificateDSA.PrivateKey = csp;
            StringBuilder builder = new StringBuilder();

            builder.AppendLine("-----BEGIN CERTIFICATE-----");
            builder.AppendLine(Convert.ToBase64String(certificateDSA.Export(X509ContentType.Pkcs12), Base64FormattingOptions.InsertLineBreaks));
            builder.AppendLine("-----END CERTIFICATE-----");

            string result = builder.ToString();                
        }

What we do here is after generating everything, but before assigning private key to certificate, we "removing" seed and counter from DSACryptoServiceProvider parameters. This code does not throw errors and completes fine. Maybe there are some caveats in this workaround, but nevertheless it might be useful to futher investigating the issue, even if it not fixes it completely.

我们在这里做的是在生成所有内容之后,但在将私钥分配给证书之前,我们从DSACryptoServiceProvider参数中“删除”种子和计数器。此代码不会抛出错误并完成正常。也许在这个解决方法中有一些警告,但是进一步研究这个问题可能是有用的,即使它没有完全解决它。

#1


2  

Got interested by this and here is my little investigation. When you set PrivateKey of certificateDSA, .NET code does roughly this:

对此感兴趣,这是我的小调查。当您设置certificateDSA的PrivateKey时,.NET代码大致如下:

byte[] numArray1 = ((ICspAsymmetricAlgorithm) certificateDSA.PublicKey.Key).ExportCspBlob(false);
byte[] numArray2 = csp.ExportCspBlob(false);
// And then those two blobs are compared byte by byte

Those blobs are different starting at position 420 (they have length of 444). So something is wrong in csp parameters. It's not easy to compare raw bytes, so let's convert them to readable xml with:

从位置420开始,这些斑点是不同的(它们的长度为444)。所以csp参数有问题。比较原始字节并不容易,所以让我们将它们转换为可读的xml:

var xml1 = certificateDSA.PublicKey.Key.ToXmlString(false);
var xml2 = csp.ToXmlString(false);

What we will get is this:

我们将得到的是:

<DSAKeyValue> <!--this is parameters of cert public key-->
    <P>2arEQPD3/tKm7pJF1y4gN0/4WzSGfkgFwdmtmoUf/gHoXpdBetRH/5j98qo4k1ybePxM4om4y6n9vhxijocMw5LaeQPceGyNOEScWXXrNKAcUsK74klQmiPOoI2qI1zU5v2HrilKmkOELH81U8/Qmmjmg7ouOdOHqlZAxW9Sv8M=</P>
    <Q>lzRdUtp56eZHIgxRemvdHciGIfc=</Q>
    <G>Z/2T+jXvv0ZLswbuMd9DxrHldakJxZ8JNGRf1QzN09B2VO9WYAzUy0S+J8hbYQjP/jzWbmL5LaK57v+MUOmOHzFwNqfVMe9OUglUfF3nN990ur9hp6csu8+vCEQt3EoI8Wmh/b2yqhtKRN6U494vf33WKo1NCNQapB+iWVQ/egQ=</G>
    <Y>ykcPXFIxWvYDDbbY05oD3hD6LsM5rk76FakUY8YiCo8ZwWbMIlQw+v5nOYS9vpQaZAzUqxx9OXIGSTUGItruTARkDqZ0nGKL0r94Zhog1Y0wU2AVKJh8Vjq/dLFyDDGZZsxBZtmI8TDyKGJbZqvzGbdGLhoRxRFmNi1fVsADv+U=</Y>
</DSAKeyValue>

<DSAKeyValue> <!-- this is paramteres of original DSACryptoServiceProvider-->
    <P>2arEQPD3/tKm7pJF1y4gN0/4WzSGfkgFwdmtmoUf/gHoXpdBetRH/5j98qo4k1ybePxM4om4y6n9vhxijocMw5LaeQPceGyNOEScWXXrNKAcUsK74klQmiPOoI2qI1zU5v2HrilKmkOELH81U8/Qmmjmg7ouOdOHqlZAxW9Sv8M=</P>
    <Q>lzRdUtp56eZHIgxRemvdHciGIfc=</Q>
    <G>Z/2T+jXvv0ZLswbuMd9DxrHldakJxZ8JNGRf1QzN09B2VO9WYAzUy0S+J8hbYQjP/jzWbmL5LaK57v+MUOmOHzFwNqfVMe9OUglUfF3nN990ur9hp6csu8+vCEQt3EoI8Wmh/b2yqhtKRN6U494vf33WKo1NCNQapB+iWVQ/egQ=</G>
    <Y>ykcPXFIxWvYDDbbY05oD3hD6LsM5rk76FakUY8YiCo8ZwWbMIlQw+v5nOYS9vpQaZAzUqxx9OXIGSTUGItruTARkDqZ0nGKL0r94Zhog1Y0wU2AVKJh8Vjq/dLFyDDGZZsxBZtmI8TDyKGJbZqvzGbdGLhoRxRFmNi1fVsADv+U=</Y>
    <Seed>1hiZoCQFivF9xDZdQEGue65oObA=</Seed>
    <PgenCounter>Og==</PgenCounter>
</DSAKeyValue>

You see that original DSACryptoServiceProvider includes Seed and PgenCounter, while after generating certificate with Bouncy Castle, certificate's public key does not contain them. Those parameters are optional (in a sense that public key may not contain then), but if they are present, they should be present on both sides (private and public). How can we workaround this? Here is the code:

您看到原始DSACryptoServiceProvider包含Seed和PgenCounter,而在使用Bouncy Castle生成证书后,证书的公钥不包含它们。这些参数是可选的(在某种意义上公钥可能不包含),但如果它们存在,它们应该存在于双方(私人和公共)。我们如何解决这个问题?这是代码:

using (DSACryptoServiceProvider csp = new DSACryptoServiceProvider(1024)) {
            var parameters = csp.ExportParameters(true);                
            var keypair = DotNetUtilities.GetDsaKeyPair(parameters);
            var gen = new X509V3CertificateGenerator();
            var CN = new X509Name("CN=" + "TEST");
            var SN = BigInteger.ProbablePrime(120, new Random());
            gen.SetSerialNumber(SN);
            gen.SetSubjectDN(CN);
            gen.SetIssuerDN(CN);
            gen.SetNotAfter(DateTime.Now.AddDays(1));
            gen.SetNotBefore(DateTime.Now.Subtract(new TimeSpan(7, 0, 0, 0)));
            gen.SetSignatureAlgorithm("sha1WithDSA");
            gen.SetPublicKey(keypair.Public);
            var newCert = gen.Generate(keypair.Private);

            var certificateDSA = new X509Certificate2(DotNetUtilities.ToX509Certificate(newCert));
            // added block
            parameters.Seed = new byte[20];
            unchecked {
                parameters.Counter = (int) 0xFFFFFFFF;
            }
            csp.ImportParameters(parameters);
            // end of added block
            certificateDSA.PrivateKey = csp;
            StringBuilder builder = new StringBuilder();

            builder.AppendLine("-----BEGIN CERTIFICATE-----");
            builder.AppendLine(Convert.ToBase64String(certificateDSA.Export(X509ContentType.Pkcs12), Base64FormattingOptions.InsertLineBreaks));
            builder.AppendLine("-----END CERTIFICATE-----");

            string result = builder.ToString();                
        }

What we do here is after generating everything, but before assigning private key to certificate, we "removing" seed and counter from DSACryptoServiceProvider parameters. This code does not throw errors and completes fine. Maybe there are some caveats in this workaround, but nevertheless it might be useful to futher investigating the issue, even if it not fixes it completely.

我们在这里做的是在生成所有内容之后,但在将私钥分配给证书之前,我们从DSACryptoServiceProvider参数中“删除”种子和计数器。此代码不会抛出错误并完成正常。也许在这个解决方法中有一些警告,但是进一步研究这个问题可能是有用的,即使它没有完全解决它。