在c#中使用ECDSA生成证书

时间:2016-04-14 13:08:15

标签: c# certificate ecdsa

我尝试使用ECDSA生成带有私钥的(自签名)证书。 目标是获得相同的" (pkcs12)使用openssl时的证书:

openssl ecparam -genkey -name secp256r1 -out mykey.key
openssl req -new -key mykey.key -out myreq.csr
openssl req -x509 -days 7 -key mykey.key -in myreq.csr -out mycert.crt
openssl pkcs12 -export -out mycert.pfx -inkey mykey.key -in mycert.crt

我已经使用BouncyCastle来帮助我创建基于RSA的证书,因此接下来的步骤或多或少都遵循我用来创建RSA证书的方式。

(请注意,BC前缀用于BouncyCastle的类,MS用于.NET类

1 生成密钥对:私钥和公钥

BC.IAsymmetricCipherKeyPairGenerator bcKpGen = BC.GeneratorUtilities.GetKeyPairGenerator("ECDSA");
bcKpGen.Init(new BC.ECKeyGenerationParameters(BC.SecObjectIdentifiers.SecP256r1, new BC.SecureRandom()));
BC.AsymmetricCipherKeyPair bcSubjKeys = bcKpGen.GenerateKeyPair();

2 使用私钥对公钥进行签名,并附加一些额外数据(主题,有效期等)

BC.X509V3CertificateGenerator bcXgen = new BC.X509V3CertificateGenerator();
// .. set subject, validity period etc
bcXgen.SetPublicKey(bcSubjKeys.Public);
BC.ISignatureFactory bcSigFac = new BC.Asn1SignatureFactory("SHA256WITHECDSA", bcSubjKeys.Private);
BC.X509Certificate bcCert = bcXgen.Generate(bcSigFac);

3 "加入"来自step1的私钥和来自step2的证书,以获取带有私钥的证书。

如果没有私钥我可以使用证书,我可以这样做:

MS.X509Certificate mcCert = new MS.X509Certificate2(bcCert.GetEncoded(), null);

我完成了。

尝试设置私钥时出现问题:

msCert.PrivateKey = ConvertBouncyToNetSomehow(bcSubjKeys.Private)

(请注意,msCert.PrivateKey的类型为MS.AsymmetricAlgorithmbcSubjKeys.Private的类型为BC.ECPrivateKeyParameters

似乎合适的方式是使用MS.ECDsaCng类(继承自MS.AsymmetricAlgorithm),但是:

1 我发现将BC.ECPrivateKeyParameters转换为MS.CngKeyMS.ECDsaCng所需)的唯一方法是通过pkcs8格式:

BC.PrivateKeyInfo bcPKInfo = BC.PrivateKeyInfoFactory.CreatePrivateKeyInfo(bcSubjKeys.Private);
byte[] pkArr = bcPKInfo.GetDerEncoded();
MS.CngKey msPKCng = MS.CngKey.Import(pkArr, MS.CngKeyBlobFormat.Pkcs8PrivateBlob);

但是使用此方法会丢失一些信息,因为msPKCng.AlgorithmGroup的值为"ECDH"bcSubjKeys.Private.AlgorithmName表示"ECDSA"。此外,ECDH密钥不能与MS.ECDsaCng一起使用。

尽管如此..我可以继续MS.ECDiffieHellmanCng而不是请求MS.ECDsaCng if ..

MS.X509Certificate2.set_PrivateKey

2 实施要求对象实现接口MS.ICspAsymmetricAlgorithm。但他们中没有一个(ECDsaCngECDiffieHellmanCng)实现它。

此时似乎必须使用不同的方法(因为MS.ICspAsymmetricAlgorithm条件),例如将证书和私钥导出到pkcs文件并使用X509Certificate2.Import(..)

任何提示? 此致

2 个答案:

答案 0 :(得分:5)

不幸的是,现在不可能直接开箱即用。您可以使用P / Invokes和.NET 4.6.2(当前处于预览状态)完成剩下的工作。或者,通过.NET Core绕道而行,您可以构建一个适用于.NET 4.6.1的PFX。

" ECDSA" vs" ECDH"

Windows CNG库将ECC拆分为ECDSA和ECDH。 ECDSA密钥对象只能用于ECDSA;但是,只要Windows无法确定PFX导入(或PKCS#8导入)期间的使用情况,它就会调用私钥ECDH。为什么?因为Windows让ECDH密钥对象同时执行密钥协商(ECDH)和数字签名(ECDSA),所以ECDH更灵活。

但.NET 4.6.1并不知道。

.NET Core没有此限制(请参阅https://github.com/dotnet/corefx/pull/5850),.NET 4.6.2也删除了限制(每https://github.com/Microsoft/dotnet/blob/master/releases/net462/dotnet462-changes.md#user-content-bcl)。

生成" ECDSA"键,而不是" ECDH"

.NET Core现在在ECDsa上有一个ImportParameters方法。如果可以将BC.ECPrivateKeyProperty对象转换为MS.ECParameters结构,则可以将blob导入ECDsaCng对象。 (务必将其用作命名曲线,而不是显式复制所有曲线参数)。

由于它被有目的地导入ECDsa对象,因此它获得了一个ECDSA密钥,该信息将嵌入到PFX中。

构建PFX(将它们捆绑在一起)

通过一些P / Invoking,您可以说服Windows使用临时密钥构建PFX。虽然.NET无法从证书中访问临时私钥,但如果从PFX加载,它将能够使用它:

[DllImport(Libraries.Crypt32, CharSet = CharSet.Unicode, SetLastError = true)]
private static extern unsafe bool CertSetCertificateContextProperty(IntPtr pCertContext, CertContextPropId dwPropId, CertSetPropertyFlags dwFlags, SafeNCryptKeyHandle pvData);

internal enum CertContextPropId : int
{
    CERT_NCRYPT_KEY_HANDLE_PROP_ID = 78,
}

[Flags]
internal enum CertSetPropertyFlags : int
{
    None = 0,
}

private static X509Certificate2 MateECDsaPrivateKey(
    X509Certificate2 cert,
    CngKey privateKey)
{
    // Make a new certificate instance which isn't tied to the current one
    using (var tmpCert = new X509Certificate2(cert.RawData))
    {
        SafeNCryptKeyHandle keyHandle = privateKey.Handle;

        // Set the ephemeral key handle property
        if (!CertSetCertificateContextProperty(
            tmpCert.Handle,
            CertContextPropId.CERT_NCRYPT_KEY_HANDLE_PROP_ID,
            CertSetPropertyFlags.None,
            keyHandle))
        {
            throw new CryptographicException(Marshal.GetLastWin32Error());
        }

        // You could emit this, if you prefer.
        byte[] pfxBytes = tmpCert.Export(X509ContentType.Pkcs12);

        // Clear the key handle out again to prevent double-free
        keyHandle = new SafeNCryptKeyHandle();

        if (!CertSetCertificateContextProperty(
            tmpCert.Handle,
            CertContextPropId.CERT_NCRYPT_KEY_HANDLE_PROP_ID,
            CertSetPropertyFlags.None,
            keyHandle))
        {
            throw new CryptographicException(Marshal.GetLastWin32Error());
        }

        // Now load a new certificate which has a temporary keyfile on disk.
        // Note: If you don't want exportability here, don't request it.
        var matedCert = new X509Certificate2(pfxBytes, (string)null, X509KeyStorageFlags.Exportable);

        using (ECDsa ecdsa = matedCert.GetECDsaPrivateKey())
        {
            if (ecdsa == null)
            {
                throw new InvalidOperationException("It didn't work");
            }
        }

        return matedCert;
    }
}

您需要.NET 4.6.1(或更高版本)才能访问GetECDsaPrivateKey()。

答案 1 :(得分:0)

通过使用 CertificateRequest 创建自签名 X509 并传递 ECDsa 密钥,我能够使用 MateECDsaPrivateKey。然后使用 Export(X509ContentType.Cert) 导出公钥。然后使用MateECDsaPrivateKey重新连接原来的Private key。

我的用例是创建一个 X509 证书来存储 ECDiffieHellman 所需的密钥。如果密钥不属于 ECDH 算法组,则底层的 Cng 密钥处理不喜欢该密钥。即使您将底层 Cng 密钥类型算法组设置为 ECDH,它也会重置为 ECDsa。 ECDiffieHellmanCng 需要 ECDH

通过使用 MateECDsaPrivateKey,我能够将 X509 公钥重新连接到正确的 ECDH 私钥。这只是 Windows 上的问题,在非 Windows 平台上,底层的 Key 处理不那么挑剔。

我创建了 https://dev.azure.com/ctx-eng-automation-devops/ElipticCurveX509Certificates,它根据操作系统平台运行正确的进程。它还对 X509Certificate2 进行了扩展,以使用正确的方法提取 ECDH 私钥以创建 ECDiffieHellman 对象。

https://www.nuget.org/packages/ElipticCurveX509Certificates/