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?