I wrote X25519 curve parameters in .NET and thought to cross-check it between this piece and BouncyCastle. The following is the code. But it fails to export the BouncyCastle key.
There will be an exception
System.Security.Cryptography.CryptographicException : Key is not a valid public or private key.
when I try to import the key between steps 3 and 4 (see code).
I'm a bit at loss what could be the reason for that.
Hints, tips or anything to push towards making this work would be much appreciated. How to make this cross-check work? What do I miss? Is there something with the curve?
public static ECCurve Curve25519 => new()
{
CurveType = ECCurve.ECCurveType.PrimeMontgomery,
//In NIST.SP.800-186-draft.pdf: 'A'.
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
//In NIST.SP.800-186-draft.pdf: 'B'.
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 },
G = new ECPoint()
{
//In NIST.SP.800-186-draft.pdf: 'Gu'.
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 = Gy2.ToByteArray(true, true),
//In NIST.SP.800-186-draft.pdf: 'Gu'.
Y = new byte[] { 0x2f, 0xaf, 0x1f, 0xaf, 0xbf, 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 = p = 2^255 - 19.
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 },
//In NIST.SP.800-186-draft.pdf: 'n'.
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 },
//In NIST.SP.800-186-draft.pdf: 'h'.
Cofactor = new byte[] { 8 }
};
public void CrossCheck25519UsingBouncyCastleAndDotNet()
{
// Step 1: Bob generates keys using BouncyCastle
var randomGenerator = new SecureRandom();
var bobPrivateKeyBouncy = new X25519PrivateKeyParameters(randomGenerator);
X25519PublicKeyParameters bobPublicKeyBouncy = bobPrivateKeyBouncy.GeneratePublicKey();
// Alternatively...
// var keys = keyGenerator.GenerateKeyPair();
// var bobPublicKeyBouncy = (X25519PublicKeyParameters)keys.Public;
// var bobPrivateKeyBouncy = (X25519PrivateKeyParameters)keys.Private;
// Step 2: Bob sends his public key to Alice
SubjectPublicKeyInfo publicKeyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(bobPublicKeyBouncy);
byte[] bobPublicKeyBytes = publicKeyInfo.GetDerEncoded();
// Step 3: Alice receives Bob's public key and generates her own keys using .NET
using(var alice = ECDiffieHellman.Create(Curve25519))
{
// This fails. Is the reason Curve25519 parameters or that BouncyCastle exported keys are in wrong format?
alice.ImportSubjectPublicKeyInfo(bobPublicKeyBytes, out _);
// Alice calculates the shared secret
byte[] aliceSharedSecret = alice.DeriveKeyFromHash(alice.PublicKey, HashAlgorithmName.SHA256);
// Step 4: Alice sends her public key to Bob
byte[] alicePublicKeyBytes = alice.PublicKey.ExportSubjectPublicKeyInfo();
// Step 5: Bob imports Alice's public key using BouncyCastle
var alicePublicKeyBouncy = new X25519PublicKeyParameters(alicePublicKeyBytes, 0);
// Step 6: Bob calculates the shared secret using BouncyCastle
var bobAgreement = new X25519Agreement();
bobAgreement.Init(bobPrivateKeyBouncy);
byte[] bobSharedSecret = new byte[bobAgreement.AgreementSize];
bobAgreement.CalculateAgreement(alicePublicKeyBouncy, bobSharedSecret, 0);
// Step 7: Compare the shared secrets
Assert.Equal(bobSharedSecret, aliceSharedSecret);
}
}