Clean KeyExchange Implementation [SSL]

05/27/2012 20:15 _DreadNought_#1
Hey,

This is what I think a much cleaner KeyExchange, and yes it's using SSL.

KeyExchange.cs
Code:
using OpenSSL;
using PoisonCO.Core.PacketProcessor.Structures;

namespace PoisonCO.Game.Crypto
{
    public class KeyExchange
    {
        public DH Exchange;
        private readonly string _p, _g;
        public ServerKeyPacket ServerKeyPacket;
        public byte[] ClientIV = new byte[8];
        public byte[] ServerIV = new byte[8];
        private GameCryptographer _cryptography;

        public KeyExchange()
        {
            _p = "E7A69EBDF105F2A6BBDEAD7E798F76A209AD73FB466431E2E7352ED262F8C558F10BEFEA977DE9E21DCEE9B04D245F300ECCBBA03E72630556D011023F9E857F";
            _g = "05";
            Exchange = new DH(BigNumber.FromHexString(_p), BigNumber.FromHexString(_g));
            Exchange.GenerateKeys();
            InitalizeKeyPacket(ServerIV, ClientIV, _p, _g, Exchange.PublicKey.ToHexString());
        }
        public void HandleClientKeyPacket(string pKey, GameClient client)
        {
            _cryptography = client.GameCrypto;
            _cryptography.SetKey(ComputeKey(pKey));
            SetIVs(_cryptography);
        }
        public void SetIVs(GameCryptographer crypto)
        {
            crypto.SetIvs(ClientIV, ServerIV);
        }
        public byte[] ComputeKey(string pKey)
        {
            return Exchange.ComputeKey(BigNumber.FromHexString(pKey));
        }
        public void InitalizeKeyPacket(byte[] serverIV1, byte[] serverIV2, string p, string g, string serverPublicKey)
        {
            ServerKeyPacket = new ServerKeyPacket
                                  {
                                      ServerPublicKey = serverPublicKey,
                                      ServerIV1 = serverIV1,
                                      ServerIV2 = serverIV2,
                                      P = p,
                                      G = g
                                  };
        }
    }
}
ServerKeyPacket.cs
Code:
using System;
using System.IO;

namespace PoisonCO.Core.PacketProcessor.Structures
{
    public class ServerKeyPacket
    {
        private byte[] _serverIV1, _serverIV2;
        private string _p, _g, _serverPublicKey;

        public ServerKeyPacket()
        {
            
        }
        public byte[] ServerIV1
        {
            get { return _serverIV1; }
            set { _serverIV1 = value; }
        }
        public byte[] ServerIV2
        {
            get { return _serverIV2; }
            set { _serverIV2 = value; }
        }
        public string P
        {
            get { return _p; }
            set { _p = value; }
        }
        public string G
        {
            get { return _g; }
            set { _g = value; }
        }
        public string ServerPublicKey
        {
            get { return _serverPublicKey; }
            set { _serverPublicKey = value; }
        }
        public byte[] ToBytes()
        {
            const int padLen = 11;
            const int junkLen = 12;
            const string tqs = "TQServer";
            byte[] packet;
            var ms = new MemoryStream();
            var pad = new byte[padLen];
            Kernel.Random.NextBytes(pad);
            var junk = new byte[junkLen];
            Kernel.Random.NextBytes(junk);
            var size = 47 + _p.Length + _g.Length + _serverPublicKey.Length + 12 + 8 + 8;
            using (var br = new BinaryWriter(ms))
            {
                br.Write(pad);
                br.Write(size - padLen);
                br.Write((UInt32)junkLen);
                br.Write(junk);
                br.Write((UInt32)_serverIV2.Length);
                br.Write(_serverIV2);
                br.Write((UInt32)_serverIV1.Length);
                br.Write(_serverIV1);
                br.Write((UInt32)_p.ToCharArray().Length);
                foreach (var fP in _p.ToCharArray())
                {
                    br.BaseStream.WriteByte((byte)fP);
                }
                br.Write((UInt32)_g.ToCharArray().Length);
                foreach (var fG in _g.ToCharArray())
                {
                    br.BaseStream.WriteByte((byte)fG);
                }
                br.Write((UInt32)_serverPublicKey.ToCharArray().Length);
                foreach (var spk in _serverPublicKey.ToCharArray())
                {
                    br.BaseStream.WriteByte((byte)spk);
                }
                foreach (var tq in tqs.ToCharArray())
                {
                    br.BaseStream.WriteByte((byte)tq);
                }
                packet = ms.ToArray();
                ms.Close();
            }
            return packet;
        }
    }
}
ClientKeyPacket.cs
Code:
using System.IO;
using System.Text;

namespace PoisonCO.Core.PacketProcessor.Structures
{
    public class ClientKeyPacket
    {
        private byte[] _junk1, _junk3;
        private uint _junk2;
        private int _junkLength, _length;
        private byte[] _pKey;
        public ClientKeyPacket(byte[] data)
        {
            var stream = new MemoryStream(data);
            using (var rdr = new BinaryReader(stream))
            {
                //Junk
                _junk1 = rdr.ReadBytes(7);
                _junk2 = rdr.ReadUInt32();
                _junkLength = rdr.ReadInt32();
                _junk3 = rdr.ReadBytes(_junkLength);

                //Useful stuff
                _length = rdr.ReadInt32();
                _pKey = rdr.ReadBytes(_length);
                stream.Close();
            }
        }

        public byte[] Junk1
        {
            get { return _junk1; }
        }
        public uint Junk2
        {
            get { return _junk2;  }
        }
        public int JunkLength
        {
            get { return _junkLength; }
        }
        public byte[] Junk3
        {
            get { return _junk3; }
        }
        public int Length
        {
            get { return _length; }
        }
        public string PublicKey
        {
            get { return Encoding.ASCII.GetString(_pKey); }
        }
    }
}
Usage:
Upon connection to the game server
Code:
Client.Send(Client.Exchange.ServerKeyPacket.ToBytes());
How I handle it:
Code:
switch (Client.IsExchanging)
            {
                case false:
                    {
                        Client.IsExchanging = true;
                        var pkt = new ClientKeyPacket(Packet);
                        Client.Exchange.HandleClientKeyPacket(pkt.PublicKey, Client);
                        break;
                    }
                case true:
                    {
                        Core.PacketProcessor.Process.PacketSplitter(Packet, Client);
                        break;
                    }
            }
Enjoy.
05/27/2012 20:17 Ultimation#2
very nice release DN, though u dont need a switch statement for a bool :) If(!)
but otherwise, nice and clean :)
05/27/2012 20:20 _DreadNought_#3
Quote:
Originally Posted by Ultimation View Post
very nice release DN, though u dont need a switch statement for a bool :) If(!)
but otherwise, nice and clean :)
Thanks, and yeah, I simply went with a case statement due to the fact I personally prefer'd it and I cannot see any performance issues with it(I don't count 0.1ms a issue), so therefore I just went with personal preference.
05/28/2012 08:36 Arabian[GM]#4
Hello

Is it possible to explain more about it?
05/28/2012 09:58 .Kinshi#5
Looks good buddy!
05/28/2012 10:32 _DreadNought_#6
Quote:
Originally Posted by Arabian[GM] View Post
Hello

Is it possible to explain more about it?
This is the Diffie Hellman Exchange, - Dreadful spelling.

It is a KeyExchange the client & server take part in, The client sends some information on GameReceive before sending an actual game packet that the server can then generate a valid reply & set up the CAST5 Encryption correctly(sets its keys), This implementation is a much clearner & sleek implementation that what I've seen - Using OpenSSL, I don't actually have the BIgNumber & DH c# class out of OpenSSL, Nor could I find it or be bothered to open it up in BouncyCastle.

This also has packet structures unlike most KeyExchanges I've seen.

If you want to find out more just goole DH KeyExchange.
05/28/2012 21:30 Sp!!ke#7
hmm that is usefull :) thank DN for sharing with us...
05/28/2012 22:53 _DreadNought_#8
Just notice how this > Other SSL Implementation & includes packet structures for the exchange :)
05/29/2012 01:03 Korvacs#9
This looks basically the same as every other key exchange ive ever seen, how is this different?

Comparing this to other existing examples this is actually more messy and less readable?
05/29/2012 01:53 pro4never#10
Quote:
Originally Posted by Korvacs View Post
This looks basically the same as every other key exchange ive ever seen, how is this different?

Comparing this to other existing examples this is actually more messy and less readable?
I think it's because he standardized it more towards packet structures people are familiar with. I agree it's more messy then some of the less... common implementations but it's definitely clearer and more organized then many of the public sources out there. Sort of a middle ground.
05/29/2012 02:11 Spirited#11
Exchange shouldn't be checked for every single packet being handled. It should be handled only on the first packet receive in a separate socket event. In other words, Begin Receive using one method, then at the end of the exchange method, begin receive with another method for handling packets.
05/29/2012 02:18 pro4never#12
Quote:
Originally Posted by Fаng View Post
Exchange shouldn't be checked for every single packet being handled. It should be handled only on the first packet receive in a separate socket event. In other words, Begin Receive using one method, then at the end of the exchange method, begin receive with another method for handling packets.
^

Has always bugged me how few sources handle it incorrectly (my own former work being part of the problem)
05/29/2012 08:44 _DreadNought_#13
Quote:
Originally Posted by Korvacs View Post
This looks basically the same as every other key exchange ive ever seen, how is this different?

Comparing this to other existing examples this is actually more messy and less readable?
Let's compare shall we?

Code:
using OpenSSL;
using PoisonCO.Core.PacketProcessor.Structures;

namespace PoisonCO.Game.Crypto
{
    public class KeyExchange
    {
        public DH Exchange;
        private readonly string _p, _g;
        public ServerKeyPacket ServerKeyPacket;
        public byte[] ClientIV = new byte[8];
        public byte[] ServerIV = new byte[8];
        private GameCryptographer _cryptography;

        public KeyExchange()
        {
            _p = "E7A69EBDF105F2A6BBDEAD7E798F76A209AD73FB466431E2E7352ED262F8C558F10BEFEA977DE9E21DCEE9B04D245F300ECCBBA03E72630556D011023F9E857F";
            _g = "05";
            Exchange = new DH(BigNumber.FromHexString(_p), BigNumber.FromHexString(_g));
            Exchange.GenerateKeys();
            InitalizeKeyPacket(ServerIV, ClientIV, _p, _g, Exchange.PublicKey.ToHexString());
        }
        public void HandleClientKeyPacket(string pKey, GameClient client)
        {
            _cryptography = client.GameCrypto;
            _cryptography.SetKey(ComputeKey(pKey));
            SetIVs(_cryptography);
        }
        public void SetIVs(GameCryptographer crypto)
        {
            crypto.SetIvs(ClientIV, ServerIV);
        }
        public byte[] ComputeKey(string pKey)
        {
            return Exchange.ComputeKey(BigNumber.FromHexString(pKey));
        }
        public void InitalizeKeyPacket(byte[] serverIV1, byte[] serverIV2, string p, string g, string serverPublicKey)
        {
            ServerKeyPacket = new ServerKeyPacket
                                  {
                                      ServerPublicKey = serverPublicKey,
                                      ServerIV1 = serverIV1,
                                      ServerIV2 = serverIV2,
                                      P = p,
                                      G = g
                                  };
        }
    }
}
Code:
using System;
using System.IO;
using System.Text;

namespace Conquer_Online_Server.Network.GamePackets
{
    public static class DHKeyExchange
    {
        public class ServerKeyExchange
        {
            OpenSSL.DH _keyExchange;
            byte[] _serverIv;
            byte[] _clientIv;

            public byte[] CreateServerKeyPacket()
            {
                _clientIv = new byte[8];
                _serverIv = new byte[8];
                //string P = "E7A69EBDF105F2A6BBDEAD7E798F76A209AD73FB466431E2E7352ED262F8C558F10BEFEA977DE9E21DCEE9B04D245F300ECCBBA03E72630556D011023F9E857F";
                string P = "A320A85EDD79171C341459E94807D71D39BB3B3F3B5161CA84894F3AC3FC7FEC317A2DDEC83B66D30C29261C6492643061AECFCF4A051816D7C359A6A7B7D8FB";
                string G = "05";
                _keyExchange = new OpenSSL.DH(OpenSSL.BigNumber.FromHexString(P), OpenSSL.BigNumber.FromHexString(G));
                _keyExchange.GenerateKeys();
                return GeneratePacket(_serverIv, _clientIv, P, G, _keyExchange.PublicKey.ToHexString());
            }

            public Cryptography.GameCryptography HandleClientKeyPacket(string PublicKey, Cryptography.GameCryptography cryptographer)
            {
                byte[] key = _keyExchange.ComputeKey(OpenSSL.BigNumber.FromHexString(PublicKey));
                cryptographer.SetKey(key);
                cryptographer.SetIvs(_clientIv, _serverIv);
                return cryptographer;
            }
            public byte[] GeneratePacket(byte[] ServerIV1, byte[] ServerIV2, string P, string G, string ServerPublicKey)
            {
                int PAD_LEN = 11;
                int _junk_len = 12;
                string tqs = "TQServer";
                MemoryStream ms = new MemoryStream();
                byte[] pad = new byte[PAD_LEN];
                ServerBase.Kernel.Random.NextBytes(pad);
                byte[] junk = new byte[_junk_len];
                ServerBase.Kernel.Random.NextBytes(junk);
                int size = 47 + P.Length + G.Length + ServerPublicKey.Length + 12 + 8 + 8;
                BinaryWriter bw = new BinaryWriter(ms);
                bw.Write(pad);
                bw.Write(size - PAD_LEN);
                bw.Write((UInt32)_junk_len);
                bw.Write(junk);
                bw.Write((UInt32)ServerIV2.Length);
                bw.Write(ServerIV2);
                bw.Write((UInt32)ServerIV1.Length);
                bw.Write(ServerIV1);
                bw.Write((UInt32)P.ToCharArray().Length);
                foreach (char fP in P.ToCharArray())
                {
                    bw.BaseStream.WriteByte((byte)fP);
                }
                bw.Write((UInt32)G.ToCharArray().Length);
                foreach (char fG in G.ToCharArray())
                {
                    bw.BaseStream.WriteByte((byte)fG);
                }
                bw.Write((UInt32)ServerPublicKey.ToCharArray().Length);
                foreach (char SPK in ServerPublicKey.ToCharArray())
                {
                    bw.BaseStream.WriteByte((byte)SPK);
                }
                foreach (char tq in tqs.ToCharArray())
                {
                    bw.BaseStream.WriteByte((byte)tq);
                }
                byte[] Packet = new byte[ms.Length];
                Packet = ms.ToArray();
                ms.Close();
                return Packet;
            }
        }
        public class ClientKeyPacket
        {
            private static int PAD_LEN = 7;
            private uint _junk_len;
            string _publicKey;

            public ClientKeyPacket(byte[] buffer)
            {
                MemoryStream ms = new MemoryStream(buffer);
                BinaryReader br = new BinaryReader(ms);
                br.BaseStream.Seek(PAD_LEN, SeekOrigin.Begin);
                uint len = br.ReadUInt32();
                _junk_len = br.ReadUInt32();
                byte[] junk = br.ReadBytes((int)_junk_len);
                _publicKey = Encoding.ASCII.GetString(br.ReadBytes(br.ReadInt32()));
            }

            public string PublicKey
            {
                get { return _publicKey; }
            }
        }
    }
}
Looks abit different to me, and messy? That's your opinion, but I am going to have to say, that is no way near as messy as the 2nd example, and if you can find a better one, then provide it as evidence or its pointless making a comment really.

@Fang & @Pro4Never: As for doing the check, meh, I did actually consider that but I guess I just got lazy, I wasn't really worried about the extra 0.01ms in time to be honest.
05/29/2012 09:18 I don't have a username#14
I only dislike the fact you used a stream.
05/29/2012 09:19 Korvacs#15
Code:
public class KeyExchange
    {
        public ClientKeyPacket CKeyPacket;
        public byte[] ClientIV = new byte[8];
        public DH Exchange;
        public bool Exchanged;
        public byte[] ServerIV = new byte[8];
        public ServerKeyPacket SKeyPacket;

        public KeyExchange()
        {
            Random random = new Random();
            random.NextBytes(this.ServerIV);
            random.NextBytes(this.ClientIV);
            string str = "E7A69EBDF105F2A6BBDEAD7E798F76A209AD73FB466431E2E7352ED262F8C558F10BEFEA977DE9E21DCEE9B04D245F300ECCBBA03E72630556D011023F9E857F";
            string str2 = "05";
            this.Exchange = new DH(BigNumber.FromHexString(str), BigNumber.FromHexString(str2));
            this.Exchange.GenerateKeys();
            this.SKeyPacket = new ServerKeyPacket(this.ClientIV, this.ServerIV, str, str2, this.Exchange.PublicKey.ToHexString());
        }

        public void HandleClientKeyPacket(ClientKeyPacket CKeyPacket, GameCryptographer Cryptographer)
        {
            Cryptographer.Blowfish.SetKey(this.Exchange.ComputeKey(BigNumber.FromHexString(CKeyPacket.PublicKey)));
            Cryptographer.Blowfish.EncryptIV = new byte[8];
            Cryptographer.Blowfish.DecryptIV = new byte[8];
        }

        public void ResetIVs(GameCryptographer Cryptographer)
        {
            Cryptographer.Blowfish.DecryptIV = new byte[8];
            Cryptographer.Blowfish.EncryptIV = new byte[8];
        }
    }
^ Publicly available.