Search code examples
c#encryptionprivate-keyecdhx25519

(C#) Calculate key share using private key and public key on (EC)DHE x25519


I am working with (EC)DHE encryption type x25519 and I have a big problem on calculating shared key.

I have three key:

  • Alice’s private key:

    a : "984a382e1e48d2a522a0e81b92fd13517e904316c6687a59d66cd2e5d9519a53"
    
  • Alice’s public key:

    Q(a) = a*G(a) : "3db045ba8a16efd9e15de287158097ee754ce5d76e83c5e434109dd132a4736d"
    
  • Bob’s public key:

    Q(b) =  b*G(b) : "74676252b0757ba3cb945ea053d9d65897a22e01592f7fa9c9503b818cd9df5a"
    

So now I need to combine Alice’s private key and Bob’s public key like this (to find a shared key between them):

Z = a * Q(b) = a * b * G(b)

Do anyone help me with this problem using C#? (I need a programming code).


Solution

  • I am working with (EC)DHE encryption type x25519 and I have a big problem on calculating shared key.

    Microsoft has no default implementation of the elliptic curve x25519. However their implementations of cryptographic Diffie Hellman objects allows us to define our own curve.

    Once we define our own curve to use (x25519) we can use Microsoft's ECDiffieHellmanCng implementation to import the curve, generate keys, and create shared secrets.

    Thanks to Yasar_yy for his question about an unrelated topic on x25519 he implemented the curve for us.

    We implement a curve using the ECCurve class

    public static ECCurve Curve25519 {get; init;} = new ECCurve()
    {
        CurveType = ECCurve.ECCurveType.PrimeMontgomery,
        B = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
        A = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x07, 0x6d, 0x06 }, // 486662
        G = new ECPoint()
        {
            X = new byte[] { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9 },
            Y = new byte[] { 0x20, 0xae, 0x19, 0xa1, 0xb8, 0xa0, 0x86, 0xb4, 0xe0, 0x1e, 0xdd, 0x2c, 0x77, 0x48, 0xd1, 0x4c,
            0x92, 0x3d, 0x4d, 0x7e, 0x6d, 0x7c, 0x61, 0xb2, 0x29, 0xe9, 0xc5, 0xa2, 0x7e, 0xce, 0xd3, 0xd9 }
        },
        Prime = new byte[] { 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
        0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xed },
        //Prime = new byte[] { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
        Order = new byte[] { 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x14, 0xde, 0xf9, 0xde, 0xa2, 0xf7, 0x9c, 0xd6, 0x58, 0x12, 0x63, 0x1a, 0x5c, 0xf5, 0xd3, 0xed },
        Cofactor = new byte[] { 8 }
    };
    

    After we define the curve we want to use we just need to generate the keys for the curve and the rest is standard for using the ECDiffieHellmanCng class.

    public class Person
    {
        public string Name {get; set;}
    
        public byte[] PublicKey {get; private set;}
    
        public byte[] PrivateKey {get; private set;}
    
        private ECParameters EncryptionParameters;
    
        public void GenerateInitialKeys()
        {
            using (ECDiffieHellmanCng bob = new ECDiffieHellmanCng())
            {
                // we have to generate the key explicitly using the curve we defined, the auto-generated keys can not be used
                bob.GenerateKey(Curve25519);
    
                // assign what algorithms for derivation and hashing should be used
                bob.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
                bob.HashAlgorithm = CngAlgorithm.Sha256;
    
                // save the keys
                PublicKey = bob.PublicKey.ToByteArray();
                PrivateKey = bob.ExportECPrivateKey();
    
                // export the curve information so we can create a shared secret later
                EncryptionParameters = bob.ExportParameters(true);
            }
        }
    
        public void CreateSharedSecret(byte[] OtherPublicKey)
        {
            if(EncryptionParameters is null)
            {
                throw new NullReferenceException($"{nameof(EncryptionParameters)} must not be null, invoke {nameof(GenerateInitialKeys)} to generate new keys and {nameof(EncryptionParameters)}");
            }
            using (ECDiffieHellmanCng bob = new ECDiffieHellmanCng())
            {
                // import the curve information from when generated our initial keys
                bob.ImportParameters(EncryptionParameters);
    
                // assign what algorithms for derivation and hashing should be used
                bob.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
                bob.HashAlgorithm = CngAlgorithm.Sha256;
    
                // import the byte[] as a key, note EccFullPublicBlob is required, otherwise a generic runtime error will throw and will contain absolutely no useful information
                CngKey otherKey = CngKey.Import(OtherPublicKey, CngKeyBlobFormat.EccFullPublicBlob)
    
                // Save the shared secret
                PrivateKey = bob.DeriveKeyMaterial(otherKey);
            }
        }
    
        // This is just here to visually verify the private keys for equality because we don't speak or read byte[]
        public string ExportPrivateKey()
        {
            return Convert.ToBase64String(PrivateKey ?? Array.Empty<byte>());
        }
    }
    

    To use this very basic class we just call GenerateKeys and subsequently CreateSharedSecret.

    Person alice = new();
    Person bob = new();
    
    alice.GenerateInitialKeys();
    
    bob.GenerateInitialKeys();
    
    alice.CreateSharedSecret(bob.PublicKey);
    
    bob.CreateSharedSecret(alice.PublicKey);
    
    Console.WriteLine(alice.ExportPrivateKey() == bob.ExportPrivateKey());
    // ideally should output: true