问题描述
我正在尝试使用ECDSA生成具有私钥的(自签名)证书。
目标是获得与使用openssl时相同的证书(pkcs12):
openssl ecparam -genkey -name secp256r1 -out mykey.key
openssl req-新-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();
// ..设置主题,有效期等
bcXgen.SetPublicKey(bcSubjKeys.Public);
BC.ISignatureFactory bcSigFac =新BC.Asn1SignatureFactory( SHA256WITHECDSA,bcSubjKeys.Private);
BC.X509Certificate bcCert = bcXgen.Generate(bcSigFac);
3 从步骤1加入私钥和从步骤2获得证书
如果我可以使用没有私钥的证书,则可以执行以下操作:
MS.X509Certificate mcCert =新的MS.X509Certificate2(bcCert.GetEncoded(),空);
我完成了。
尝试设置私钥时出现问题:
msCert.PrivateKey = ConvertBouncyToNetSomehow(bcSubjKeys.Private)
(请注意, msCert.PrivateKey
的类型为 MS.AsymmetricAlgorithm
和 bcSubjKeys.Private
的类型为 BC.ECPrivateKeyParameters
)
似乎合适的方法是使用 MS.ECDsaCng
类(继承自 MS.AsymmetricAlgorithm
),但是:
1 是我发现转换 BC.ECPrivateKeyParameters 到 MS.CngKey
( MS.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
。但是他们两个( 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并不知道。 / p>
.NET Core没有此限制(请参见),. NET 4.6.2也删除了该限制(根据)。
生成 ECDSA键,而不是 ECDH
.NET Core现在具有方法。如果可以将BC.ECPrivateKeyProperty对象转换为MS.ECParameters结构,则可以将Blob导入ECDsaCng对象。 (确保将其用作命名曲线,而不是显式复制所有曲线参数。)
由于有意将其导入到ECDsa对象中,因此它获得了ECDSA密钥,并且信息将被嵌入到PFX中。
构建PFX(将它们捆绑在一起)
使用一些P /调用,您可以说服Windows使用临时密钥构建PFX。尽管.NET无法从证书访问临时私钥,但如果从PFX加载它,它将能够使用它。
[DllImport (Libraries.Crypt32,CharSet = CharSet.Unicode,SetLastError = true)]
私有静态外部不安全布尔CertSetCertificateContextProperty(IntPtr pCertContext,CertContextPropId dwPropId,CertSetPropertyFlags dwFlags,SafeNCryptKeyHandle pv)
内部枚举CertContextPropId:int
{
CERT_NCRYPT_KEY_HANDLE_PROP_ID = 78,
}
[Flags]
内部枚举CertSetPropertyFlags: int
{
None = 0,
}
private static X509Certificate2 MateECDsaPrivateKey(
X509Certificate2 cert,
CngKey privateKey)
{
//使用(var tmpCert = new X509Certificate2(cert.RawData))创建一个新证书实例,该实例不与当前
绑定。
{
SafeNCryptKeyHandle keyHandle = privateKey.Handle;
//设置临时密钥句柄属性
if(!CertSetCertificateContextProperty(
tmpCert.Handle,
CertContextPropId.CERT_NCRYPT_KEY_HANDLE_PROP_ID,
CertSetPropertyFlags.None,$
keyHandle))
{
抛出new CryptographicException(Marshal.GetLastWin32Error());
}
//如果愿意,可以发出此信号。
byte [] pfxBytes = tmpCert.Export(X509ContentType.Pkcs12);
//再次清除密钥句柄,以防止双重释放
keyHandle = new SafeNCryptKeyHandle();
if(!CertSetCertificateContextProperty(
tmpCert.Handle,
CertContextPropId.CERT_NCRYPT_KEY_HANDLE_PROP_ID,
CertSetPropertyFlags.None,
keyHandle))
{
抛出新的CryptographicException(Marshal.GetLastWin32Error());
}
//现在加载新证书,该证书在磁盘上具有临时密钥文件。
//注意:如果您不希望在此处导出,请不要提出要求。
var matedCert = new X509Certificate2(pfxBytes,(string)null,X509KeyStorageFlags.Exportable);
使用(ECDsa ecdsa = matedCert.GetECDsaPrivateKey())
{
if(ecdsa == null)
{
抛出新的InvalidOperationException(它不起作用);
}
}
返回matedCert;
}
}
您需要.NET 4.6.1(或更高版本)才能拥有访问GetECDsaPrivateKey()。
I'm trying to generate (self-signed) certificate with private key using ECDSA.The goals is to get "the same" (pkcs12) certificate as when using 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
I already use BouncyCastle to help me with creating RSA-based certificate(s), so next steps more or less follow the way I use to create RSA certs.
(note that BC
prefix is used for classes from BouncyCastle, MS
for .NET classes)
1 generate key pair: private and public keys
BC.IAsymmetricCipherKeyPairGenerator bcKpGen = BC.GeneratorUtilities.GetKeyPairGenerator("ECDSA");
bcKpGen.Init(new BC.ECKeyGenerationParameters(BC.SecObjectIdentifiers.SecP256r1, new BC.SecureRandom()));
BC.AsymmetricCipherKeyPair bcSubjKeys = bcKpGen.GenerateKeyPair();
2 use private key to sign public key with some additional data (subject, validity period etc)
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 "join" private key from step1 and certificate from step2 to get certificate with private key.
If I'm ok with certificate without private key, I could do something like:
MS.X509Certificate mcCert = new MS.X509Certificate2(bcCert.GetEncoded(), null);
and I'm done.
The issue(s) come when trying to set private-key:
msCert.PrivateKey = ConvertBouncyToNetSomehow(bcSubjKeys.Private)
(note that typeof msCert.PrivateKey
is MS.AsymmetricAlgorithm
and the type of bcSubjKeys.Private
is BC.ECPrivateKeyParameters
)
It seems that suitable way is using MS.ECDsaCng
class (which inherits from MS.AsymmetricAlgorithm
), but:
1 The only way I found to convert BC.ECPrivateKeyParameters
to MS.CngKey
(required by MS.ECDsaCng
) is via pkcs8 format:
BC.PrivateKeyInfo bcPKInfo = BC.PrivateKeyInfoFactory.CreatePrivateKeyInfo(bcSubjKeys.Private);
byte[] pkArr = bcPKInfo.GetDerEncoded();
MS.CngKey msPKCng = MS.CngKey.Import(pkArr, MS.CngKeyBlobFormat.Pkcs8PrivateBlob);
but using this approach some information is lost because value of msPKCng.AlgorithmGroup
is "ECDH"
while bcSubjKeys.Private.AlgorithmName
says "ECDSA"
. Also ECDH-key cannot be used with MS.ECDsaCng
.
Nevertheless.. I could continue with MS.ECDiffieHellmanCng
instead of requested MS.ECDsaCng
if..
2 implementation of MS.X509Certificate2.set_PrivateKey
requires the object implements interface MS.ICspAsymmetricAlgorithm
. But neither one of them (ECDsaCng
, ECDiffieHellmanCng
) implement it.
At this point it seems different approach must be used (because of MS.ICspAsymmetricAlgorithm
condition), e.g. export certificate and private key to pkcs file and use X509Certificate2.Import(..)
.
Any hint?Regards
Unfortunately, it's not possible to do straight out of the box right now. You can get the rest of the way with P/Invokes and .NET 4.6.2 (currently in preview). Or, with a detour through .NET Core you can build a PFX that works in .NET 4.6.1.
"ECDSA" vs "ECDH"
The Windows CNG libraries split ECC into ECDSA and ECDH. ECDSA key objects can only be used for ECDSA; but whenever Windows can't determine the usage during a PFX import (or PKCS#8 import) it calls a private key ECDH. Why? Because Windows lets ECDH key objects do both key agreement (ECDH) and digital signature (ECDSA), so ECDH is more flexible.
But .NET 4.6.1 didn't know that.
.NET Core doesn't have this limitation (see https://github.com/dotnet/corefx/pull/5850), and .NET 4.6.2 has also removed the restriction (per https://github.com/Microsoft/dotnet/blob/master/releases/net462/dotnet462-changes.md#user-content-bcl).
Generating "ECDSA" keys, instead of "ECDH"
.NET Core now has an ImportParameters method on ECDsa. If you can translate the BC.ECPrivateKeyProperty object to an MS.ECParameters structure you can import the blob into an ECDsaCng object. (Be sure to use it as a named curve, instead of explicitly copying all of the curve parameters).
Since it was purposefully imported into an ECDsa object it gets an ECDSA key, and that information will be embedded in the PFX.
Building the PFX (tying it all together)
With a bit of P/Invoking you can convince Windows to build a PFX using an ephemeral key. While .NET can't access ephemeral private keys from certificates, it will be able to make use of it if loaded from a 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;
}
}
You'll need .NET 4.6.1 (or newer) to have access to GetECDsaPrivateKey().
这篇关于在C#中使用ECDSA生成证书的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!