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