Jump to content

  • Log In with Google      Sign In   
  • Create Account


MatsK

Member Since 21 Feb 2008
Offline Last Active May 23 2014 12:55 PM
-----

Posts I've Made

In Topic: Cryptography - am I doing it right?

19 April 2014 - 11:53 PM


I'd be worried about your use of GUIDs for nonces and challenges - they are not cryptographically secure.

 

Ah, thanks! What can I use instead?

 


Also, Diffie-Hellman is vulnerable against a man in the middle simply interspersing himself, and running one D-H exchange to the client, and another D-H exchange to the server, decrypting and re-encrypting everything in between. You need public/private keys to exchange AND SIGN the session key to defend against that. Which, btw, is included in TLS.

 

I thought EC DH solved this issue?


However, if you are doing that for multi-byte values anywhere (ReadPascalString, perhaps?) you might allow a malicious client to make you to do an awful lot of work just by sending packets with large usernames / GUID strings.

 

The internal packet buffer has an upper limit of 11kb, but point taken! ;)


In Topic: Cryptography - am I doing it right?

12 April 2014 - 05:30 PM

Like I said: I don't care about protocol encryption, only secure authentication.

At least I know that there's no way in hell anyone can get a user's password, or even hash, because now it isn't being sent across.

What do you mean "can he read data on your client"?

 

I was thinking about that, and I was wondering: does it matter if I hardcode the server's public key into the client before releasing a binary, or simply distribute the server's public key as a standalone file? The way I see it, if an attacker wants the server's public key, he's going to be able to get it either way.

 

Now that I think about it, EC Diffie Hellman is still vulnerable to attack, because an attacker could use malware to get another user's key in-memory, and then he'd have the ability to impersonate that user. But TLS would also be vulnerable to this kind of attack, no?


In Topic: Cryptography - am I doing it right?

11 April 2014 - 05:21 PM

hplus: I guess what I meant was - are there any obvious flaws/weaknesses in the code? It looks to me like it should do what samoth prescribed, but I'd like an informed answer!

As far as AES en/decryption, that seems to work just fine in the sample, but to be honest I don't even care about that. The protocol doesn't contain any sensitive information as such. The only thing I really care about is secure authentication (again, without using TLS because it takes half a PhD to figure out how to use it).

 

Also, is it OK to send the nonce unencrypted from client to server?

 


In Topic: Cryptography - am I doing it right?

11 April 2014 - 06:02 AM

Ok, time for peer review!

Here's my code:

using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Cryptography;
using System.IO;
using GonzoNet;

namespace CryptoSample
{
    public class PacketHandlers
    {
        //Not sure why, but both keys must derive from the same master for decryption to work.
        private static CngKey MasterKey = CngKey.Create(CngAlgorithm.ECDiffieHellmanP256);
        public static ECDiffieHellmanCng ClientKey = new ECDiffieHellmanCng(MasterKey), 
            ServerKey = new ECDiffieHellmanCng(MasterKey);

        private static byte[] SessionKey, IV;
        private static Guid ChallengeResponse = Guid.NewGuid();
        public static Guid ClientNOnce = Guid.NewGuid();

        //This will be generated when the client sends the first packet.
        public static byte[] ClientIV;

        /// <summary>
        /// Helper method to generate an Initialization Vector for the client.
        /// </summary>
        public static void GenerateClientIV()
        {
            AesCryptoServiceProvider AES = new AesCryptoServiceProvider();
            AES.GenerateIV();
            ClientIV = AES.IV;
        }

        /// <summary>
        /// A client requested login.
        /// </summary>
        /// <param name="Client">NetworkClient instance.</param>
        /// <param name="Packet">ProcessedPacket instance.</param>
        public static void InitialClientConnect(NetworkClient Client, ProcessedPacket Packet)
        {
            Console.WriteLine("Server receives data - test 1");

            //AES is used to encrypt all further communication between client and server.
            AesCryptoServiceProvider AesCrypto = new AesCryptoServiceProvider();
            AesCrypto.GenerateKey();
            AesCrypto.GenerateIV();
            AES AesEncryptor = new AES(AesCrypto.Key, AesCrypto.IV);
            SessionKey = AesCrypto.Key;
            IV = AesCrypto.IV;

            Guid Nonce = new Guid(Packet.ReadPascalString());

            //Username would normally be used to lookup client's public key in DB (only time such a use is valid).
            string Username = Packet.ReadPascalString();
            ECDiffieHellmanPublicKey ClientPub = StaticStaticDiffieHellman.ImportKey("ClientPublic.dat");

            PacketStream EncryptedPacket = new PacketStream(0x02, 0);
            EncryptedPacket.WriteHeader();

            MemoryStream StreamToEncrypt = new MemoryStream();
            BinaryWriter Writer = new BinaryWriter(StreamToEncrypt);
            Writer.Write((byte)ChallengeResponse.ToByteArray().Length);
            Writer.Write(ChallengeResponse.ToByteArray(), 0, ChallengeResponse.ToByteArray().Length);
            Writer.Write((byte)SessionKey.Length);
            Writer.Write(SessionKey, 0, SessionKey.Length);
            Writer.Write((byte)IV.Length);
            Writer.Write(IV, 0, IV.Length);
            Writer.Flush();

            byte[] EncryptedData = StaticStaticDiffieHellman.Encrypt(ServerKey, ClientPub, Nonce.ToByteArray(), 
                StreamToEncrypt.ToArray());

            EncryptedPacket.WriteUInt16((ushort)(PacketHeaders.UNENCRYPTED + 1 + EncryptedData.Length));

            EncryptedPacket.WriteByte((byte)EncryptedData.Length);
            EncryptedPacket.Write(EncryptedData, 0, EncryptedData.Length);

            Client.Send(EncryptedPacket.ToArray());

            Console.WriteLine("Test 1: passed!");
        }

        /// <summary>
        /// Initial response from server to client.
        /// </summary>
        /// <param name="Client">A NetworkClient instance.</param>
        /// <param name="Packet">A ProcessedPacket instance.</param>
        public static void HandleServerChallenge(NetworkClient Client, ProcessedPacket Packet)
        {
            Console.WriteLine("Client receives encrypted data - test 2");

            byte[] PacketBuf = new byte[Packet.ReadByte()];
            Packet.Read(PacketBuf, 0, (int)PacketBuf.Length);

            ECDiffieHellmanPublicKey ServerPub = StaticStaticDiffieHellman.ImportKey("ServerPublic.dat");

            MemoryStream DecryptedStream = new MemoryStream(StaticStaticDiffieHellman.Decrypt(ClientKey, ServerPub, 
                ClientNOnce.ToByteArray(), PacketBuf));
            BinaryReader Reader = new BinaryReader(DecryptedStream);

            Guid ChallengeResponse = new Guid(Reader.ReadBytes(Reader.ReadByte()));
            SessionKey = Reader.ReadBytes(Reader.ReadByte());
            IV = Reader.ReadBytes(Reader.ReadByte());

            //Yay, we have key and IV, we can now start encryption with AES!
            AES AesEncryptor = new AES(SessionKey, IV);

            PacketStream EncryptedPacket = new PacketStream(0x03, 0);
            EncryptedPacket.WriteHeader();

            MemoryStream StreamToEncrypt = new MemoryStream();
            BinaryWriter Writer = new BinaryWriter(StreamToEncrypt);
            Writer.Write((byte)ChallengeResponse.ToByteArray().Length);
            Writer.Write(ChallengeResponse.ToByteArray(), 0, ChallengeResponse.ToByteArray().Length);

            //Encrypt data using key and IV from server, hoping that it'll be decrypted correctly at the other end...
            byte[] EncryptedData = AesEncryptor.Encrypt(StreamToEncrypt.ToArray());

            EncryptedPacket.WriteUInt16((ushort)(PacketHeaders.UNENCRYPTED + EncryptedData.Length + 1));
            EncryptedPacket.WriteByte((byte)EncryptedData.Length);
            EncryptedPacket.Write(EncryptedData, 0, EncryptedData.Length);

            Client.Send(EncryptedPacket.ToArray());

            Console.WriteLine("Test 2: passed!");
        }

        public static void HandleChallengeResponse(NetworkClient Client, ProcessedPacket Packet)
        {
            Console.WriteLine("Server receives challenge response - test 3");

            byte[] PacketBuf = new byte[Packet.ReadByte()];
            Packet.Read(PacketBuf, 0, (int)PacketBuf.Length);

            AES AesEncryptor = new AES(SessionKey, IV);
            MemoryStream DecryptedStream = new MemoryStream(AesEncryptor.Decrypt(PacketBuf));
            BinaryReader Reader = new BinaryReader(DecryptedStream);

            byte[] CResponseBuf = Reader.ReadBytes(Reader.ReadByte());
            Guid CResponse = new Guid(CResponseBuf);

            if (CResponse.CompareTo(ChallengeResponse) == 0)
                Console.WriteLine("Received correct challenge response, client was authenticated!");

            Console.WriteLine("Test 3: passed!");
        }
    }
}

ECDH:

using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;
using System.IO;

namespace CryptoSample
{
    /// <summary>
    /// Contains methods for en/decryption and ex/importing keys.
    /// From: http://stackoverflow.com/questions/3196297/minimal-message-size-public-key-encryption-in-net
    /// </summary>
    public static class StaticStaticDiffieHellman
    {
        private static Aes DeriveKeyAndIv(ECDiffieHellmanCng privateKey, ECDiffieHellmanPublicKey publicKey, byte[] nonce)
        {
            privateKey.KeyDerivationFunction = ECDiffieHellmanKeyDerivationFunction.Hash;
            privateKey.HashAlgorithm = CngAlgorithm.Sha256;
            privateKey.SecretAppend = nonce;
            byte[] keyAndIv = privateKey.DeriveKeyMaterial(publicKey);
            byte[] key = new byte[16];
            Array.Copy(keyAndIv, 0, key, 0, 16);
            byte[] iv = new byte[16];
            Array.Copy(keyAndIv, 16, iv, 0, 16);

            Aes aes = new AesManaged();
            aes.Key = key;
            aes.IV = iv;
            aes.Mode = CipherMode.CBC;
            aes.Padding = PaddingMode.PKCS7;

            return aes;
        }

        public static byte[] Encrypt(ECDiffieHellmanCng privateKey, ECDiffieHellmanPublicKey publicKey, byte[] nonce, byte[] data)
        {
            Aes aes = DeriveKeyAndIv(privateKey, publicKey, nonce);
            return aes.CreateEncryptor().TransformFinalBlock(data, 0, data.Length);
        }

        public static byte[] Decrypt(ECDiffieHellmanCng privateKey, ECDiffieHellmanPublicKey publicKey, byte[] nonce, byte[] encryptedData)
        {
            Aes aes = DeriveKeyAndIv(privateKey, publicKey, nonce);
            return aes.CreateDecryptor().TransformFinalBlock(encryptedData, 0, encryptedData.Length);
        }

        public static void ExportKey(string Path, ECDiffieHellmanPublicKey Key)
        {
            using (BinaryWriter Writer = new BinaryWriter(File.Create(Path)))
            {
                Writer.Write((byte)Key.ToByteArray().Length);
                Writer.Write(Key.ToByteArray());
            }
        }

        public static ECDiffieHellmanPublicKey ImportKey(string Path)
        {
            ECDiffieHellmanPublicKey Key;
            using (BinaryReader Reader = new BinaryReader(File.Open(Path, FileMode.Open)))
            {
                Key = ECDiffieHellmanCngPublicKey.FromByteArray(Reader.ReadBytes(Reader.ReadByte()), CngKeyBlobFormat.EccPublicBlob);
                return Key;
            }
        }
    }
}

AES:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CryptoSample
{
    using System;
    using System.Text;
    using System.Security.Cryptography;

    /// <summary>
    /// AES class is derived from the MSDN .NET CreateEncryptor() example
    /// http://msdn.microsoft.com/en-us/library/09d0kyb3.aspx
    /// </summary>
    class AES
    {
        // Symmetric algorithm interface is used to store the AES service provider
        private SymmetricAlgorithm AESProvider;

        // Crytographic transformers are used to encrypt and decrypt byte arrays
        private ICryptoTransform encryptor;
        private ICryptoTransform decryptor;

        /// <summary>
        /// Constructor for AES class that takes byte arrays for the key and IV
        /// </summary>
        /// <param name="key">Cryptographic key</param>
        /// <param name="IV">Cryptographic initialization vector</param>
        public AES(byte[] key, byte[] IV)
        {
            // Initialize AESProvider with AES service provider
            AESProvider = new AesCryptoServiceProvider();

            // Set the key and IV for AESProvider
            AESProvider.Key = key;
            AESProvider.IV = IV;

            // Initialize cryptographic transformers from AESProvider
            encryptor = AESProvider.CreateEncryptor();
            decryptor = AESProvider.CreateDecryptor();
        }

        /// <summary>
        /// Constructor for AES class that generates the key and IV from salted passwords
        /// </summary>
        /// <param name="keyPassword">Password used to generate the key</param>
        /// <param name="IVPassword">Password used to generate the IV</param>
        /// <param name="salt">Salt used to secure the passwords</param>
        public AES(string keyPassword, string IVPassword, string salt)
        {
            // Initialize AESProvider with AES service provider
            AESProvider = new AesCryptoServiceProvider();

            // Initialize a hasher with the default MD5 algorithm
            MD5 md5 = System.Security.Cryptography.MD5.Create();

            // Generate the key and IV for AESProvider from hashed, salted passwords
            AESProvider.Key = md5.ComputeHash(UnicodeEncoding.Unicode.GetBytes(keyPassword + salt));
            AESProvider.IV = md5.ComputeHash(UnicodeEncoding.Unicode.GetBytes(IVPassword + salt));

            // Initialize cryptographic transformers from AESProvider
            encryptor = AESProvider.CreateEncryptor();
            decryptor = AESProvider.CreateDecryptor();
        }

        /// <summary>
        /// Encrypts a string with AES
        /// </summary>
        /// <param name="plainText">String to encrypt</param>
        /// <returns>Encrypted string</returns>
        public string Encrypt(string plainText)
        {
            // Convert string to bytes
            byte[] plainBytes = UnicodeEncoding.Unicode.GetBytes(plainText);

            // Encrypt bytes
            byte[] secureBytes = encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);

            // Return encrypted bytes as a string
            return UnicodeEncoding.Unicode.GetString(secureBytes);
        }

        /// <summary>
        /// Encrypts a byte array with AES
        /// </summary>
        /// <param name="plainBytes">Data to encrypt</param>
        /// <returns>Encrypted byte array</returns>
        public byte[] Encrypt(byte[] plainBytes)
        {
            // Encrypt bytes
            return encryptor.TransformFinalBlock(plainBytes, 0, plainBytes.Length);
        }

        /// <summary>
        /// Decrypts a string with AES
        /// </summary>
        /// <param name="secureText">Encrypted string to decrypt</param>
        /// <returns>Decrypted string</returns>
        public string Decrypt(string secureText)
        {
            // Convert encrypted string to bytes
            byte[] secureBytes = UnicodeEncoding.Unicode.GetBytes(secureText);

            // Decrypt bytes
            byte[] plainBytes = decryptor.TransformFinalBlock(secureBytes, 0, secureBytes.Length);

            // Return decrypted bytes as a string
            return UnicodeEncoding.Unicode.GetString(plainBytes);
        }

        /// <summary>
        /// Decrypts data with AES
        /// </summary>
        /// <param name="secureBytes">Encrypted data to decrypt</param>
        /// <returns>Decrypted data</returns>
        public byte[] Decrypt(byte[] secureBytes)
        {
            // Decrypt bytes
            return decryptor.TransformFinalBlock(secureBytes, 0, secureBytes.Length);
        }
    }
}

Comments, please! Is this safe?


In Topic: Cryptography - am I doing it right?

29 March 2014 - 04:20 AM

 


I will spend the weekend trying to fork out some code that can implement this protocol.

 

I would still be concerned that it doesn't actually solve your security problems.

 

You are looking for an encrypted communications channel. That is analogous to hiring an armored vehicle delivery service. Very nice, you have protected one thing. On your server side you have a nice environment that you control. The armored car will be delivering the package to BadGuysRUs, and will be returning a package using the same armored vehicle service.

 
Let's assume you do implement a rock-solid TLS implementation.  Communication between the two endpoints is moderately secure; TLS interception proxies are very common in corporate, academic, and other computing environments. Your connection with the corporate firewall is secure, they re-encode it with the company-mandated certificate, and it works just fine.  Using your own encryption system is much more effort and is extremely difficult to get right, but it can get through a TLS proxy securely.  That is just one item on a long list of security.
 
I have also heard security described as a fence. Communications encryption is a single fencepost. It is high and very strong, but still just a single post that can be easily walked around.
 
That kind of encryption does have many valid uses. If you are talking with a bank to check your account balance and do transfers, it makes it difficult to eavesdrop on the session to learn your bank balance or to add some extra money to a money transfer. The bank assumes that you are in charge of your end of the connection; they don't have protection against keyloggers or compromised web browsers or someone with a gun to your head. It only protects in-transit communication, but for banking that is the critical part.
 
 
Before trying to secure your application, you need to figure out exactly what attacks you are trying to prevent. Encrypting the communication reduces the risk of a "robbing the stagecoach" type of attack. While it is more common for financial institutions, that type of attack is extremely rare in games. The most common attack is aimed against the client itself; why attack the stagecoach when you have complete ownership of one endpoint? The bad guys can just attach a debugger or run inside a sandbox environment and have perfect access to everything.

 

 

Again, this game is open source, so attackers already have an open playing field. My main concern is to try to prevent attacks of the type where "robbing the stagecoach" will allow men in the middle to get away with users' passwords. Now, I know that social engineering is a much more effective way to get users' passwords, but I can't do anything about that. I can however protect against MiM attacks.

I'm also not using TLS, I'm taking samoth's advice and trying to aim for Elliptic Curve Diffie Hellman. I'll probably post the code here for peer review, and if it doesn't look good, I'll just revert to hplus' advice and use AES.

 

hplus0603: It would actually be physically impossible for anyone to take a dump of my DB! :) At least as of yet, because I'm running the login server and city (game) server on the same box, and thus the DB isn't connected to the intarwebs.


PARTNERS