Search code examples
c#.netcryptographydiffie-hellmanpublic-key-exchange

C# - importing a public key blob into ECDiffieHellmanCng


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?


Solution

  • 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));