使用ECDSA x509证书的Diffie Hellman密钥交换

时间:2018-12-07 12:35:43

标签: c# bouncycastle x509certificate2 ecdsa diffie-hellman

我正在尝试使用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:不支持所请求的操作。

     

在System.Security.Cryptography.NCryptNative.ExportKey(SafeNCryptKeyHandle键,字符串格式)
     在System.Security.Cryptography.CngKey.Export(CngKeyBlobFormat格式)
     在System.Security.Cryptography.ECCng.ExportParameters(CngKey键,布尔值includePrivateParameters,ECParameters和ecparams)处
     在System.Security.Cryptography.ECDsaCng.ExportParameters(布尔值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()方法返回true,我读过该方法可以返回假阳性。

将证书添加到商店时,我可以验证证书链。

    [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");
    }

我最初认为私有证书可能必须来自证书存储,所以我导入了该证书然后将其撤回,但是我遇到了同样的错误,这是我认为我没有做某件事的另一个原因非常正确。

编辑:

我还有另一个类,它使用相同的代码将RSA加密密钥放入证书中,从而生成RSA x509。它允许我导出RSA私钥。

变量_keyStrength为384,我的签名工厂正在使用"SHA256withECDSA"。我也尝试使用"SHA384withECDSA",但遇到相同的错误。

2 个答案:

答案 0 :(得分:2)

好。这是盲目的,但是在查看您的代码后,我注意到了两件事:

  • 创建PFX时,请设置空密码。但是,当您将PFX加载到X509Certificate2类中时,使用的是错误的构造函数。您应该使用一个带有密码参数的参数,并输入一个空值
  • 将PFX加载到X509Certificate2类中时,如果私钥是否应该可导出,则无需指定。我认为这就是privateKey.ExportParameters(true)给您例外的原因。您应该使用this构造函数并指定null作为密码

使其正常工作

我认为这是一个错误。可能是这样。我们在X509Constructor中明确指出,私钥应该是可导出的。我也使用了X509KeyStorageFlags.EphemeralKeySet | X509KeyStorageFlags.Exportable标志。但是当我查看CngKey时,已将ExportPolicy设置为AllowExport,但没有设置为AllowPlaintextExport

它可以以某种方式导出。 privateKey.Key.Export(CngKeyBlobFormat.OpaqueTransportBlob)有效。但是privateKey.ExportParameters(true)没有。

我一直在寻找一种解决方案,该方法如何更改CngKey的ExportPolicy。我发现this SO question可以帮助我进行更改。之后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 https://stackoverflow.com/q/48542233/3245057 
        // 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 https://stackoverflow.com/q/48542233/3245057

        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;
}

答案 1 :(得分:0)

我开始使用发布的解决方案@pepo,这使我发现'GetECDsaPrivateKey'不是返回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;