Skip to content

Instantly share code, notes, and snippets.

@dhcgn
Last active September 23, 2016 11:16
Show Gist options
  • Save dhcgn/85b88b516953e8996af8544ee9d7b567 to your computer and use it in GitHub Desktop.
Save dhcgn/85b88b516953e8996af8544ee9d7b567 to your computer and use it in GitHub Desktop.
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using NUnit.Framework;
using ProtoBuf;
namespace EncryptionSample
{
[ProtoContract]
public class Message : ProtoBase<Message>, IHmacAndData
{
[ProtoMember(1)]
public byte[] Data { get; set; }
[ProtoMember(2)]
public byte[] IV { get; set; }
[ProtoMember(3)]
public byte[] SaltPassword { get; set; }
[ProtoMember(4)]
public byte[] SaltHMAC { get; set; }
[ProtoMember(5)]
public byte[] HMAC { get; set; }
}
public interface IHmacAndData
{
byte[] Data { get; set; }
byte[] HMAC { get; set; }
}
internal class Program
{
public static void Main()
{
byte[] msgFromAlice;
{ // Alice
Console.Out.WriteLine("Alice encrypts \"Hallo Welt\" with the passphrase \"Geheimis\"");
var dataPlain = Encoding.UTF8.GetBytes("Hallo Welt");
var msg = Encryptor.Encrypt("Geheimis", dataPlain);
msgFromAlice = msg.Serialize();
}
// File.WriteAllBytes(@"c:\folder\file.enc", msgFromAlice);
Console.Out.WriteLine($"Alice send this to Bob: {Convert.ToBase64String(msgFromAlice)}");
{ // Bob
var dataEncrypted = Encryptor.Decrypt("Geheimis", Message.Deserialize(msgFromAlice));
var dataEncryptedPlain = Encoding.UTF8.GetString(dataEncrypted);
Console.Out.WriteLine($"Alice encrypts the message \"{dataEncryptedPlain}\"");
}
Console.Out.WriteLine("Press any key ...");
Console.ReadKey();
}
}
[TestFixture]
internal class TestClass
{
const string PlainMsg = "Hallo Welt";
const string Password = "Geheimis";
private static byte[] GetMessageFromAlice()
{
var dataPlain = Encoding.UTF8.GetBytes(PlainMsg);
var msg = Encryptor.Encrypt(Password, dataPlain);
return msg.Serialize();
}
[Test]
public void Sucess()
{
byte[] msgFromAlice = GetMessageFromAlice();
var dataEncrypted = Encryptor.Decrypt(Password, Message.Deserialize(msgFromAlice));
var dataEncryptedPlain = Encoding.UTF8.GetString(dataEncrypted);
Assert.AreEqual(PlainMsg, dataEncryptedPlain);
}
[Test]
public void Tampert_IV()
{
byte[] msgFromAlice = GetMessageFromAlice();
var deserialize = Message.Deserialize(msgFromAlice);
deserialize.IV[0] = 255;
deserialize.IV[1] = 255;
Assert.Throws<CryptographicException>(() =>
{
Encryptor.Decrypt(Password, deserialize);
});
}
[Test]
public void Tampert_HMAC()
{
byte[] msgFromAlice = GetMessageFromAlice();
var deserialize = Message.Deserialize(msgFromAlice);
deserialize.HMAC[0] = 255;
deserialize.HMAC[1] = 255;
Assert.Throws<CryptographicException>(() =>
{
Encryptor.Decrypt(Password, deserialize);
});
}
[Test]
public void Wrong_Password()
{
byte[] msgFromAlice = GetMessageFromAlice();
var deserialize = Message.Deserialize(msgFromAlice);
Assert.Throws<CryptographicException>(() =>
{
Encryptor.Decrypt("WRONG PASSWORD", deserialize);
});
}
[Test]
public void Tampert_SaltPassword()
{
byte[] msgFromAlice = GetMessageFromAlice();
var deserialize = Message.Deserialize(msgFromAlice);
deserialize.SaltPassword[0] = 255;
deserialize.SaltPassword[1] = 255;
Assert.Throws<CryptographicException>(() =>
{
Encryptor.Decrypt(Password, deserialize);
});
}
[Test]
public void Tampert_SaltHmac()
{
byte[] msgFromAlice = GetMessageFromAlice();
var deserialize = Message.Deserialize(msgFromAlice);
deserialize.SaltHMAC[0] = 255;
deserialize.SaltHMAC[1] = 255;
Assert.Throws<CryptographicException>(() =>
{
Encryptor.Decrypt(Password, deserialize);
});
}
[Test]
public void Tampert_Data()
{
byte[] msgFromAlice = GetMessageFromAlice();
var deserialize = Message.Deserialize(msgFromAlice);
deserialize.Data[0] = 255;
deserialize.Data[1] = 255;
Assert.Throws<CryptographicException>(() =>
{
Encryptor.Decrypt(Password, deserialize);
});
}
}
internal class Encryptor
{
#if DEBUG
private const int Iterations = 1;
#else
/// <summary>
/// encryption and decryption will take time, on my rack around 10s.
/// </summary>
private const int Iterations = 100000;
#endif
private const int BlockSize = 128;
private const int KeySize = 256;
/// <summary>
/// Recommended size in MSDN dokumentation
/// </summary>
private const int HmacKeySize = 1024;
private const int SaltSize = 256;
internal static byte[] Decrypt(string password, Message msg)
{
var keyAes = DeriveKey(password, msg.SaltPassword, KeySize);
var keyHmac = DeriveKey(password, msg.SaltHMAC, HmacKeySize);
return DecryptInternal(keyAes, keyHmac, msg.IV, msg.Data, msg.HMAC, msg.SaltPassword, msg.SaltHMAC);
}
private static void Print(string name, byte[] data)
{
Console.Out.WriteLine($"{name} -> {Convert.ToBase64String(data)}");
}
public static Message Encrypt(string password, byte[] dataPlain)
{
var msg = new Message
{
IV = CreateRandomData(BlockSize),
SaltPassword = CreateRandomData(SaltSize),
SaltHMAC = CreateRandomData(SaltSize),
};
var keyAes = DeriveKey(password, msg.SaltPassword, KeySize);
var keyHmac = DeriveKey(password, msg.SaltHMAC, HmacKeySize);
var hmacAndData = EncryptInternal(keyAes, keyHmac, msg.IV, dataPlain, msg.SaltPassword, msg.SaltHMAC);
msg.HMAC = hmacAndData.HMAC;
msg.Data = hmacAndData.Data;
return msg;
}
private static byte[] DecryptInternal(byte[] key, byte[] keyHmac, byte[] iv, byte[] compressed, byte[] savedHmac, byte[] saltPassword, byte[] saltHmac)
{
byte[] plainData;
var hmac = new HMACSHA512(keyHmac);
using (var aesManaged = CreateAesManaged(iv, key))
{
var encryptor = aesManaged.CreateDecryptor(aesManaged.Key, aesManaged.IV);
using (var plainStream = new MemoryStream())
{
using (var aesStream = new CryptoStream(plainStream, encryptor, CryptoStreamMode.Write))
using (var hmacStream = new CryptoStream(aesStream, hmac, CryptoStreamMode.Write))
using (var encryptedStream = new MemoryStream(compressed))
encryptedStream.CopyTo(hmacStream);
plainData = plainStream.ToArray();
}
}
var hmacHash = hmac.Hash;
hmac = new HMACSHA512(keyHmac);
hmacHash = hmac.ComputeHash(CreateOverallHmacKey(hmacHash, iv, saltPassword, saltHmac));
if (!hmacHash.SequenceEqual(savedHmac))
throw new CryptographicException("HMAC not as expected.");
return plainData;
}
private static IHmacAndData EncryptInternal(byte[] keyAes, byte[] keyHmac, byte[] iv, byte[] plainData, byte[] saltPassword, byte[] saltHmac)
{
byte[] encryptedBytes;
var hmac = new HMACSHA512(keyHmac);
using (var aesManaged = CreateAesManaged(iv, keyAes))
{
var encryptor = aesManaged.CreateEncryptor(aesManaged.Key, aesManaged.IV);
using (var resultStream = new MemoryStream())
{
using (var hmacStream = new CryptoStream(resultStream, hmac, CryptoStreamMode.Write))
using (var aesStream = new CryptoStream(hmacStream, encryptor, CryptoStreamMode.Write))
using (var plainStream = new MemoryStream(plainData))
plainStream.CopyTo(aesStream);
encryptedBytes = resultStream.ToArray();
}
}
var hmacHash = hmac.Hash;
hmac = new HMACSHA512(keyHmac);
hmacHash = hmac.ComputeHash(CreateOverallHmacKey(hmacHash, iv, saltPassword, saltHmac));
return new Message {HMAC = hmacHash, Data = encryptedBytes};
}
private static byte[] CreateOverallHmacKey(byte[] hmacHash, byte[] iv, byte[] saltPassword, byte[] saltHmac)
{
return hmacHash.Concat(iv).Concat(saltPassword).Concat(saltHmac).ToArray();
}
private static AesManaged CreateAesManaged(byte[] iv, byte[] key)
{
var aesManaged = new AesManaged
{
Mode = CipherMode.CBC,
Padding = PaddingMode.PKCS7,
KeySize = KeySize,
IV = iv,
Key = key
};
return aesManaged;
}
public static byte[] CreateRandomData(int bits)
{
var randomData = new byte[bits / 8];
new RNGCryptoServiceProvider().GetBytes(randomData);
return randomData;
}
public static byte[] DeriveKey(string password, byte[] salt, int bits)
{
var deriveBytes = new Rfc2898DeriveBytes(password, salt, Iterations);
return deriveBytes.GetBytes(bits / 8);
}
}
public class ProtoBase<T>
{
public static T Deserialize(byte[] data)
{
var ms = new MemoryStream(data);
T result;
try
{
result = Serializer.Deserialize<T>(ms);
}
catch (Exception e)
{
return default(T);
}
return result;
}
public static byte[] Serialize(T proto)
{
var ms = new MemoryStream();
Serializer.Serialize(ms, proto);
return ms.ToArray();
}
}
public static class ProtoBaseExtension
{
public static byte[] Serialize<T>(this T proto) where T : ProtoBase<T>
{
var ms = new MemoryStream();
Serializer.Serialize(ms, proto);
return ms.ToArray();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment