使用 ECDSA x509 证书的 Diffie Hellman 密钥交换
Diffie Hellman Key Exchange using ECDSA x509 certificates
我正在尝试使用 2 个 ECDSA x509 证书执行 Diffie-Hellman 密钥交换。
这是我从证书中提取密钥以计算派生密钥的方法。
private byte[] GetDerivedKey(X509Certificate2 publicCertificate, X509Certificate2 privateCertificate)
{
byte[] derivedKey;
using (var privateKey = privateCertificate.GetECDsaPrivateKey())
using (var publicKey = publicCertificate.GetECDsaPublicKey())
{
var privateParams = privateKey.ExportParameters(true); //This line is failing
var publicParams = publicKey.ExportParameters(false);
using (var privateCng = ECDiffieHellmanCng.Create(privateParams))
using (var publicCng = ECDiffieHellmanCng.Create(publicParams))
{
derivedKey = privateCng.DeriveKeyMaterial(publicCng.PublicKey);
}
}
return derivedKey;
}
我评论了失败的行 privateKey.ExportParameters(true)
错误:
System.Security.Cryptography.CryptographicException : The requested operation is not supported.
at System.Security.Cryptography.NCryptNative.ExportKey(SafeNCryptKeyHandle key, String format)
at System.Security.Cryptography.CngKey.Export(CngKeyBlobFormat format)
at System.Security.Cryptography.ECCng.ExportParameters(CngKey key, Boolean includePrivateParameters, ECParameters& ecparams)
at System.Security.Cryptography.ECDsaCng.ExportParameters(Boolean includePrivateParameters)
因为这是我生成的自签名证书,所以我认为我做错了什么。
我首先创建一个根 CA 证书并传入私钥来签署我的证书。
private X509Certificate2 CreateECSDACertificate(string certificateName,
string issuerCertificateName,
TimeSpan lifetime,
AsymmetricKeyParameter issuerPrivateKey,
string certificateFriendlyName = null)
{
// Generating Random Numbers
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);
var signatureFactory = new Asn1SignatureFactory("SHA256WithECDSA", issuerPrivateKey, random);
// The Certificate Generator
var certificateGenerator = new X509V3CertificateGenerator();
// Serial Number
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);
certificateGenerator.SetSerialNumber(serialNumber);
// Issuer and Subject Name
var subjectDistinguishedName = new X509Name($"CN={certificateName}");
var issuerDistinguishedName = new X509Name($"CN={issuerCertificateName}");
certificateGenerator.SetSubjectDN(subjectDistinguishedName);
certificateGenerator.SetIssuerDN(issuerDistinguishedName);
// Valid For
var notBefore = DateTime.UtcNow.Date;
var notAfter = notBefore.Add(lifetime);
certificateGenerator.SetNotBefore(notBefore);
certificateGenerator.SetNotAfter(notAfter);
//key generation
var keyGenerationParameters = new KeyGenerationParameters(random, _keyStrength);
var keyPairGenerator = new ECKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
var certificate = certificateGenerator.Generate(signatureFactory);
var store = new Pkcs12Store();
var certificateEntry = new X509CertificateEntry(certificate);
store.SetCertificateEntry(certificateName, certificateEntry);
store.SetKeyEntry(certificateName, new AsymmetricKeyEntry(subjectKeyPair.Private), new[] { certificateEntry });
X509Certificate2 x509;
using (var pfxStream = new MemoryStream())
{
store.Save(pfxStream, null, new SecureRandom());
pfxStream.Seek(0, SeekOrigin.Begin);
x509 = new X509Certificate2(pfxStream.ToArray());
}
x509.FriendlyName = certificateFriendlyName;
return x509;
}
.HasPrivateKey()
方法 return 是正确的,我读过它可以 return 误报。
当我将我的证书添加到商店时,我可以验证证书链。
[Test]
public void CreateSelfSignedCertificate_AfterAddingToStore_CanBuildChain()
{
var result = _target.CreateSelfSignedCertificate(_subject, _issuer, TimeSpan.FromDays(356), _certificateFriendlyName, _issuerFriendlyName);
_store.TryAddCertificateToStore(result.CertificateAuthority, _caStoreName, _location);
_store.TryAddCertificateToStore(result.Certificate, _certStoreName, _location);
var chain = new X509Chain
{
ChainPolicy =
{
RevocationMode = X509RevocationMode.NoCheck
}
};
var chainBuilt = chain.Build(result.Certificate);
if (!chainBuilt)
{
foreach (var status in chain.ChainStatus)
{
Assert.Warn(string.Format("Chain error: {0} {1}", status.Status, status.StatusInformation));
}
}
Assert.IsTrue(chainBuilt, "Chain");
}
起初我以为私有证书可能必须来自证书存储,所以我导入它然后将其拉回,但我得到了同样的错误,这是我相信我不是的另一个原因做的很对。
编辑:
我有另一个 class 使用相同的代码生成 RSA x509,将私钥放入证书中。它允许我导出 RSA 私钥。
变量_keyStrength
是384,我的签名工厂用的是"SHA256withECDSA"
。我也尝试过使用 "SHA384withECDSA"
但我得到了同样的错误。
好的。这是一个盲目射击,但在查看您的代码后,我注意到两件事:
- 当您创建 PFX 时,您设置了空密码。但是当您将 PFX 加载到
X509Certificate2
class 时,您使用了错误的构造函数。你应该使用一个带有密码参数的,并给它一个空
- 当您将 PFX 加载到
X509Certificate2
class 时,您没有指定私钥是否应该是可导出的。我认为这就是 privateKey.ExportParameters(true)
给你例外的原因。您应该使用 this 构造函数并将 null
指定为密码
成功了
我认为这是一个错误。有可能是这样。我们在 X509Constructor 中明确说明私钥应该是可导出的。我也使用了 X509KeyStorageFlags.EphemeralKeySet | X509KeyStorageFlags.Exportable
标志。但是当我查看 CngKey 时,它的 ExportPolicy 设置为 AllowExport
而不是 AllowPlaintextExport
。
它在某种程度上是可以导出的。 privateKey.Key.Export(CngKeyBlobFormat.OpaqueTransportBlob)
成功了。但是 privateKey.ExportParameters(true)
没有。
我已经搜索了如何更改 CngKey 的 ExportPolicy 的解决方案。我发现 帮助我改变了它。之后 ExportParameters
起作用了。
你的 GetDerivedKey
方法的固定版本是
private byte[] GetDerivedKey(X509Certificate2 publicCertificate, X509Certificate2 privateCertificate)
{
byte[] derivedKey;
using (var privateKey = privateCertificate.GetECDsaPrivateKey())
using (var publicKey = privateCertificate.GetECDsaPublicKey())
{
var myPrivateKeyToMessWith = privateKey as ECDsaCng;
// start - taken from
// make private key exportable:
byte[] bytes = BitConverter.GetBytes((int)(CngExportPolicies.AllowExport | CngExportPolicies.AllowPlaintextExport));
CngProperty pty = new CngProperty(NCryptExportPolicyProperty, bytes, CngPropertyOptions.Persist);
myPrivateKeyToMessWith.Key.SetProperty(pty);
// end - taken from
var privateParams = myPrivateKeyToMessWith.ExportParameters(true); //This line is NOT failing anymore
var publicParams = publicKey.ExportParameters(false);
using (var privateCng = ECDiffieHellmanCng.Create(privateParams))
using (var publicCng = ECDiffieHellmanCng.Create(publicParams))
{
derivedKey = privateCng.DeriveKeyMaterial(publicCng.PublicKey);
}
}
return derivedKey;
}
我开始使用@pepo 发布的解决方案,它让我发现 'GetECDsaPrivateKey' 不是 return 一个 ECDsa
对象,而是一个 ECDsaCng
。我将密钥推导简化为这个。
byte[] derivedKey;
using (var privateKey = (ECDsaCng)certificate.GetECDsaPrivateKey())
using (var publicKey = (ECDsaCng)certificate.GetECDsaPublicKey())
{
var publicParams = publicKey.ExportParameters(false);
using (var publicCng = ECDiffieHellmanCng.Create(publicParams))
using (var diffieHellman = new ECDiffieHellmanCng(privateKey.Key))
{
derivedKey = diffieHellman.DeriveKeyMaterial(publicCng.PublicKey);
}
}
return derivedKey;
我正在尝试使用 2 个 ECDSA x509 证书执行 Diffie-Hellman 密钥交换。
这是我从证书中提取密钥以计算派生密钥的方法。
private byte[] GetDerivedKey(X509Certificate2 publicCertificate, X509Certificate2 privateCertificate)
{
byte[] derivedKey;
using (var privateKey = privateCertificate.GetECDsaPrivateKey())
using (var publicKey = publicCertificate.GetECDsaPublicKey())
{
var privateParams = privateKey.ExportParameters(true); //This line is failing
var publicParams = publicKey.ExportParameters(false);
using (var privateCng = ECDiffieHellmanCng.Create(privateParams))
using (var publicCng = ECDiffieHellmanCng.Create(publicParams))
{
derivedKey = privateCng.DeriveKeyMaterial(publicCng.PublicKey);
}
}
return derivedKey;
}
我评论了失败的行 privateKey.ExportParameters(true)
错误:
System.Security.Cryptography.CryptographicException : The requested operation is not supported.
at System.Security.Cryptography.NCryptNative.ExportKey(SafeNCryptKeyHandle key, String format)
at System.Security.Cryptography.CngKey.Export(CngKeyBlobFormat format)
at System.Security.Cryptography.ECCng.ExportParameters(CngKey key, Boolean includePrivateParameters, ECParameters& ecparams)
at System.Security.Cryptography.ECDsaCng.ExportParameters(Boolean includePrivateParameters)
因为这是我生成的自签名证书,所以我认为我做错了什么。
我首先创建一个根 CA 证书并传入私钥来签署我的证书。
private X509Certificate2 CreateECSDACertificate(string certificateName,
string issuerCertificateName,
TimeSpan lifetime,
AsymmetricKeyParameter issuerPrivateKey,
string certificateFriendlyName = null)
{
// Generating Random Numbers
var randomGenerator = new CryptoApiRandomGenerator();
var random = new SecureRandom(randomGenerator);
var signatureFactory = new Asn1SignatureFactory("SHA256WithECDSA", issuerPrivateKey, random);
// The Certificate Generator
var certificateGenerator = new X509V3CertificateGenerator();
// Serial Number
var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random);
certificateGenerator.SetSerialNumber(serialNumber);
// Issuer and Subject Name
var subjectDistinguishedName = new X509Name($"CN={certificateName}");
var issuerDistinguishedName = new X509Name($"CN={issuerCertificateName}");
certificateGenerator.SetSubjectDN(subjectDistinguishedName);
certificateGenerator.SetIssuerDN(issuerDistinguishedName);
// Valid For
var notBefore = DateTime.UtcNow.Date;
var notAfter = notBefore.Add(lifetime);
certificateGenerator.SetNotBefore(notBefore);
certificateGenerator.SetNotAfter(notAfter);
//key generation
var keyGenerationParameters = new KeyGenerationParameters(random, _keyStrength);
var keyPairGenerator = new ECKeyPairGenerator();
keyPairGenerator.Init(keyGenerationParameters);
var subjectKeyPair = keyPairGenerator.GenerateKeyPair();
certificateGenerator.SetPublicKey(subjectKeyPair.Public);
var certificate = certificateGenerator.Generate(signatureFactory);
var store = new Pkcs12Store();
var certificateEntry = new X509CertificateEntry(certificate);
store.SetCertificateEntry(certificateName, certificateEntry);
store.SetKeyEntry(certificateName, new AsymmetricKeyEntry(subjectKeyPair.Private), new[] { certificateEntry });
X509Certificate2 x509;
using (var pfxStream = new MemoryStream())
{
store.Save(pfxStream, null, new SecureRandom());
pfxStream.Seek(0, SeekOrigin.Begin);
x509 = new X509Certificate2(pfxStream.ToArray());
}
x509.FriendlyName = certificateFriendlyName;
return x509;
}
.HasPrivateKey()
方法 return 是正确的,我读过它可以 return 误报。
当我将我的证书添加到商店时,我可以验证证书链。
[Test]
public void CreateSelfSignedCertificate_AfterAddingToStore_CanBuildChain()
{
var result = _target.CreateSelfSignedCertificate(_subject, _issuer, TimeSpan.FromDays(356), _certificateFriendlyName, _issuerFriendlyName);
_store.TryAddCertificateToStore(result.CertificateAuthority, _caStoreName, _location);
_store.TryAddCertificateToStore(result.Certificate, _certStoreName, _location);
var chain = new X509Chain
{
ChainPolicy =
{
RevocationMode = X509RevocationMode.NoCheck
}
};
var chainBuilt = chain.Build(result.Certificate);
if (!chainBuilt)
{
foreach (var status in chain.ChainStatus)
{
Assert.Warn(string.Format("Chain error: {0} {1}", status.Status, status.StatusInformation));
}
}
Assert.IsTrue(chainBuilt, "Chain");
}
起初我以为私有证书可能必须来自证书存储,所以我导入它然后将其拉回,但我得到了同样的错误,这是我相信我不是的另一个原因做的很对。
编辑:
我有另一个 class 使用相同的代码生成 RSA x509,将私钥放入证书中。它允许我导出 RSA 私钥。
变量_keyStrength
是384,我的签名工厂用的是"SHA256withECDSA"
。我也尝试过使用 "SHA384withECDSA"
但我得到了同样的错误。
好的。这是一个盲目射击,但在查看您的代码后,我注意到两件事:
- 当您创建 PFX 时,您设置了空密码。但是当您将 PFX 加载到
X509Certificate2
class 时,您使用了错误的构造函数。你应该使用一个带有密码参数的,并给它一个空 - 当您将 PFX 加载到
X509Certificate2
class 时,您没有指定私钥是否应该是可导出的。我认为这就是privateKey.ExportParameters(true)
给你例外的原因。您应该使用 this 构造函数并将null
指定为密码
成功了
我认为这是一个错误。有可能是这样。我们在 X509Constructor 中明确说明私钥应该是可导出的。我也使用了 X509KeyStorageFlags.EphemeralKeySet | X509KeyStorageFlags.Exportable
标志。但是当我查看 CngKey 时,它的 ExportPolicy 设置为 AllowExport
而不是 AllowPlaintextExport
。
它在某种程度上是可以导出的。 privateKey.Key.Export(CngKeyBlobFormat.OpaqueTransportBlob)
成功了。但是 privateKey.ExportParameters(true)
没有。
我已经搜索了如何更改 CngKey 的 ExportPolicy 的解决方案。我发现 ExportParameters
起作用了。
你的 GetDerivedKey
方法的固定版本是
private byte[] GetDerivedKey(X509Certificate2 publicCertificate, X509Certificate2 privateCertificate)
{
byte[] derivedKey;
using (var privateKey = privateCertificate.GetECDsaPrivateKey())
using (var publicKey = privateCertificate.GetECDsaPublicKey())
{
var myPrivateKeyToMessWith = privateKey as ECDsaCng;
// start - taken from
// make private key exportable:
byte[] bytes = BitConverter.GetBytes((int)(CngExportPolicies.AllowExport | CngExportPolicies.AllowPlaintextExport));
CngProperty pty = new CngProperty(NCryptExportPolicyProperty, bytes, CngPropertyOptions.Persist);
myPrivateKeyToMessWith.Key.SetProperty(pty);
// end - taken from
var privateParams = myPrivateKeyToMessWith.ExportParameters(true); //This line is NOT failing anymore
var publicParams = publicKey.ExportParameters(false);
using (var privateCng = ECDiffieHellmanCng.Create(privateParams))
using (var publicCng = ECDiffieHellmanCng.Create(publicParams))
{
derivedKey = privateCng.DeriveKeyMaterial(publicCng.PublicKey);
}
}
return derivedKey;
}
我开始使用@pepo 发布的解决方案,它让我发现 'GetECDsaPrivateKey' 不是 return 一个 ECDsa
对象,而是一个 ECDsaCng
。我将密钥推导简化为这个。
byte[] derivedKey;
using (var privateKey = (ECDsaCng)certificate.GetECDsaPrivateKey())
using (var publicKey = (ECDsaCng)certificate.GetECDsaPublicKey())
{
var publicParams = publicKey.ExportParameters(false);
using (var publicCng = ECDiffieHellmanCng.Create(publicParams))
using (var diffieHellman = new ECDiffieHellmanCng(privateKey.Key))
{
derivedKey = diffieHellman.DeriveKeyMaterial(publicCng.PublicKey);
}
}
return derivedKey;