Простая криптография в .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();