在c#中使用ECDSA生成证书

我正在尝试使用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) 

(注意,typeof msCert.PrivateKeyMS.AsymmetricAlgorithmMS.AsymmetricAlgorithm的类型是BC.ECPrivateKeyParameters

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

1我发现将BC.ECPrivateKeyParameters转换为MS.CngKeyMS.CngKey所需)的唯一方法是通过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(..)

任何提示? 问候

不幸的是,现在不可能直接开箱即用。 您可以使用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()。