Почему этот код шифрования AES всегда возвращает другой зашифрованный текст?

Примечание:

Следующий пример кода предназначен только для демонстрационных целей и реализует небезопасную схему. Если вы ищете безопасную схему, посмотрите https://stackoverflow.com/a/10177020/40347.

Я использую класс AESCryptoServiceProvider для тестирования некоторых концепций шифрования. До сих пор во всех примерах и статьях они генерировали случайный ключ для шифрования, а затем сразу для расшифровки. Конечно, это работает нормально, потому что вы используете ключ прямо здесь, но если вы зашифруете, сохраните текст, а позже вы захотите его расшифровать, вам понадобится ТОТ ЖЕ ключ. А для этого и тот же IV.

Теперь в этом коде я использую один и тот же ключ и IV на нескольких проходах, каждый раз, когда я запускаю пакет, этот пакет дает один и тот же результат (как и ожидалось). Но затем я закрываю тестовое приложение и повторно запускаю тот же код без изменений, и результирующий (в кодировке Base64) зашифрованный текст отличается для тех же входных параметров, почему?

Я «сохранил» один из шифров с кодировкой B64 из предыдущего запуска и передал его методу TestDecrypt, и, как и ожидалось, он выдал криптографическое исключение, упоминающее что-то о дополнении, хотя я уверен, что это связано с тем фактом, что каким-то образом для один и тот же ключ, IV, обычный текст и параметры дают разные результаты при каждом отдельном запуске приложения.

Для шифрования у меня есть это:

    public string Test(string password, Guid guid, string text)
    {
        const int SaltSize = 16;

        string b64Cryptogram;
        MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
        Rfc2898DeriveBytes pwbytes = new Rfc2898DeriveBytes(password, SaltSize);

        // Block 128-bits Key 128/192/256 bits (16/24/32 bytes)
        using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider())
        {
            aes.Padding = PaddingMode.PKCS7;
            aes.Mode = CipherMode.CBC;
            //aes.IV = pwbytes.GetBytes(aes.BlockSize / 8);
            aes.IV = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
            aes.Key = guid.ToByteArray();

            ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
            using (MemoryStream msEncrypt = new MemoryStream())
            {
                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
                {
                    using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
                    {

                        //Write all data to the stream.
                        swEncrypt.Write(text);
                    }
                    b64Cryptogram = Convert.ToBase64String(msEncrypt.ToArray());
                }
            }
            Console.WriteLine("E: {0}", b64Cryptogram);
            aes.Clear();
        }
        return b64Cryptogram;
    }

Обратите внимание, что я не использую RFC2898DeriveBytes, потому что он случайным образом выведет что-то, что я больше не вспомню :) Идея его шифрования заключается именно в том, что я ЗНАЮ, что я использовал для его шифрования.

Метод расшифровки выглядит так:

    public void TestDecrypt(string password, Guid guid, string ciphertextB64)
    {
        const int SaltSize = 16;

        byte[] cipher = Convert.FromBase64String(ciphertextB64);
        string plaintext;

        MD5CryptoServiceProvider md5 = new MD5CryptoServiceProvider();
        Rfc2898DeriveBytes pwbytes = new Rfc2898DeriveBytes(password, SaltSize);

        // Block 128-bits Key 128/192/256 bits (16/24/32 bytes)
        using (AesCryptoServiceProvider aes = new AesCryptoServiceProvider())
        {
            aes.Padding = PaddingMode.PKCS7;
            aes.Mode = CipherMode.CBC;
            //aes.IV = pwbytes.GetBytes(aes.BlockSize / 8);
            aes.IV = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
            aes.Key = guid.ToByteArray();

            ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
            using (MemoryStream msEncrypt = new MemoryStream(cipher))
            {
                using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, decryptor, CryptoStreamMode.Read))
                {
                    using (StreamReader swEncrypt = new StreamReader(csEncrypt))
                    {
                        plaintext = swEncrypt.ReadToEnd();
                    }
                }
            }
            Console.WriteLine("D: {0}", plaintext);
            aes.Clear();
        }
    }

Теперь просто поместите это в консольное приложение и запустите. Затем выйдите и запустите его снова, и вы увидите, что для одного и того же режима, заполнения, IV, ключа и данных в виде обычного текста выходной шифр не будет одинаковым при каждом запуске приложения. Они будут одинаковыми, если вы повторно запускаете метод в одном и том же запуске приложения.

Если это не очевидно, вот код консоли, который я использовал для тестирования:

        Guid guid = Guid.NewGuid();
        string plain = "Text to be encrypted 123458970";
        string password = "This is a test of the emergency broadcast system";

        TestDecrypt(password, guid, Test(password, guid, plain));
        TestDecrypt(password, guid, Test(password, guid, plain));
        Test(password, guid, plain);
        Test(password, guid, plain);
        Test(plain, guid, password);
        TestDecrypt(password, guid, "W4Oi0DrKnRpxFwtE0xVbYJwWgcA05/Alk6LrJ5XIPl8=");
    }    

person Lord of Scripts    schedule 10.03.2016    source источник
comment
Переданный гид всегда один и тот же?   -  person Frozenthia    schedule 10.03.2016
comment
Да, как я уже сказал, параметры постоянны.   -  person Lord of Scripts    schedule 11.03.2016
comment
Пробовал это, я каждый раз получаю один и тот же зашифрованный текст.   -  person Kevin    schedule 11.03.2016
comment
Как вы создаете свой гайд? var guid = Guid.NewGuid(); Это будет генерировать новое руководство при каждом запуске и создавать другой текст при каждом запуске. ЕСЛИ вы жестко кодируете значение guid, то я не знаю, потому что это дает мне один и тот же текст при каждом запуске.   -  person Kevin    schedule 11.03.2016
comment
@ Кевин, как я уже упоминал, когда вы запустите его, результат будет таким же. НО вы должны затем ВЫЙТИ из приложения и запустить его снова, результаты ПАКЕТА 1 НЕ совпадают с результатами ПАКЕТА 2.   -  person Lord of Scripts    schedule 11.03.2016
comment
Я выхожу из каждого запуска, затем бегу снова. По-прежнему получайте идентичный зашифрованный текст при каждом запуске.   -  person Kevin    schedule 11.03.2016
comment
ghKliXeGkzICxTa9lcKbmpQ4q0j1ThIYYNRPl0fpsEPmfcSbwZqYs1j5NPXoj/e3yfdfpS5nCQ1q2DjcObXsiwO4TXs755BRaoWdIvPsWE4=   -  person Kevin    schedule 11.03.2016
comment
ghKliXeGkzICxTa9lcKbmpQ4q0j1ThIYYNRPl0fpsEPmfcSbwZqYs1j5NPXoj/e3yfdfpS5nCQ1q2DjcObXsiwO4TXs755BRaoWdIvPsWE4=   -  person Kevin    schedule 11.03.2016
comment
То, как вы устанавливаете guid, имеет значение, потому что, очевидно, оно будет одинаковым при каждом запуске, если вы установите guid один раз и вызовете его в одно и то же время. Потому что, когда вы перезапускаете приложение, Guid.NewGuid() будет каждый раз возвращать другой guid.   -  person Frozenthia    schedule 11.03.2016
comment
Где шифрованный текст отличается? Он совсем другой или только ближе к концу?   -  person Collin Dauphinee    schedule 11.03.2016
comment
Штопать! какую глупую ошибку я совершил! Я не использовал постоянный Guid, неудивительно, что он генерировал новый результат при каждом запуске.   -  person Lord of Scripts    schedule 11.03.2016
comment
Для чего вы используете этот код? И почему вы не используете случайный IV? Это серьезный недостаток безопасности, поскольку он делает вашу схему уязвимой для атак по словарю (см. cwe.mitre .org/data/definitions/329.html).   -  person Dirk Vollmar    schedule 11.03.2016
comment
См. здесь лучший способ сделать это: stackoverflow.com/a/10177020/40347   -  person Dirk Vollmar    schedule 11.03.2016


Ответы (1)


Решение здесь состоит в том, чтобы получить из сохраненного или постоянного Guid. Вызов

Guid.NewGuid();

каждый раз будет возвращать другой результат. Из документов:

Это удобный статический метод, который можно вызвать для получения нового идентификатора Guid. Метод заключает в себе вызов функции Windows CoCreateGuid. Гарантировано, что возвращаемый Guid не равен Guid.Empty.

В качестве альтернативы при тестировании вы можете использовать Guid.Empty, который вернет все нули.

Или вы можете сохранить его как таковой, используя перегрузку строкового конструктора:

var guid = new Guid("0f8fad5b-d9cb-469f-a165-70867728950e");
person Frozenthia    schedule 10.03.2016