Last active
September 23, 2016 11:16
-
-
Save dhcgn/85b88b516953e8996af8544ee9d7b567 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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