Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

.NET ECDiffieHellmanCng and BouncyCastle Core compatible agreement

I have to make a Diffie Hellman agreement with a third party that communicates the public keys in the .NET ECDiffieHellmanCng XmlString format. I cannot change their code. What they send looks like this:

<ECDHKeyValue xmlns="http://www.w3.org/2001/04/xmldsig-more#">
  <DomainParameters>
    <NamedCurve URN="urn:oid:1.3.132.0.35" />
  </DomainParameters>
  <PublicKey>
    <X Value="11" xsi:type="PrimeFieldElemType" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
    <Y Value="17" xsi:type="PrimeFieldElemType" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" />
  </PublicKey>
</ECDHKeyValue>

They generate that using typical .NET Framework code like this:

using (ECDiffieHellmanCng dhKey = new ECDiffieHellmanCng())
{
    dhKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
    dhKey.HashAlgorithm = CngAlgorithm.Sha256;

    Console.WriteLine(dhKey.PublicKey.ToXmlString());
}

They expect to receive my public key in the same format. They use my public key like this:

ECDiffieHellmanCngPublicKey pbkey = ECDiffieHellmanCngPublicKey.FromXmlString(xmlHere);

I work in .NET core 2.1. Unfortunately the ECDiffieHellmanCng classes and the like are currently not implemented in .NET core. I thought I could use the BouncyCastle for .NET Core package for this: https://www.nuget.org/packages/BouncyCastle.NetCore/ I would assume these both implement the same standard and they would be compatible.

I know how to do the agreement completely with the bouncy castle, however it's not clear to me how to do that starting with the X and Y values in the xml that come out of the .NET ECDiffieHellmanCng and how to make sure I use compatible parameters. It's also not clear to me how I get the X and Y values from the bouncy castle public key that I generate to send back to them. It doesn't help that the bouncy castle for .net api is not exactly the same as the java api and the documentation is limited.

Update 1: After reading some comments below, it appears indeed that the ECDiffieHellmanCng are partially implemented in .NET Core. Most of the logic works but only ToXmlString and FromXmlString don't work. That's ok, I can work around that. However I'm now running into a different problem. The curve that the other side uses is oid:1.3.132.0.35. However when I try to use this in .NET core, even with a basic example like this:

    using (ECDiffieHellman dhBob = ECDiffieHellman.Create(ECCurve.CreateFromValue("1.3.132.0.35")))
    {
        using (ECDiffieHellman dhAlice = ECDiffieHellman.Create(ECCurve.CreateFromValue("1.3.132.0.35")))
        {
            byte[] b = dhAlice.DeriveKeyMaterial(dhBob.PublicKey);

            byte[] b2 = dhBob.DeriveKeyMaterial(dhAlice.PublicKey);

            Console.WriteLine(b.SequenceEqual(b2));
        }
    }

Then I get this error:

Unhandled Exception: System.PlatformNotSupportedException: The specified curve 'ECDSA_P521' or its parameters are not valid for this platform. ---> Internal.Cryptography.CryptoThrowHelper+WindowsCryptographicException: The parameter is incorrect
   at System.Security.Cryptography.CngKeyLite.SetProperty(SafeNCryptHandle ncryptHandle, String propertyName, Byte[] value)
   at System.Security.Cryptography.CngKeyLite.SetCurveName(SafeNCryptHandle keyHandle, String curveName)
   at System.Security.Cryptography.CngKeyLite.GenerateNewExportableKey(String algorithm, String curveName)
   at System.Security.Cryptography.ECCngKey.GenerateKey(ECCurve curve)
   --- End of inner exception stack trace ---
   at System.Security.Cryptography.ECCngKey.GenerateKey(ECCurve curve)
   at System.Security.Cryptography.ECDiffieHellman.Create(ECCurve curve)
   at TestCore.Program.Main(String[] args) 

The error message is not clear to me. Is that curve really not supported? Or is something wrong in the parameters, but then what exactly? It would surprise me if the curve is not supported because nistP521 curve is supported and according to this IBM document I found online they are the same: https://www.ibm.com/support/knowledgecenter/en/linuxonibm/com.ibm.linux.z.wskc.doc/wskc_r_ecckt.html

like image 706
user968698 Avatar asked Nov 08 '22 01:11

user968698


1 Answers

Thanks all for your help. Eventually I wrote this code which works on .Net Core 2.1 and which is compatible with the .Net Framework To/FromXmlString:

        using (ECDiffieHellmanCng dhBob = new ECDiffieHellmanCng())
        {
            dhBob.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
            dhBob.HashAlgorithm = CngAlgorithm.Sha256;
            string xmlBob = ToXmlString(dhBob.PublicKey);
            //Console.WriteLine(xmlBob);

            using (ECDiffieHellmanCng dhAlice = new ECDiffieHellmanCng())
            {
                dhAlice.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
                dhAlice.HashAlgorithm = CngAlgorithm.Sha256;
                ECDiffieHellmanPublicKey keyBob = FromXmlString(xmlBob, dhAlice.KeySize);
                byte[] b = dhAlice.DeriveKeyMaterial(keyBob);


                string xmlAlice = ToXmlString(dhAlice.PublicKey);
                ECDiffieHellmanPublicKey keyAlice = FromXmlString(xmlAlice, dhBob.KeySize);
                byte[] b2 = dhBob.DeriveKeyMaterial(keyAlice);

                Console.WriteLine(b.SequenceEqual(b2));
            }
        }

public static string ToXmlString(ECDiffieHellmanPublicKey key)
{
    // the regular ToXmlString from ECDiffieHellmanPublicKey throws PlatformNotSupportedException on .net core 2.1
    ECParameters parameters = key.ExportParameters();
    return string.Format("<ECDHKeyValue xmlns='http://www.w3.org/2001/04/xmldsig-more#'><DomainParameters><NamedCurve URN='urn:oid:{0}' />" +
                         "</DomainParameters><PublicKey><X Value='{1}' xsi:type='PrimeFieldElemType' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' />" +
                         "<Y Value='{2}' xsi:type='PrimeFieldElemType' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' /></PublicKey></ECDHKeyValue>",
        GetOid(parameters.Curve),
        new BigInteger(parameters.Q.X.Reverse().ToArray().Concat(new byte[] { 0 }).ToArray()).ToString(System.Globalization.CultureInfo.InvariantCulture), // watch out for big endian - little endian
        new BigInteger(parameters.Q.Y.Reverse().ToArray().Concat(new byte[] { 0 }).ToArray()).ToString(System.Globalization.CultureInfo.InvariantCulture));
}

public static ECDiffieHellmanPublicKey FromXmlString(string xml, int keySize)
{
    // the regular FromXmlString from ECDiffieHellmanPublicKey throws PlatformNotSupportedException on .net core 2.1
    XDocument doc = XDocument.Parse(xml);
    XNamespace nsSys = "http://www.w3.org/2001/04/xmldsig-more#";
    string xString = doc.Element(nsSys + "ECDHKeyValue").Element(nsSys + "PublicKey").Element(nsSys + "X").Attribute("Value").Value;
    string yString = doc.Element(nsSys + "ECDHKeyValue").Element(nsSys + "PublicKey").Element(nsSys + "Y").Attribute("Value").Value;
    string curve = doc.Element(nsSys + "ECDHKeyValue").Element(nsSys + "DomainParameters").Element(nsSys + "NamedCurve").Attribute("URN").Value;
    curve = curve.Replace("urn:", "").Replace("oid:", "");

    byte[] arrayX = BigInteger.Parse(xString, System.Globalization.CultureInfo.InvariantCulture).ToByteArray(false, true); // watch out for big endian - little endian
    byte[] arrayY = BigInteger.Parse(yString, System.Globalization.CultureInfo.InvariantCulture).ToByteArray(false, true);

    // make sure each part has the correct and same size
    int partSize = (int) Math.Ceiling(keySize / 8.0);
    ResizeRight(ref arrayX, partSize);
    ResizeRight(ref arrayY, partSize);

    ECParameters parameters = new ECParameters() { Q = new ECPoint() { X = arrayX, Y = arrayY }, Curve = GetCurveByOid(curve) };
    ECDiffieHellman dh = ECDiffieHellman.Create(parameters);
    return dh.PublicKey;
}

/// <summary>
/// Resize but pad zeroes to the left instead of to the right like Array.Resize
/// </summary>
public static void ResizeRight(ref byte[] b, int length)
{
    if (b.Length == length)
        return;
    if (b.Length > length)
        throw new NotSupportedException();

    byte[] newB = new byte[length];
    Array.Copy(b, 0, newB, length - b.Length, b.Length);
    b = newB;
}

private static ECCurve GetCurveByOid(string oidValue)
{
    // there are strange bugs in .net core 2.1 where the createfromvalue doesn't work for the named curves
    switch (oidValue)
    {
        case "1.2.840.10045.3.1.7":
            return ECCurve.NamedCurves.nistP256;
        case "1.3.132.0.34":
            return ECCurve.NamedCurves.nistP384;
        case "1.3.132.0.35":
            return ECCurve.NamedCurves.nistP521;
        default:
            return ECCurve.CreateFromValue(oidValue);
    }
}

private static string GetOid(ECCurve curve)
{
    // there are strange bugs in .net core 2.1 where the value of the oid of the named curves is empty
    if (curve.Oid.FriendlyName == ECCurve.NamedCurves.nistP256.Oid.FriendlyName)
        return "1.2.840.10045.3.1.7";
    else if (curve.Oid.FriendlyName == ECCurve.NamedCurves.nistP384.Oid.FriendlyName)
        return "1.3.132.0.34";
    else if (curve.Oid.FriendlyName == ECCurve.NamedCurves.nistP521.Oid.FriendlyName)
        return "1.3.132.0.35";
    else
        return curve.Oid.Value;
}
like image 66
user968698 Avatar answered Nov 15 '22 11:11

user968698