image

Это краткое введение в криптографию под .NET для чайников, как и следует из заголовка. Здесь будут простые вещи и никаких углубленных знаний.


Немного про хэши


Хэширование — это процесс преобразования некоторых входных данных любой длины в массив байтов фиксированной длины.
Хэш представляет собой одностороннюю функцию преобразования, результат работы которой нельзя реверсировать для получения исходных входных данных. Очень часто используется для хранения паролей. Даже если злоумышленник получит хэш, то получить сам пароль не получится. Длина хэша определяется алгоритмом хэширования.
В .NET можно найти следующие алгоритмы хэширования (наследующие абстрактный класс HashAlgorithm):
  • MD5 — длина 128 битов
  • SHA (Secure Hash Algorithm) — такого класса нет, но есть SHA1 (160 битов), SHA256, SHA384, SHA512
  • KeydHashAlgorithm (известные как Message Authentication Code). Представителями является класс алгоритмов HMAC и MACTripleDES

В .NET, если не нужен хэш, основанный на ключе, то, при прочих равных условиях на сегодняшний день, лучше всего использовать алгоритм SHA512. MD5 несколько устарел и может быть скомпрометирован. SHA512 работает очень быстро (уступает только SHA1). Вообще-то всё семейство SHA работает быстро. Самый медленный MACTripleDES. Семейство HMAC работает примерно в два-четыре раза медленнее, чем SHA.

Хэши на основе ключа могут использоваться для защиты данных от модификаций. Запросы посылаемые на сервер с клиента могут проверяться на предмет модификаций. Если данные, передаваемые на сервер были модифицированы, то хэши не совпадут. При защите строковых запросов нельзя использовать простой хэш, без ключа. Потому что, тогда злоумышленник просто переберёт все возможные алгоритмы хэширования, найдёт нужный, модифицирует аргументы в строке запроса и подставит нужный хэш и тогда, серверу ничего не останется, как отвечать на корректно сформированный запрос. Для такой защиты вполне может подойти HMACSHA1 в качестве алгоритма хэширования, основанного на ключе. Разумеется, в полный рост встаёт и проблема хранения секретного ключа.

public static byte[] ComputeHmacsha1(byte[] data, byte[] key)
{
      using (var hmac = new HMACSHA1(key))
      {
            return hmac.ComputeHash(data);
      }
}

Чтобы у злоумышленника оставалось меньше возможностей на сбор входных данных и соответствующих им хэшей для последующего перебора с целью поиска ключа, можно добавить соль.
Соль — это некий рандомизированный дополнительный «ключ», который добавляет энтропии к шифрованию. Соль можно передавать в открытом виде. Соль можно сгенерировать и ассоциировать с текущей сессией пользователя. Таким образом, соль будет меняться каждую сессию и результаты хэширования для одних и тех же запросов будут разные. Соль также добавляют при хэшировании паролей, чтобы усложнить перебор по словарю, особенно если, например, у злоумышленника есть доступ к значениям хэшей. Для генерации соли лучше использовать криптоустойчивый генератор типа RNGCryptoServiceProvider.
Соль можно сгенерировать следующим образом:
string salt = GenSalt(32);
string GenSalt(int length)
{
    RNGCryptoServiceProvider p = new RNGCryptoServiceProvider();
    var salt = new byte[length];
    p.GetBytes(salt);
    return Convert.ToBase64String(salt);
}


Немного про симметричные алгоритмы шифрования


В .NET представлено несколько алгоритмов, наследующих базовый класс SymmetricAlgorithm:
  • Rijndael
  • DES
  • TripleDES
  • RC2

В настоящее время рекомендуемым симметричным алгоритмом в .NET является Rijndael. Также хороши Mars, RC6, Serpent, TwoFish, но в .NET они не реализованы. Если только в сторонних библиотеках. Rijndael представляет собой блочный алгоритм шифрования.
Блочный значит, что входные данные будут нарезаны на блоки и алгоритм шифрования будет применён последовательно к каждому из этих блоков.
Паддинг (Padding) означает процесс «добивания» результатов блочного шифрования для того, чтобы в результате получить требуемый размер.
Rijndael поддерживает несколько режимов паддинга — добивания нулями, добивание случайными значениям и ещё пару режимов. Пожалуй, наиболее надёжный способ, добавляющий энтропии это добивание случайными значениями — это режим ISO10126.
Rijndael также поддерживает несколько способов работы над блоками. По умолчанию, лучше выбрать режим CBC, который означает, что на вход шифрованию следующего блока будут подаваться также результаты шифрования предыдущего блока, что опять же увеличивает энтропию.
IV — initialization vector (вектор инициализации). Нужен для подачи на вход шифрования первого блока. При использовании Rijndael генерируется автоматически. Для дешифрования вектор инициализации знать необходимо и посылать его можно в открытом виде.
Маленький пример:
string Encrypt()
{
   RijndaelManaged cipher = new RijndaelManaged();
   cipher.KeySize = 256;
   cipher.BlockSize = 256; // используйте 128 для совместимости с AES
   cipher.Padding = PaddingMode.ISO10126;
   cipher.Mode = CipherMode.CBC;
   cipher.Key = "some_super_secret_key";
   
   ICryptoTransform t = cipher.CreateEncryptor();
   string text = "some_text_to_encrypt";
   byte[] textInBytes = Encoding.UTF8.GetBytes(text);
   byte[] result = t.TransformFinalBlock(textInBytes, 0, textInBytes.Length);
   return Convert.ToBase64String(result);
}


В .NET также есть интересный класс CryptoStream. Он предназначен для интеграции шифрования с потоками типа FileStream и так далее. Например, в один CryptoStream, который производит шифрование, можно вложить другой CryptoStream, который содержит FileStream. Затем в первый CryptoStream сделать Write, передав текст, и в результате получить шифрованный текст, записанный в некий файл.

Немного про асимметричные алгоритмы шифрования


В случае с асимметричными алгоритмами используется публичный ключ для шифрования и приватный для расшифровки. Если шифрованные сообщения расшифровываются только одним из участников, то закрытый ключ можно хранить только в одном месте. В случае с симметричным шифрованием закрытый ключ приходится делать известным всем участникам обмена сообщениями.
Асимметричные алгоритмы шифрования приблизительно в 100-1000 раз медленнее симметричных. Поэтому большие объёмы данных ими шифровать может оказаться накладно. Асимметричное шифрование может быть использовано вместе с симметричным. С помощью асимметричного шифрования генерируется сессионный ключ, который используется для шифрования данных.
В .NET реализованы следующие основные алгоритмы:
  • RSA
  • DSA (только для цифровых подписей)
  • ECDiffieHellman

Обычно для RSA используют ключи длиной 1024, 2048, 4096 битов. Доказано, что RSA на ключе длиной 1024 — ненадёжен, так что следует использовать более длинные ключи.
Пример использования RSA:
 public class RsaWithCspKey 
    {
        const string ContainerName = "MyContainer";
        public void AssignNewKey()
        {
            CspParameters cspParams = new CspParameters(1);
            cspParams.KeyContainerName = ContainerName;
            cspParams.Flags = CspProviderFlags.UseMachineKeyStore;
            cspParams.ProviderName = "Microsoft Strong Cryptographic Provider";
            var rsa = new RSACryptoServiceProvider(cspParams) { PersistKeyInCsp = true };
        }
        public void DeleteKeyInCsp()
        {
            var cspParams = new CspParameters { KeyContainerName = ContainerName };
            var rsa = new RSACryptoServiceProvider(cspParams) { PersistKeyInCsp = false };
            rsa.Clear();                         
        }
        public byte[] EncryptData(byte[] dataToEncrypt)
        {
            byte[] cipherbytes;
            var cspParams = new CspParameters { KeyContainerName = ContainerName };
            using (var rsa = new RSACryptoServiceProvider(2048, cspParams))
            {
                cipherbytes = rsa.Encrypt(dataToEncrypt, false);
            }
            return cipherbytes;
        }
        public byte[] DecryptData(byte[] dataToDecrypt)
        {
            byte[] plain;
            var cspParams = new CspParameters { KeyContainerName = ContainerName };
            using (var rsa = new RSACryptoServiceProvider(2048, cspParams))
            {                               
                plain = rsa.Decrypt(dataToDecrypt, false);
            }
            return plain;
        }
    }

Цифровые подписи основаны на асимметричном шифровании. Цифровая подпись обеспечивает приватность сообщения и доказывает принадлежность сообщения тому или иному автору. Обычно это работает следующим образом:
1. Алиса шифрует своё сообщение.
2. Алиса берёт хэш своего сообщения.
3. Алиса подписывает сообщение своим приватным ключом для подписывания.
4. Алиса посылает зашифрованные данные, хэш и подпись Бобу.
5. Боб вычисляет хэш зашифрованных данных.
6. Боб проверяет подпись, используя публичный ключ.

Пример использования подписи:

public class DigitalSignature
    {
        private RSAParameters _publicKey;
        private RSAParameters _privateKey;
        public void AssignNewKey()
        {
            using (var rsa = new RSACryptoServiceProvider(2048))
            {                
                rsa.PersistKeyInCsp = false;               
                _publicKey = rsa.ExportParameters(false);
                _privateKey = rsa.ExportParameters(true);                
            }
        }
        public byte[] SignData(byte[] hashOfDataToSign)
        {
            using (var rsa = new RSACryptoServiceProvider(2048))
            {
                rsa.PersistKeyInCsp = false;
                rsa.ImportParameters(_privateKey);
                
                var rsaFormatter = new RSAPKCS1SignatureFormatter(rsa);                
                rsaFormatter.SetHashAlgorithm("SHA256");
                return rsaFormatter.CreateSignature(hashOfDataToSign);
            }
        }
        public bool VerifySignature(byte[] hashOfDataToSign, byte[] signature)
        {
            using (var rsa = new RSACryptoServiceProvider(2048))
            {
                rsa.ImportParameters(_publicKey);
                var rsaDeformatter = new RSAPKCS1SignatureDeformatter(rsa);
                rsaDeformatter.SetHashAlgorithm("SHA256");
                return rsaDeformatter.VerifySignature(hashOfDataToSign, signature);
            }
        }   
    }

Статья представляет собой очень краткий конспект двух курсов с Pluralsight: «Introduction to Cryptography in .NET» и «Practical Cryptography in .NET».

Комментарии (5)


  1. Ivan_83
    20.07.2015 22:12
    +1

    Не думал что в .NET настолько всё уныло с криптопримитивами.
    Не увидел SHA3, AES, ECDSA — это из самого что ни на есть стандартного, я молчу про 25519, чача20 и пр.
    Ринжаель это не всегда AES.

    «SHA512 работает очень быстро (уступает только SHA1).» — Он самый медленный из md5/shaXXX.

    «Семейство HMAC работает примерно в два-четыре раза медленнее, чем SHA.»
    Потому что там хэш берётся два раза, потому и в два раза медленнее.

    «Хэши на основе ключа могут использоваться для защиты данных от модификаций. Запросы посылаемые на сервер с клиента могут проверяться на предмет модификаций. Если данные, передаваемые на сервер были модифицированы, то хэши не совпадут.» — Именно для этого и нужен MAC.

    «При защите строковых запросов нельзя использовать простой хэш, без ключа. Потому что, тогда злоумышленник просто переберёт все возможные алгоритмы хэширования, найдёт нужный, модифицирует аргументы в строке запроса и подставит нужный хэш и тогда, серверу ничего не останется, как отвечать на корректно сформированный запрос.» —
    Некоторые умники при расчёте хэша добавляют секретный ключ к данным, получают хэш и передают строку с хэшем но без ключа, наивно полагая что всё в порядке. Но есть атаки на расширение которые представляют болт для таких умников.
    HMAC не подвержено атаке на расширение.
    Кратко и понятно правильная схема выглядит так: перед отправкой ЗАПРОС на сервер добавляем к нему значение HMAC(СЕКРЕТНЫЙ_КЛЮЧ, ЗАПРОС). Сервер при получении так же рассчитывает hmac и сравнивает с тем что прикреплено к сообщению чтобы удостоверится что отправитель знает секретный ключ и сообщение не было изменено.

    «Соль — это некий рандомизированный дополнительный «ключ», который добавляет энтропии к шифрованию.» — Какоенафиг шифрование в главе про хэши!?

    «Хэши солят» только для того чтобы противостоять подбору по радужным талицам — это когда некто взял и рассчитал значение hash для всех вариантов пределах, скажем 8 символов, отсортировал результат и таким образом может легко и быстро найти иходные данные для данной hash функции.
    Те раньше было модно хранить хэши паролей в базе, типа подбирать долго. Но с такой базой/таблицей это дело считанных секунд/минут.

    «рекомендуемым симметричным алгоритмом в .NET является Rijndael.» — И выбирать то не из чего.

    «Блочный значит, что входные данные будут нарезаны на блоки и алгоритм шифрования будет применён последовательно к каждому из этих блоков.» — А ещё блочный шифр означает что во время шифрования в блоке могут происходить взаимные перестановки и замены по таблице.
    Под ваше определение и чача20 подойдёт, она поточный шифр, но там тоже есть блоки…

    «Паддинг (Padding) означает процесс «добивания» результатов блочного шифрования для того, чтобы в результате получить требуемый размер.» — Наверное не результатов шифрования а открытого текста, ведь блочный шифр на вход может принимать только целые блоки.

    «лучше выбрать режим CBC» — Лучше почитать, а потом выбирать, чтобы не было мучительно больно.

    «IV — initialization vector (вектор инициализации). Нужен для подачи на вход шифрования первого блока.» — Нинужен!
    В том смысле что это частный случай CBC.
    В более общем смысле IV это скорее некоторое расширение ключа.

    «С помощью асимметричного шифрования генерируется сессионный ключ, который используется для шифрования данных.» — … симметричными алгоритмами шифрования.

    «Обычно для RSA используют ключи длиной 1024, 2048, 4096 битов.»
    А я обычно в 16384 бит использовал ключ и считалось оно долго, а потом переехал на ECDSA 521, что как бы эквивалент, но ощутимо быстрее. А сейчас на Ed25519, и ещё быстрее стало. но это в ssh.

    «Цифровые подписи основаны на асимметричном шифровании.» — … и алгоритмах хэширования. Вычисляется хэш сообщения, далее к нему применяется ассиметричная криптография для генерации подписи или её проверки.

    «Цифровая подпись обеспечивает приватность сообщения и доказывает принадлежность сообщения тому или иному автору.» — Зависит от реализации.

    Это у вас курсы такие плохие или мне не понравилось потому что Мойша так напел?)


  1. Qbit
    21.07.2015 11:34
    +3

    >>> Не увидел SHA3, AES, ECDSA

    AES есть, да и всякое другое: msdn.microsoft.com/ru-ru/library/System.Security.Cryptography(v=vs.140).aspx.

    >>> «Хэши солят» только для того чтобы противостоять подбору по радужным талицам

    Хэши солят ещё и для того, чтобы одинаковые пользовательские пароли имели разные хэши.

    >>> В том смысле что это частный случай CBC. В более общем смысле IV это скорее некоторое расширение ключа.

    Не свовсем. Это расширение секретного ключа публично передаваемым куском.

    >>> «IV — initialization vector (вектор инициализации). Нужен для подачи на вход шифрования первого блока.» — Нинужен!

    Нужен. Позволяет избежать replay attacks, если в протоколе обеспечена постоянная смена IV. То есть злоумышленник (даже не понимая зашифрованного сообщения) не сможет организовать его повторную отправку — адресат будет отвергать повторные IV.


    1. EngineerSpock Автор
      21.07.2015 11:42

      Спасибо за адекватный комментарий :)


    1. murzilka
      26.07.2015 21:58

      Хэши солят ещё и для того, чтобы одинаковые пользовательские пароли имели разные хэши.


      А что плохого в одинаковых хешах кроме упомянутой уже возможности вскрытия с помощью радужных таблиц?


      1. Qbit
        26.07.2015 23:49

        >>> А что плохого в одинаковых хешах кроме упомянутой уже возможности вскрытия с помощью радужных таблиц?

        Если злоумышленник знает, что у пользователей john.doe и jackie.brown одинаковые несолёные хэши, то будет уверен, что пароль от пользователя jackie.brown будет подходить к пользователю john.doe. (При этом не обязательно сами пароли будут совпадать, если мы имеем дело с коллизей; и это неважно.) Злоумышленник пойдёт к jackie.brown и купит её пароль за небольшие деньги — ей ведь ничего не стоит раскрыть свой работающий пароль и тут же его сменить на новый, чтоб покупатель старого пароля не имел доступ к её аккаунту. Таким образом в результате злоумышленник будет иметь доступ к аккаунту john.doe, используя для той или иной атаки чужой более уязвимый аккаунт с совпадающим хэшем. Или даже шуточный «терморектальный криптоанализ». Если его применить к john.doe и выпытать пароль, то john.doe всё-таки будет знать, что его пароль скомпрометирован. Но jackie.brown при этом не будет знать, что её конфиденциальность под угрозой. То есть с помощью пыток и криптографической дыры в безопасности можно добиться существенно больше, чем только с помощью пыток.