2

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).

Kom Pe
  • 35
  • 5
  • 1
    are you using the [system.security.cryptography.ecdiffiehellmancng](https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.ecdiffiehellmancng?view=net-5.0) or are you self-implementing the key exchange function? – DekuDesu Aug 02 '21 at 13:29
  • I am using [https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.ecdiffiehellman?view=net-5.0] to create own public and private key. After that I receive public key from server. I got all thing to compute the shared key but I still don't know how to du this. – Kom Pe Aug 02 '21 at 14:20
  • @DekuDesu Do you know, how to calculate it or do you have some new idea to create a shareKey between them? – Kom Pe Aug 02 '21 at 22:28

2 Answers2

4

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
DekuDesu
  • 2,224
  • 1
  • 5
  • 19
  • Hello @DekuDesu, Thank you very much for your help. When I debugged ‘PublicKey = bob.PublicKey.ToByteArray();’ I got 289 bytes: first 32 bytes + prime + B + A + G(x) + G(y) + order + cofactor + 32 bytes publicKey + 32 bytes 00 padding. Can you tell me why ‘bob.PublicKey’ is 289 bytes, and what does first 32 bytes stand for and why it has to add 32 bytes 00 padding in the end? – Kom Pe Aug 03 '21 at 23:46
  • Unfortunately I'm not an expert on the cryptographic implementations, however I could take an educated guess that it's somehow used to calculate the final agreement. Since the private agreement is all we care are about in a diffie hellman exchange the public key is not used for assumetric cryptographic operations, an example would be the Double Ratchet Protocol. However I'm sure someone else could provide a better and more exact guess than I. – DekuDesu Aug 04 '21 at 03:39
  • In second thought, it could also be an intentional padding used to prevent uninformed users from using the public key as a primary encryption key accidentally since it's key size is abnormal and won't work with MSDNs default implementations of symmetric encryption such as AES – DekuDesu Aug 04 '21 at 03:41
3

A comfortable alternative to the built-in functionality (see the other answer) is BouncyCastle, which allows a more compact implementation and also supports easy import of raw X25519 keys:

using Org.BouncyCastle.Crypto.Agreement;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Utilities.Encoders;
...
X25519PrivateKeyParameters privateKeyAlice = new X25519PrivateKeyParameters(Hex.Decode("a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4"), 0);
X25519PublicKeyParameters publicKeyBob = new X25519PublicKeyParameters(Hex.Decode("e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c"), 0);

X25519Agreement agreementAlice = new X25519Agreement();
agreementAlice.Init(privateKeyAlice);
byte[] secretAlice = new byte[agreementAlice.AgreementSize];
agreementAlice.CalculateAgreement(publicKeyBob, secretAlice, 0);

Console.WriteLine(Hex.ToHexString(secretAlice)); // c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552

In the posted example, a test vector from Rfc7748 (which specifies curve 25519, among others) was used.

In contrast to the built-in functionality (see here), BC pleasantly provides the unmodified shared secret, which can be used to derive keys as needed (e.g. by applying a digest).

Topaco
  • 40,594
  • 4
  • 35
  • 62