2

I'm having trouble exchanging keys using the ECDiffieHellmanCng class:

Step 1 - Create a public key

public byte[] CreatePublicKey()
{
    using (ECDiffieHellmanCng cng = new ECDiffieHellmanCng())
    {
        cng.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
        cng.HashAlgorithm = CngAlgorithm.Sha512;
        return cng.PublicKey.ToByteArray();
    }
}

Step 2 - Exchange and get private key

public byte[] CreatePrivateKey(byte[] publicKey1, byte[] publicKey2)
{
    using(ECDiffieHellmanCng cng = new ECDiffieHellmanCng(CngKey.Import(publicKey1, CngKeyBlobFormat.EccPublicBlob)))
    {
        cng.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
        cng.HashAlgorithm = CngAlgorithm.Sha512;
        return cng.DeriveKeyMaterial(CngKey.Import(publicKey2, CngKeyBlobFormat.EccPublicBlob));
    }
}

Example

byte[] alicePublicKey = CreatePublicKey();
byte[] bobPublicKey = CreatePublicKey();

// This fails
byte[] alicePrivateKey = CreatePrivateKey(alicePublicKey, bobPublicKey);
byte[] bobPrivateKey = CreatePrivateKey(bobPublicKey, alicePublicKey);

Specifically it fails on this line from the CreatePrivateKey(...) method:

return cng.DeriveKeyMaterial(CngKey.Import(publicKey2, CngKeyBlobFormat.EccPublicBlob));

Error

System.Security.Cryptography.CryptographicException: 'Key does not exist.'

What am I doing wrong?

Matthew Layton
  • 39,871
  • 52
  • 185
  • 313

1 Answers1

4

Problem is you are trying to derive shared secret (result of DeriveKeyMaterial) using two public keys. This isn't going to work, because you need private key of one party and public key of the other party (public key of the first party is not needed because it can be derived from private key). Here is an example (I fixed some terms because now they are misleading - CreatePrivateKey does not create private key). Note that you don't usually export private keys like this and store them in container instead, so that is just for example:

public static (byte[] publicKey, byte[] privateKey) CreateKeyPair() {
    using (ECDiffieHellmanCng cng = new ECDiffieHellmanCng(
        // need to do this to be able to export private key
        CngKey.Create(
            CngAlgorithm.ECDiffieHellmanP256,
            null,
            new CngKeyCreationParameters
                { ExportPolicy = CngExportPolicies.AllowPlaintextExport }))) {
        cng.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
        cng.HashAlgorithm = CngAlgorithm.Sha512;
        // export both private and public keys and return
        var pr = cng.Key.Export(CngKeyBlobFormat.EccPrivateBlob);
        var pub = cng.PublicKey.ToByteArray();
        return (pub, pr);
    }
}

public static byte[] CreateSharedSecret(byte[] privateKey, byte[] publicKey) {
    // this returns shared secret, not private key
    // initialize algorithm with private key of one party
    using (ECDiffieHellmanCng cng = new ECDiffieHellmanCng(CngKey.Import(privateKey, CngKeyBlobFormat.EccPrivateBlob))) {
        cng.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
        cng.HashAlgorithm = CngAlgorithm.Sha512;
        // use public key of another party
        return cng.DeriveKeyMaterial(CngKey.Import(publicKey, CngKeyBlobFormat.EccPublicBlob));
    }
}

Now with two functions above:

var aliceKeyPair = CreateKeyPair();
var bobKeyPair = CreateKeyPair();                                
byte[] bobSharedSecret = CreateSharedSecret(bobKeyPair.privateKey, aliceKeyPair.publicKey);
byte[] aliceSharedSecret = CreateSharedSecret(aliceKeyPair.privateKey, bobKeyPair.publicKey);
// derived shared secrets are the same - the whole point of this algoritm
Debug.Assert(aliceSharedSecret.SequenceEqual(bobSharedSecret));
Evk
  • 98,527
  • 8
  • 141
  • 191
  • In the code example here: https://msdn.microsoft.com/en-us/library/system.security.cryptography.ecdiffiehellmancng(v=vs.110).aspx there isn't any notion of shared secrets. Each participant (alice, bob) has a public key and a private key. Their private keys are the same once they've both been created. How is this different from your example? – Matthew Layton Jan 30 '18 at 14:52
  • 1
    @series0ne what I call shared secret is named just "bobKey" and "aliceKey" in code example from msdn. That is fine, but calling it private key is misleading, because each party has another private key (corresponding to public key). So, each party has private key and public key. Private key of one party + public key of another produces another key (named shared secret in my code and just "key" in your sample) which is the same for both parties. – Evk Jan 30 '18 at 14:58
  • 1
    @series0ne in msdn example private key is generated when you create ECDiffieHellmanCng with empty constructor. In your code it is generated too, but is discarded because you only export public key. So after you leave CreatePublicKey function - private key is lost and returned public key is useless. In msdn example they do not dispose ECDiffieHellmanCng until all is done, so that private key is used. – Evk Jan 30 '18 at 15:37
  • That makes sense. So if I want to create keys and then shared secrets as two separate processes, I need to export both the public and private keys. – Matthew Layton Jan 30 '18 at 15:54
  • Question: Is the shared secret always the same for a particular pair of public/private keys? - I.e. are secrets deterministic? – Matthew Layton Jan 30 '18 at 15:54
  • Also how can alice's shared secret be based on bob's private key, and vice versa...shouldn't private keys be private? – Matthew Layton Jan 30 '18 at 16:10
  • 1
    Yes it is deterministic. As for your last question - that is mistake in naming in my answer - of course Alice key is based on her private and Bob's public. – Evk Jan 30 '18 at 16:12