Эта обертка вокруг AesManaged в порядке?

Мне нужно зашифровать / расшифровать некоторые строки. Я создал свой класс-оболочку в соответствии с документация msdn, но с некоторыми изменениями.

Поскольку я хочу зашифровать / расшифровать данные с помощью заданной строки / парольной фразы, я не использую AesManaged для создания ключа. (Пользователь должен иметь возможность шифровать / дешифровать ключом, который он вводит, поэтому я не могу использовать ключ из AesManaged и не могу сохранить ключ).

Вместо этого я создаю ключ, используя Rfc2898DeriveBytes (PBKDF2) с заданной солью. Данная соль используется, так как я не храню ключ, и я думаю, из-за этого соль всегда должна быть одинаковой.

Затем я создаю IV, шифрую данную строку и объединяю IV и зашифрованную строку. В конечном итоге это будет сохранено в файле. Это означает, что IV сохраняется вместе с зашифрованными данными.

Вопросы:

  1. Можно ли хранить IV вместе с зашифрованными данными?
  2. Есть ли другой способ создать ключ, не используя каждый раз одну и ту же соль (на основе заданной кодовой фразы)?
  3. Это шифрование выполняется с использованием AES128 или AES256?
  4. Будет ли IV всегда 16 байтов, или это может измениться?

    static void Main(string[] args)
    {
        const string stringToEncrypt = "String to be encrypted/decrypted. Encryption is done via AesManaged";
        const string password = "m1Sup3rS3cre!Password";

        string encrypted = EncryptString(stringToEncrypt, password);
        string roundtrip = DecryptStringFromBytes_Aes(encrypted, password);

        Console.WriteLine("Original:   {0}", stringToEncrypt);
        Console.WriteLine("Round Trip: {0}", roundtrip);

        Console.ReadLine();
    }

    static string EncryptString(string plainText, string password)
    {
        string encryptedString;

        using (AesManaged aesAlg = new AesManaged())
        {
            aesAlg.Key = PasswordAsByte(password);
            ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);

            using (MemoryStream msEncrypt = new MemoryStream())
            {
                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                    {
                        swEncrypt.Write(plainText);
                    }
                    var encrypted = msEncrypt.ToArray();

                    encryptedString = Encoding.Default.GetString(aesAlg.IV);
                    encryptedString += Encoding.Default.GetString(encrypted);
                }
            }
        }
        return encryptedString;
    }

    static string DecryptStringFromBytes_Aes(string cipherText, string password)
    {
        using (AesManaged aesAlg = new AesManaged())
        {
            aesAlg.Key = PasswordAsByte(password);

            aesAlg.IV = Encoding.Default.GetBytes(cipherText).Take(16).ToArray();

            ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);

            var encryptedByteArray = Encoding.Default.GetBytes(cipherText).Skip(16).ToArray();

            using (MemoryStream msDecrypt = new MemoryStream(encryptedByteArray))
            {
                using (CryptoStream csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
                {
                    using (StreamReader srDecrypt = new StreamReader(csDecrypt))
                    {
                        return srDecrypt.ReadToEnd();
                    }
                }
            }
        }
    }

    private static byte[] PasswordAsByte(string password)
    {
        byte[] salt = Encoding.Default.GetBytes("foobar42");
        Rfc2898DeriveBytes passwordBytes = new Rfc2898DeriveBytes(password, salt);

        return passwordBytes.GetBytes(32);
    }

person Manuel    schedule 25.03.2015    source источник
comment
Хорошо, что вы предоставили большую часть кода, но если бы вы включили директивы using и объявление класса, мы могли бы просто скопировать, вставить и скомпилировать ...   -  person Jon Skeet    schedule 25.03.2015
comment
Это не реализация AES, просто вы используете API.   -  person Dai    schedule 25.03.2015
comment
Я голосую за то, чтобы закрыть этот вопрос как не по теме, потому что это вопрос проверки кода.   -  person Dai    schedule 25.03.2015
comment
Это шифрование без аутентификации. Злоумышленники могут изменять зашифрованные данные без вашего ведома при расшифровке. Ты согласен с этим?   -  person usr    schedule 25.03.2015
comment
О реализации AES: я только что сказал, что что-то реализую с помощью AesManaged. Может, мой английский недостаточно хорош.   -  person Manuel    schedule 25.03.2015
comment
@ Дай, я не согласен. У меня есть конкретные вопросы, связанные с .net и безопасностью. Где, если не в stackoverflow, мне обратиться за помощью? Мне кажется, с самого начала не так-то просто получить шифрование.   -  person Manuel    schedule 25.03.2015
comment
Теперь у меня есть очищенная и рабочая реализация этого. Могу ли я добавить эту реализацию в качестве комментария для дальнейшего использования?   -  person Manuel    schedule 26.03.2015


Ответы (3)


Нет, это не нормально.

1) Вы используете Encoding.Default в разных местах. Не делайте этого - это означает, что вы зависите от платформы, на которой находитесь. Всегда используйте явную кодировку, в идеале в большинстве случаев UTF-8.

2) Вы используете Encoding.GetString / Encoding.GetBytes для преобразования произвольных двоичных данных в строку и обратно. Это почти обязательно приведет к потере данных. (На моей машине это получилось, но это действительно зависит от кодировки - и это принципиально плохая идея.) Encoding предназначен для данных, которые по своей сути являются текстовыми данными, и вы просто применяете кодирование так или иначе. Ваши зашифрованные данные по своей сути являются двоичными данными. Вместо этого используйте Convert.ToBase64String и Convert.FromBase64String.

По другим вопросам:

  • Да, насколько я знаю, можно хранить IV с зашифрованными данными.
  • Вы можете использовать тот же подход для пароля: каждый раз генерировать другую соль и сохранять это с зашифрованным текстом. Боюсь, не уверен, что это обычно рекомендуется или нет.
  • Я считаю, что вы контролируете, будет ли размер ключа 128 или 256 бит, с вашим вызовом passwordBytes.GetBytes(32) - это 256-битный ключ, так что это AES256.
  • Я считаю, что размер IV для AES всегда составляет 16 байтов (128 бит)
person Jon Skeet    schedule 25.03.2015
comment
Я изменил кодировку в соответствии с вашим комментарием. Не был полностью осведомлен об этом. - person Manuel; 25.03.2015
comment
В твоем ответе много неуверенности, Джон :) В принципе, ты действительно можешь хранить соль с зашифрованным текстом. Статический IV подходит, если ключ меняется каждый раз (из-за соли), хотя случайный IV все равно рекомендуется. Я постараюсь ответить и сегодня вечером (CET), но вы в основном правы здесь (с небольшим замечанием, что IV требуется для режима CBC, а не AES, и в этом случае он равен размеру блока блочного шифра , что действительно составляет 128 бит для AES). - person Maarten Bodewes; 25.03.2015
comment
@MaartenBodewes: Я стараюсь не выражать уверенности в ответах, связанных с безопасностью, если я не действительно уверен :) Буду с нетерпением ждать вашего ответа. - person Jon Skeet; 25.03.2015

Обычно соль используется вместе с криптографическим хешированием паролей для защиты от словарных атак. Чтобы получить такую ​​же защиту для симметричного шифрования с AES, вы должны использовать случайный вектор инициализации. Поэтому при шифровании создайте случайный IV и добавьте его к сообщению (в открытом виде). Когда вы расшифровываете, получите IV из зашифрованного сообщения и используйте его для расшифровки сообщения. Тогда шифротекст одного и того же сообщения, зашифрованного одним и тем же ключом, будет другим.

  • Итак, да, можно хранить IV вместе с зашифрованными данными.

  • Вам не нужно каждый раз разную соль, потому что цель случайного IV аналогична тому, как соль усложняет словарные атаки на хеши.

  • AES может использовать ключи размером 128, 192 или 256 бит, поэтому для использования AES 256 вам понадобится 256-битный ключ (32 байта), который вы и используете.

  • AES использует 128-битный блок, который требует 128-битного IV (или 16 байтов).

person Martin Liversage    schedule 25.03.2015
comment
Неправильно, боюсь, капельница - не альтернатива соли. - person Maarten Bodewes; 25.03.2015
comment
@MaartenBodewes: Вы можете уточнить свое заявление? Если вы хотите добавить случайную соль, тогда зашифрованное сообщение будет иметь вид соль + IV + зашифрованный текст. От каких атак защищает соль? - person Martin Liversage; 25.03.2015
comment
Он защищает от атак по словарю и атак с радужной таблицей на значение ключа. Если у вас только что зашифрованный текст, будет сложнее проверить ключ, но он не добавляет такой же защиты, как PBKDF2 (как определено в RFC 2898). PBKDF2 также содержит несколько итераций (рабочий фактор) для пользователя и возможных злоумышленников. Соль используется в этих итерациях. IV применяется только впоследствии как требование для шифрования в режиме CBC. Таким образом, он не учитывается в факторе работы. - person Maarten Bodewes; 25.03.2015
comment
@MaartenBodewes: Я не понимаю, как можно провести атаку радужной таблицы на шифрование AES со случайным IV. Радужная таблица содержит предварительно вычисленные хеши, но AES не является хешем. Кроме того, я не говорю, что PBKDF2 не следует использовать для усложнения словарных атак методом перебора. Я хочу сказать, что использование случайной соли не добавляет безопасности, которую еще не обеспечивает случайный IV. - person Martin Liversage; 25.03.2015
comment
Вы можете предварительно вычислить ключи и проверить их на соответствие любому зашифрованному тексту, созданному с той же солью. IV ничего не делает для защиты от проверки ключей. Проверка будет состоять из одного или двух блоков дешифрования вместо полного рабочего коэффициента. Конечно, это не так быстро, как простой поиск, но и не так сложно, как должно быть. - person Maarten Bodewes; 25.03.2015

Можно ли хранить IV вместе с зашифрованными данными?

Да, это хорошо. Более того, вы используете AesManaged без явной установки Mode - это режим CBC, а в режиме CBC IV должен предшествовать шифрованный текст.

Есть ли другой способ создать ключ, не используя каждый раз одну и ту же соль (на основе заданной кодовой фразы)?

Rfc2898DeriveBytes - довольно стандартный способ получить ключ из текстового пароля. Нет необходимости заново изобретать способ получения ключа из пароля, просто используйте Rfc2898DeriveBytes, как вы это делаете сейчас.

Это шифрование выполняется с использованием AES128 или AES256?

Это AES256, поскольку вы используете 32-байтовый пароль.

Будет ли IV всегда 16 байт, или это может измениться?

Размер Свойство IV должно быть таким же, как свойство BlockSize, деленное на 8. Таким образом, для 128-битных блоков оно равно 16.

person Andy Korneyev    schedule 25.03.2015
comment
Один вопрос о IV, уже сохраненном как префикс: AesManaged.CreateDecryptor требует IV, и я не нашел образца, который показывает использование префикса IV. У вас есть для этого ссылка? - person Manuel; 25.03.2015
comment
@Manuel Oh .... похоже, что я неправильно понимаю реализацию .NET AES. Поскольку по стандарту в режиме CBC IV должен быть префиксом шифротекста - я предположил, что CryptoStream уже делает это. Но на самом деле он не добавляет IV к крипертексту при записи в поток. Итак, вы правы, вам нужно добавить IV вручную. Извините за путаницу, я изменю ответ, чтобы не запутать кого-то еще. - person Andy Korneyev; 25.03.2015
comment
В вопросе уже используется Rfc2898DeriveBytes вместо того, чтобы изобретать его заново, так что это замечание, вероятно, следует отбросить. - person Maarten Bodewes; 25.03.2015
comment
Вопрос @MaartenBodewes: есть ли другой способ, кроме Rfc2898DeriveBytes ... Мой ответ должен был быть: Не изобретайте заново способ получения ключа, просто используйте Rfc2898DeriveBytes, как вы это делаете. Вероятно, это не было очевидно из первоначальной формулировки ответа. - person Andy Korneyev; 25.03.2015
comment
Пожалуйста, используйте копирование / вставку, чтобы создать цитату. Я не вижу. Есть ли другой способ, кроме Rfc2898DeriveBytes, в любом месте вопроса, хотя это может подразумеваться: Есть ли другой способ создать ключ, не используя каждый раз одну и ту же соль? - person Maarten Bodewes; 25.03.2015