Простая криптография в .NET: AES
Криптография. Краткий ликбез
Криптография существует еще с древних времен. Сущесвтует бесконечное количество методов шифрования но все они имеют свои человеческие недостатки. Часто метод анализа сообщения помогает определить что собственно и передается. Поэтому ими серьезно особо не пользуются.
При расцвете ЭВМ в середине ХХ века зародились и первые симметричные алгоритмы. Одни их популярных — AES и DES. Последний из которых сломали в 1990х и государство США настоятельно порекомендовало всем перейти на AES. Поэтому взглянем на AES. Чтобы зашифровать наши данные необходимы две вещи: ключ, вернее, определенная длина байтового массива, и IV (вектор инициализации, initialization vector) который будет использоваться для расшифровки первого блока.
Ключики и PasswordDerivedBytes
Длину ключика лучше выбрать либо 192, либо 256, либо выше если поддерживает шифр. Большая ошибка, которую часто замечаю, это люди вытаскивают ключик методом получения байтов из букв. (byte[] key = Encoding.Unicode.GetBytes("трололо йа хреновый ключег")) Естественно, ошибки здесь на виду: неверная длина ключа, не совсем безопасный подход. Поэтому можно попользоваться утилитой PasswordDerivedBytes из неймспейса System.Security.Cryptography которому можно скормить строку, и соль — порядок доп. мусорных байт которые затруднят попытки атак на наш ключ.
// где password - string содержащий наш пароль, второй аргумент - соль
PasswordDeriveBytes pdb = new PasswordDeriveBytes(Encoding.UTF8.GetBytes(password), new byte[] { 0x21, 0x22, 0x23, 0x24, 0x24, 0xff, 0xaa });
Далее, генерируем подходящию ключик
// РазмерКлюча делить на 8
byte[] key = pdb.GetBytes(256 / 8);
Я не стану настолько углубляться в IV тематику, об этом достаточно хорошо описано в Интернете. Чтобы получить IV, нам необходима строка из 17 случайных букв ASCII.
byte[] iv = Encoding.ASCII.GetBytes("2ccbucsu28772hdh");
Хозяйке на заметку. IV нужно держать в строгом секрете и ни в коем случае не передавать, ну не стараться передавать. Иначе у нас появится еще один WEP. В принципе, шифр достаточно устойчив, что IV можно записать в нашу сборку и особо не беспокоиться.
Впереди, задача: создать универсальный класс которому можно скармливать разные объекты которые он сериализует, шифрует, и выдает нам. Ну и в обратном порядке естественно.
/// <summary>
/// Пакет с шифрованными данными
/// </summary>
[Serializable]
[DataContract]
public class Packet {
/// <summary>
/// Билет который можно передавать чистым текстом, но который не скомпроментирует безопасность данных
/// </summary>
[DataMember]
public string Token {
get;
set;
}
[DataMember]
public byte[] EncryptedData {
get; private set;
}
/// <summary>
/// Задает данные для пакета, шифруя их паролем
/// </summary>
/// <param name="o">Передаваемые данные</param>
/// <param name="password">Пароль</param>
public void Set(object o, string password) {
// откуда мы будем вытаскивать пароль
using (MemoryStream ms = new MemoryStream()) {
// юзаем AES
using (AesManaged AES = new AesManaged()) {
// пароле
PasswordDeriveBytes pdb = new PasswordDeriveBytes(Encoding.UTF8.GetBytes(password), new byte[] { 0x21, 0x22, 0x23, 0x24, 0x24, 0xff, 0xaa });
byte[] iv = Encoding.ASCII.GetBytes("2ccbucsu28772hdh");
byte[] key = pdb.GetBytes(256 / 8);
AES.Mode = CipherMode.CBC;
using (ICryptoTransform enc = AES.CreateEncryptor(key, iv)) {
using (CryptoStream cs = new CryptoStream(ms, enc, CryptoStreamMode.Write)) {
BinaryFormatter bf = new BinaryFormatter();
bf.Serialize(cs, o);
cs.FlushFinalBlock();
}
}
}
this.EncryptedData = ms.ToArray();
}
}
/// <summary>
/// Дешифровка объекта
/// </summary>
/// <typeparam name="T">Тип получаемого объекта</typeparam>
/// <param name="password">Пароль</param>
/// <returns></returns>
public T Get<T>(string password) {
T type = default(T);
PasswordDeriveBytes pdb = new PasswordDeriveBytes(Encoding.UTF8.GetBytes(password), new byte[] { 0x21, 0x22, 0x23, 0x24, 0x24, 0xff, 0xaa });
byte[] iv = Encoding.ASCII.GetBytes("2ccbucsu28772hdh");
byte[] key = pdb.GetBytes(256 / 8);
using (MemoryStream ms = new MemoryStream(this.EncryptedData)) {
using (AesManaged AES = new AesManaged()) {
AES.Mode = CipherMode.CBC;
using (ICryptoTransform enc = AES.CreateDecryptor(key, iv)) {
using (CryptoStream cs = new CryptoStream(ms, enc, CryptoStreamMode.Read)) {
try {
BinaryFormatter bf = new BinaryFormatter();
type = (T)bf.Deserialize(cs);
} catch (Exception ex) {
throw new SerializationException("Невозможно десериализировать объект. См. InnerException", ex);
}
}
}
}
this.EncryptedData = ms.ToArray();
}
return type;
}
}
Пример
Packet pk = new Packet();
string s = "Олололололооло порнуха";
pk.Set(s, "П@р0ль!");
// дешифруем и получаем оригинальную строку
s = pk.Get<string>("П@р0ль!");
Console.WriteLine(s);
// ошибка десериализации т-к пароль неверный, естественно на выходе из шифра появится хрен знает что
s = pk.Get<string>("НеправильныйПароль");
Console.ReadKey();