The second elliptic curve algorithm added to Orcas is elliptic curve Diffie-Hellman, as the ECDiffieHellmanCng class.

This is the first time Diffie-Hellman is available as part of the .NET Framework, so lets take a quick look at what it is and what it does. Diffie-Hellman is one of the oldest asymmetric algorithms, however unlike the other asymmetric algorithms in the framework today, it does not perform encryption or digital signatures. Instead it allows two parties to exchange private key material even if they only can communicate through a completely public channel. (In *Network Security: Private Communication in a Public World*, an amusing example is given where Diffie-Hellman is performed by two parties taking out ads in the local newspaper).

Key exchange is somewhat of a misleading term, since it implies that one party to the communication generates a key and via the communication protocol lets the other party know what that key is. Instead what really happens is that Diffie-Hellman allows both parties to calculate the same secret value, which is referred to as the secret agreement in the managed Diffie-Hellman classes. This secret agreement can then be used for any number of purposes, including being used as a symmetric key.

Instead of exposing the secret agreement directly however, the ECDiffieHellmanCng class does some post-processing on the agreement before letting the value out. We refer to this post processing as the key derivation function; you can select which KDF you want to use and set its parameters via a set of properties on the instance of the Diffie-Hellman object:

Key Derivation Function |
Properties |
Meaning |

Hash | HashAlgorithm | Hash algorithm to process the secret agreement with |

SecretPrepend | Optional byte array to prepend to the secret agreement before hashing it | |

SecretAppend | Optional byte array to append to the secret agreement before hashing it | |

Hmac | HashAlgortihm | Hash algorithm to process the secret agreement with (using the HMAC version of the algorithm). |

HmacKey | Key used for the HMAC operation | |

SecretPrepend | Optional byte array to prepend to the secret agreement before hashing it | |

SecretAppend | Optional byte array to append to the secret agreement before hashing it | |

Tls | Label | TLS PRF Label |

Seed | TLS PRF Seed |

The result of passing the secret agreement through the key derivation function is a byte array that may be used as key material for your application. The number of bytes of key material generated is dependent on the key derivation function, for instance SHA-256 will generate 256 bits of key material, while SHA-512 will generate 512 bits of key material.

The basic flow of an elliptic curve Diffie-Hellman key exchange is:

- Alice and Bob create a key pair to use for the Diffie-Hellman key exchange operation
- Alice and Bob configure the KDF using agreed upon parameters
- Alice sends Bob her public key
- Bob sends Alice his public key
- Using each other’s public keys, the secret agreement is generated, and the KDF is applied to the secret agreement generating key material.

In code, this looks basically as you would expect:

ECDiffieHellmanCng alice = new ECDiffieHellmanCng();

alice.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;

alice.HashAlgorithm = CngAlgorithm.Sha256;

ECDiffieHellmanCng bob = new ECDiffieHellmanCng();

bob.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;

bob.HashAlgorithm = CngAlgorithm.Sha256;

byte[] bobKey = bob.DeriveKeyMaterial(alice.PublicKey);

byte[] aliceKey = alice.DeriveKeyMaterial(bob.PublicKey);

After running this code, aliceKey and bobKey are both 32 bytes long and match each other. Now, Alice could use this as a symmetric key:

AesCryptoServiceProvider aes = new AesCryptoServiceProvider();

aes.Key = aliceKey;

Obviously, this example is simplified as both Alice and Bob are unlikely to be running in the same process. ECDiffieHellmanPublicKey, which is the class returned by the PublicKey property is Serializable, so that it may be sent across any remoting channel. It also can be manually converted to and from a byte array and XML, allowing for manual serialization in advanced use cases.

One thing to note about Diffie-Hellman is that it only guarantees that both parties are generating a secret that nobody else knows about. It does not let either party know the identity of the other. For instance, in the above code, Alice cannot be sure that the other person in the conversation is Bob; there could potentially be a man-in-the-middle attack here.

In order to solve that, Alice or Bob could use a well-known public key that is distributed by PKI (you can pass a CngKey to the DeriveKeyMaterial API in this case). You could also use HMAC as the KDF and a key that you know only Alice and Bob share to solve this problem.

Ugh :-/ It’s the whole RSA catastrophy all over again…

Remember that CAPI only allowed to use the standard RSA paddings PKCS#1.5 and OAEP, and this caused a few problems for you guys when implementing the .NET wrappers. .NET has a clean RSA architecture with its barebones RSA descendants, and the higher-level formatter classes, but because of the way the CryptoAPI was designed you had to implement silly workarounds to get the thing working (RSACryptoServiceProvider.EncryptValue that always throws an exception for instance).

This ECDH implementation has essentially the same problem: what if I need to implement a different key derivation function? Since I can’t access the bare output bits of the ECDH algorithm, there’s nothing much I can do with this class…

There is a difference however. With the advent of the .NET framework v1.0, Microsoft had finally done it right. I really like the RSA architecture. When someone creates their own RSA implementation, they can simply plug it into the framework, and all the different paddings will immediately work.

However, the ECDiffieHellmanCng class appears to go the other way: a simple enum to select the key derivation function. That was not exactly the handsome OO architecture I was hoping for :-/ When the CNG API in Windows Vnext finally allows us to access the raw bits, we’ll be stuck in .NET with this design decision.

I realize that this decision was most likely made because of the current CNG architecture, and that you guys are probably trying to save yourself some time by not having to duplicate the key derivation code in .NET (unlike the RSA padding code, which is duplicated in the .NET runtime). But do realize that this design decision might come back to haunt you within this and 10 years.

As a side note, can you explain where those three key derivation functions come from? I must admit I’m not exactly an ECDH expert, but I don’t know of too many standards that use ECDH. So isn’t it a bit early to declare there three key derivation functions as the de facto standards? In fact, I could only find the ANSI X9.63 key dervation function as a standard ECDH key derivation function (and this algorithm doesn’t seem to correspond to any of the supported algorithms by CNG), so what happens if different standards begin to use ANSI X9.63?

Likewise, the TLS PRF option already seems to be outdated before CNG was even released. The current TLS 1.2 standard specifies a different PRF that allows for any hash function to be used.

Supporting all these different key derivation functions would be much easier if we had a plug-in system like the RSA classes (and if we could access the raw bits of the ECDH calculation, of course).

I realize that the tone in this post is somewhat negative, but dont’t get me wrong: I’m really excited to see these new things happen in the Windows and the .NET crypto API. I’m just a bit disappointed that this same mistake has been made twice…

Hi Pieter,

Thanks for your feedback! Let me try to address some of your comments:

In this post I was talking about the ECDiffieHellmanCng class, which is roughly the equivilent to RSACryptoServiceProvider in the RSA hierarchy. There is also an ECDiffieHellman base class which is the analog of RSA – namely it defines only the basic operations, and you can inherit new implementation classes if you want. The base class simply has to be able to export its public key, and be able to generate a byte array given another party’s public key.

You guessed correctly that the enum for the KDF is based around CNG. There’s different structures we need to pass through depending on the KDF selected, and therefore we couldn’t make it extensible like the other pseudo-enum string extensibility points. However, we do also provide a way around this. In addition to DeriveKeyMaterial, ECDSACNG provides a method DeriveSecretAgreement which returns back the CNG handle to the secret agreement. If CNG v2 allows you to use a new KDF that we haven’t yet wrapped or lets you get at the raw secret agreement directly, you can then P/Invoke with that handle to access the data.

By exposing the secret agreement handle, we let you do just the last part of the P/Invokes rather than have to throw away the whole class and do all of them, which tends to be the case with our RSA class.

Not being able to get the raw secret agreement is enforced at the CNG layer, and not at the managed wrappers. When I was talking to the CNG guys during the design of this class, it turns out that this was by request from some certifications they were going through (although I don’t recall all the details at this time).

If, when you’re playing around with these classes, you find that you do have some more feedback about the design, please share it here with me. We’re always glad to revisit our APIs before we ship if there’s something that we’ve msised.

-Shawn

Shawn,

thanks for the answer. The fact that these decisions were made because of certification requirements explains a lot about the design of CNG (and probably the CryptoAPI too).

I still don’t see why someone wouldn’t allow access to the calculated bits (while at the same moment allowing access to the underlying key), but at least it’s outside of Microsoft’s control.

The class ECDiffieHellmanCng has properties called "HmacKey" and "UseSecretAgreementAsHmacKey". The documentation says that if UseSecretAgreementAsHmacKey is true, HmacKey property does not apply. I would like to know however, if UseSecretAgreementAsHmacKey is set to true, what is used as the actual HMAC input? I don't see any property for this and think it's a serious flaw in the API.

If UseSecretAgreementAsHmacKey is true, then the secret agreement that is generated from the ECDiffie-Hellman algorithm is used as the HMAC key. There is no way for you to supply your own, because you're using a value generated by the cryptographic operation. If you do want to supply your own HMAC key, then UseSecretAgreementAsHmacKey will be false.

-Shawn

It is clear (from the not very clear documentation) that if UseSecretAgreementAsHmacKey is true you use the generated raw secret as HMAC "key". What is completely unclear is what the "argument" is. IMO, it SHOULD have used some of the public inputs.

It is clear (from the not very clear documentation) that if UseSecretAgreementAsHmacKey is true you use the generated raw secret as HMAC "key". What is completely unclear is what the "argument" is. IMO, it SHOULD have used some of the public inputs.

Shawn, A Rundgren,

I didn't notice I had actually got more replies to this recently. I did some research earlier on my own and found out that if UseSecretAgreementAsHmacKey is true, then the secret agreement is used BOTH as the HMAC key AND also as the HMAC message input. That makes this API completely unusable and, I'm sorry to say, stupid.

What I want to do is to generate HMAC(key, message) data, where key is the secret agreement and message is some other public input. This kind of functionality is for example required for the TLSv1.2 master secret generation, because the ECDiffieHellmanCng Tls KeyDerivationFunction can only be used with TLSv1.0 and TLSv1.1. According to my research that is impossible, which makes this one of the most disappointing APIs I've seen in a while. If I'm wrong I probably owe an apology to people related, but so far I don't have any data to support that.

I solved this problem by writing ECDiffie-Hellman with C# from scratch without using ECDiffieHellmanCng at all. Needless to say, I'm not happy with this solution.