novotnyllc / bc-csharp

Portable version of Bouncy Castle with support for .NET 4, .NET Standard 2.0, MonoAndroid, Xamarin.iOS, .NET Core
https://www.bouncycastle.org/csharp
Other
176 stars 36 forks source link

CalculateAgreement error using ECDHBasicAgreement v1.8.1.3 #7

Closed iodev closed 6 years ago

iodev commented 6 years ago

I have a blocking issue in Portable.BouncyCastle v1.8.1.3 nuget package. Using the following code, the error is isolated. Within 55 exchanges, between receiver and sender, the agreement calculation will not match on the receiving side, at some random point. The occasion that the error occurs is flagged out in the console app attached; that is, when sender and receiver do not calculate the same agreement. To avoid the initial exchange error that would occur, you can uncomment the initial keys, or leave it as it is here, with two initial exchanges, not reporting the error. So, in Main, if you use the 4 keys, then you can comment out the first two initial exchanges before the loop.

Here is the code:

using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Agreement;
using Org.BouncyCastle.Utilities;
using Org.BouncyCastle.Math;
using Org.BouncyCastle.Security;
using Org.BouncyCastle.Crypto.Generators;
using Org.BouncyCastle.Math.EC;
using Org.BouncyCastle.Crypto.EC;
using Org.BouncyCastle.Asn1.X9;
namespace My
{
    public struct AsymmetricKeyPair
    {
        public byte[] priv;
        public byte[] pub;
    }
    public class EC25519CKA
    {
        public AsymmetricKeyPair State;
        public byte[] RUpd;
        public byte[] SUpd;
        private const int KEYSIZE = 33;
        // Returns a new Diffie-Hellman key pair
        public void Generate(ref byte[] priv, ref byte[] pub)
        {
            ECKeyPairGenerator dhGen = new ECKeyPairGenerator();
            SecureRandom rng = new SecureRandom();
            dhGen.Init(new ECKeyGenerationParameters(ecparameters, rng));
            AsymmetricCipherKeyPair dhPair = dhGen.GenerateKeyPair();
            priv = ((ECPrivateKeyParameters)dhPair.Private).D.ToByteArrayUnsigned();
            pub = ((ECPublicKeyParameters)dhPair.Public).Q.GetEncoded(true);
        }
        public byte[] GetAgreement(byte[] Privkey, byte[] Pubkey)
        {
            ECPrivateKeyParameters privateKey = new ECPrivateKeyParameters(new BigInteger(Privkey), ecparameters);
            ECPoint pt = ecurve.DecodePoint(Pubkey);
            ECPublicKeyParameters publicKey = new ECPublicKeyParameters(pt, ecparameters);
            ECDHBasicAgreement basicAgreement = new ECDHBasicAgreement();
            basicAgreement.Init(privateKey);
            BigInteger agreement = basicAgreement.CalculateAgreement(publicKey);
            return BigIntegers.AsUnsignedByteArray(agreement);
        }
        ECDomainParameters ecparameters;
        ECCurve ecurve;
        ECPoint epoint;
        BigInteger ecn;
        public EC25519CKA()
        {
            State = new AsymmetricKeyPair() { pub = new byte[KEYSIZE], priv = new byte[KEYSIZE] };
            X9ECParameters ecp = CustomNamedCurves.GetByName("curve25519");
            ecurve = ecp.Curve;
            epoint = ecp.G;
            ecn = ecp.N;
            ecparameters = new ECDomainParameters(ecurve, epoint, ecn);
            InitialGeneration(); /// avoid warning
        }
        private void InitialGeneration()
        {
            // both Alice and Bob have private keys initialized
            // PopulateObject replaces these from JSON
            Generate(ref State.priv, ref State.pub);
        }
        public void Receive(byte[] keymaterial)
        {
            Console.WriteLine($"RUpd => {Convert.ToBase64String(State.priv)} {Convert.ToBase64String(keymaterial)}");
            RUpd = GetAgreement(State.priv, keymaterial);
            Console.WriteLine($"RUpd: {Convert.ToBase64String(RUpd)}");
            keymaterial.CopyTo(State.pub, 0);
        }
        public byte[] Send()
        {
            AsymmetricKeyPair tempPair = new AsymmetricKeyPair() { pub = new byte[State.pub.Length], priv = new byte[State.priv.Length] };
            // this generates and saves new priv, while generating new pub
            Generate(ref State.priv, ref tempPair.pub);
            // this uses previous pub, with new priv
            Console.WriteLine($"SUpd => {Convert.ToBase64String(State.priv)} {Convert.ToBase64String(State.pub)}");
            SUpd = GetAgreement(State.priv, State.pub);
            Console.WriteLine($"SUpd: {Convert.ToBase64String(SUpd)}");
            // sets stores the new pub in SLast
            return (tempPair.pub);
        }
    }
    class MainClass
    {
        public static void Main(string[] args)
        {
            var ecdhA = new EC25519CKA();
            var ecdhB = new EC25519CKA();
            /* you can start with these 4 keys to initialize, to avoid initial exchange error */
            //ecdhA.State.priv = Convert.FromBase64String("BozkoUceQN/5qF8SqhRU4K7UFL3evaQpvWEP4webyto=");
            //ecdhA.State.pub = Convert.FromBase64String("Amui2YWWsuCirkHkrDxiWyv+wasSt8/UJSxWZcF9Tdb/");
            //ecdhB.State.priv = Convert.FromBase64String("CgZJsQ5o0jejASt9ujvGlI2YK4bRSg63pcFlpJKy2es=");
            //ecdhB.State.pub = Convert.FromBase64String("AmrptG+cwVHqQQykUyam37R85hka6pQmyS20ZMUTMgSo");
            /* or just do an initial exchange of keys */
            Exchange(ref ecdhA, ref ecdhB);
            Exchange(ref ecdhB, ref ecdhA);
            for (int i = 0; i < 55; i++)
            {
                Exchange(ref ecdhA, ref ecdhB);
                // At some random point, an exchange error will occur, and then after an initial exchange error it will resume
                if(!Arrays.AreEqual(ecdhA.SUpd, ecdhB.RUpd))
                    Console.WriteLine($"[ERROR] ******************");
                Exchange(ref ecdhB, ref ecdhA);
                if (!Arrays.AreEqual(ecdhB.SUpd, ecdhA.RUpd))
                    Console.WriteLine($"[ERROR] ******************");
            }
        }
        public static void Exchange(ref EC25519CKA ecdh_s, ref EC25519CKA ecdh_r)
        {
            var e1 = ecdh_s.Send();
            ecdh_r.Receive(e1);
        }
    }
}

Attaching runnable console solution. My.zip

clairernovotny commented 6 years ago

Hi @iodev This bug should go on the main BC repo: https://github.com/bcgit/bc-csharp

Bugs here should only be about the packaging of the Portable version. The code is the same as BC, so functional issues belong there.

Thanks