In your question you link to an article explaining (classic/IFC) Diffie-Hellman, which is secret = (g ^^ (alice * bob)) % p
. You then mention ECDHE and ask about the ECDiffieHellman(Cng) class in .NET, which is about Elliptic Curve Diffie-Hellman... the ECC variant of the IFC (Integer Factorization Cryptography) algorithm.
(IFC)DH definitely has a bootstrapping problem of picking a good (g, p)
combination, and not being tricked by a man in the middle. For TLS the server side of the connection gets to make up whatever (g, p)
it wants and then tells the client what it picked, but the client can't really tell if it's being tricked. If you own both sides you can solve this problem by generating a quality group in the 2048-bit space, and sticking with it.
There is no support for (IFC) Diffie-Hellman provided in-box in .NET.
ECDH has a different set of parameters, which are collectively called "the curve". For a prime curve (the most common form) the parameters are the tuple (p, a, b, G, n, h)
(though really n
and h
are computations from (p, a, b, G)
), and then ECC math is defined on top of that. Once ECC math is defined, ECDH is secret = X-Coordinate((alice * bob) * G)
. ECC has the same pitfalls as (IFC)DH, choosing bad values for the parameters can let tricks be played on the other party. Because of the potential trickery, and the fact that the domain parameters are large, choices of domain parameters get standardized into "named curves". Two keys on the same curve, by definition, have the same set of domain parameters. In TLS you are only allowed to use the name (well, the Object Identifier value) of the curve. The server pretty much gets to choose what set of parameters, but for maximum interoperability usually only three curves are chosen from (as of 2018): secp256r1
(aka NIST P-256), secp384r1
(aka NIST P-384), and secp521r1
(aka NIST P-521).
ECDiffieHellmanCng defaults to using secp521r1
, but you can control the curve in one of three different ways:
Setting ecdh.KeySize
Changing the KeySize value (setting it to any value other than what it currently holds) results in generating a key onto a curve of that size. This made total sense for Windows 7, 8, and 8.1... because Windows CNG only supported secp256r1
, secp384r1
, and secp521r1
. So you can set KeySize to any of { 256, 384, 521 }.
using (ECDiffieHellman ecdh = ECDiffieHellman.Create())
{
ecdh.KeySize = 384;
...
}
Creating it that way
Windows 10 added support for more curves, and the sizes became ambiguous. Does 256 mean secp256r1
or brainpoolp256r1
(or brainpoolp256t1
, numsp256t1
, secp256k1
, ...)? Okay, it means secp256r1
, and more complicated API exists.
The ECDiffieHellman.Create
factory has an overload (.NET Core 2.1+, .NET Framework 4.7+) which accepts an ECCurve
. So another way to create a curve over secp384r1
would be
using (ECDiffieHellman ecdh = ECDiffieHellman.Create(ECCurve.NamedCurves.nistP384))
{
...
}
It can still be set later
Maybe you're using DI and can't use the factory nicely. Well, you can use the GenerateKey
method (.NET Core 2.1+, .NET Framework 4.7+) to achieve the same results
using (ECDiffieHellman ecdh = ECDiffieHellman.Create())
{
ecdh.GenerateKey(ECCurve.NamedCurves.nistP384);
...
}
There are other ways of getting ECCurve values, such as ECCurve.CreateFromValue("1.3.132.0.34")
or just manually building it from (p, a, b, G = (Gx, Gy), n, h)
.