在 c# 中使用 ECDSA 生成证书

Posted

技术标签:

【中文标题】在 c# 中使用 ECDSA 生成证书【英文标题】:generate certificate using ECDSA in c# 【发布时间】:2016-04-14 13:08:15 【问题描述】:

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

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-key 也不能与MS.ECDsaCng 一起使用。

尽管如此.. 我可以继续使用MS.ECDiffieHellmanCng 而不是请求的MS.ECDsaCng 如果..

2 实现MS.X509Certificate2.set_PrivateKey 需要对象实现接口MS.ICspAsymmetricAlgorithm。但他们中的任何一个(ECDsaCngECDiffieHellmanCng)都没有实现它。

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

有什么提示吗? 问候

【问题讨论】:

【参考方案1】:

很遗憾,目前无法直接开箱即用。您可以使用 P/Invokes 和 .NET 4.6.2(目前为预览版)完成剩下的工作。或者,绕过 .NET Core,您可以构建在 .NET 4.6.1 中工作的 PFX。

“ECDSA”与“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()。

【讨论】:

它没有用 :-( 有什么提示吗?@bartonjs 你有 .net 4.6.1/2 的运行示例 @DanielFisherlennybacon 看起来我有一个 & 错误(和几个错别字)。给定一个属于有效证书 blob 的有效 PKCS8 blob,gist.github.com/bartonjs/fac9ea9108dd184a7a66be77f0af3fef 为我工作。 我去看看。提前致谢! 有效!非常感谢 也可以。可以在这里找到基于 .net4.7 的工作示例:Translating Elliptic Curve parameters (BC to MS)【参考方案2】:

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

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

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

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

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

【讨论】:

以上是关于在 c# 中使用 ECDSA 生成证书的主要内容,如果未能解决你的问题,请参考以下文章

如何使用证书中的公钥检查 ECDSA(在 p-256 上)签名

创建由其他 ECDSA 证书签名的 ECDSA 证书

ECDSA证书可以有RSA签名吗?

为啥 GCP LoadBalancer 不支持 ECDSA 证书?

如何使用椭圆曲线私钥和 ECDSA 算法签署证书?

Openssl 生成ECC证书及密钥