I am working on a project to integrate with the new Push API that exists in Firefox and is being developed as a W3C standard.
Part of this is encrypting the data. The server will receive a Diffie Hellman P256 Curve (Generated in JS using var key = subscription.getKey('p256dh');
)
An example of this when converted to a .NET base64 is
BOAiqZO6ucAzDlZKKhF1aLjNpU8+R2Pfsz4bQzNpV145D+agNxvLqyu5Q2tLalK2w31RpoDHE8Sipo0m2jiX4WA=
However I ran into issues generating the Derived Material.
var key1 = Convert.FromBase64String("<stringFromAbove>").ToList() // You can criticize my .toList inefficiencies later
// .NET doesn't like the key without these prefixes. See here
// http://stackoverflow.com/questions/24251336/import-a-public-key-from-somewhere-else-to-cngkey
// I know the bytes don't match that post, but that is because the key type is different between their example and mine.
var keyType = new byte[] { 0x45, 0x43, 0x4B, 0x31 };
var keyLength = new byte[] { 0x20, 0x00, 0x00, 0x00 };
key1.RemoveAt(0);
key1 = keyType.Concat(keyLength).Concat(key1).ToList();
ECDiffieHellmanCng a = new ECDiffieHellmanCng();
a.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
// If I set this as CngAlgorithm.Sha256 it works, but that's not what Firefox gives me.
a.HashAlgorithm = CngAlgorithm.ECDiffieHellmanP256;
a.KeySize = 256; // It complains if I don't add this since keys are different lengths.
// Now time to actually import the key
CngKey k = CngKey.Import(key1.ToArray(), CngKeyBlobFormat.EccPublicBlob); // Works successfully
byte[] derivedMaterial = a.DeriveKeyMaterial(k); // Exception Here
System.Security.Cryptography.CryptographicException: The requested operation is not supported.
What do I not understand correctly (or on the more sad side, what is not implemented correctly (or at all) in windows/.NET)?
As an alternative, if somebody could explain how to port this Node JS library to .NET that'd work too (I think that's a bit of a reach)
Update
I needed to keep working through the rest of the problem and not be held up by the encryption, so I used a Node.JS Wrapper to allow for further development on the .NET side. The node code simply generates the local public key and the Shared secret and returns those values to me. I still need to get this working without the Node wrapper.
Because of this test I can confirm that the rest of the code (not included here) works, so the issue definitely lies in the code above (and my inability to generate the derived key material if the HashAlgorithm is specified as CngAlgorithm.ECDiffieHellmanP256
ICSF generates ECC key pairs using the Elliptic Curve Digital Signature Algorithm (ECDSA). This algorithm uses elliptic curve cryptography (an encryption system based on the properties of elliptic curves) to provide a variant of the Digital Signature Algorithm.
Protocol: Key exchange based on En (a, b) can be set up as follows. The two parties agreed on the Elliptic curve equation, that is select prime number n and two parameters a and b , En (a,b): Y2 = x3 + ax + b , satisfying gcd (4a3+27b2, n ) = 1. Select n = pq as in RSA, p and q are two prime numbers.
ECDH is a key-agreement protocol that allows two parties, each having an elliptic curve public-private key pair, to establish a shared secret over an insecure channel. This shared secret is used to derive another symmetric key.
The formula to calculate the secret key is K = (Ya)Xb mod q. If both the values of K generated are equal, the Diffie-Hellman key exchange algorithm is complete.
This solution is only confirmed working on Windows 10 64-bit. It is confirmed not working on Windows 8.1 64 bit, and is untested on other platforms.
The problem is that ECDiffieHellmanP256
is not a hash algorithm, but you are specifying to use a hash key derivation function. Your KeyDerivationFunction
should be set to ECDiffieHellmanKeyDerivationFunction.Tls
, and you need to specify the seed and label for the KDF.
Your fixed code looks like this:
var key1 = Convert.FromBase64String("BOAiqZO6ucAzDlZKKhF1aLjNpU8+R2Pfsz4bQzNpV145D+agNxvLqyu5Q2tLalK2w31RpoDHE8Sipo0m2jiX4WA=").ToList();
var keyType = new byte[] { 0x45, 0x43, 0x4B, 0x31 };
var keyLength = new byte[] { 0x20, 0x00, 0x00, 0x00 };
key1.RemoveAt(0);
key1 = keyType.Concat(keyLength).Concat(key1).ToList();
ECDiffieHellmanCng a = new ECDiffieHellmanCng();
a.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Tls;
byte[] label = new byte[32];
string labelStr = "The purpose";
Encoding.ASCII.GetBytes(labelStr, 0, labelStr.Length, label, 0);
a.Label = label;
byte[] seed = new byte[32];
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
rng.GetBytes(seed);
a.Seed = seed;
a.HashAlgorithm = CngAlgorithm.ECDiffieHellmanP256;
a.KeySize = 256;
CngKey k = CngKey.Import(key1.ToArray(), CngKeyBlobFormat.EccPublicBlob);
byte[] derivedMaterial = a.DeriveKeyMaterial(k);
Note that I set a nonsense value to the a.Label
property.
The NIST SP 800-108 publication defines the label as:
Label – A string that identifies the purpose for the derived keying material, which is encoded as a binary string.
I'm not sure what the purpose should be set to in your specific context. If anyone has a better understanding what this string should be, please leave a comment.
Also note that if you're going to call this function repeatedly, you should probably keep a persistent copy of the RNGCryptoServiceProvider
and use that.
Thanks to a comment by Simon Mourier which got me on the right track.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With