在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.PrivateKey
是MS.AsymmetricAlgorithm
, MS.AsymmetricAlgorithm
的类型是BC.ECPrivateKeyParameters
)
似乎合适的方式是使用MS.ECDsaCng
类(inheritance自MS.AsymmetricAlgorithm
),但是:
1我发现将BC.ECPrivateKeyParameters
转换为MS.CngKey
( MS.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
。 但他们中没有一个( ECDsaCng
, ECDiffieHellmanCng
)实现它。
此时似乎必须使用不同的方法(因为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()。