Я пытаюсь заменить PasswordDerivedBytes на Rfc2898DerivedBytes, но у меня проблема с последним при получении результата в кодировке Unicode.

Возьмем, к примеру, этот код:

    [TestMethod]
    public void DerivedBytesTest()
    {
        string encrypted = "y4Ijqo9Ga/mHlFbLHDdDUkYZlyu7CHF4PVXGLnb8by7FAVtCgPLhFSiA9Et6hDac";
        string key = "{00B3403A-3C29-4f26-A9CC-14C411EA8547}";
        string salt = "gT5M07XB9hHl3l1s";
        string expected = "4552065703414505";
        string decrypted;

        decrypted = Decrypt(encrypted, key, salt, true);
        Assert.IsTrue(decrypted == expected); // Works

        decrypted = Decrypt(encrypted, key, salt, false);
        Assert.IsTrue(decrypted == expected); // Doesn't work, get wrong unicode characters in 24 character string
    }

    private string Decrypt(string encrypted, string key, string salt, bool legacy = false)
    {
        UnicodeEncoding encoding = new UnicodeEncoding();

        byte[] encryptedDataBytes = Convert.FromBase64String(encrypted);
        byte[] saltBytes = encoding.GetBytes(salt);

        RijndaelManaged encryption = new RijndaelManaged();
        DeriveBytes secretKey;

        if (legacy)
        {
            secretKey = new PasswordDeriveBytes(key, saltBytes) {IterationCount = 100};
            encryption.Padding = PaddingMode.PKCS7;
        }
        else
        {
            secretKey = new Rfc2898DeriveBytes(key, saltBytes, 100);
            encryption.Padding = PaddingMode.Zeros; // This is the only one that doesn't throw the "Padding is invalid and cannot be removed" exception, but gives me a non-ASCII result
        }

        ICryptoTransform decryptor = encryption.CreateDecryptor(secretKey.GetBytes(32), secretKey.GetBytes(16));

        string decryptedText = "";

        using (MemoryStream memoryStream = new MemoryStream(encryptedDataBytes))
        {
            using (CryptoStream cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
            {
                byte[] bytes = new byte[encryptedDataBytes.Length];
                int decryptedCount = cryptoStream.Read(bytes, 0, bytes.Length);
                decryptedText = encoding.GetString(bytes, 0, decryptedCount);

                if (!legacy)
                {
                    // Something more to do with result?
                }
            }
        }
        return decryptedText;
    }

Интересно, может ли кто-нибудь посоветовать, где я ошибаюсь?

1
mhapps 21 Янв 2018 в 15:13

1 ответ

Лучший ответ

PasswordDeriveBytes - это плохо реализованное расширение PBKDF1, а Rfc2898DeriveBytes - реализация PBKDF2. Оба получают ключ из пароля, но это два разных алгоритма, и поэтому они получают два разных результата. Поскольку они используют криптографически безопасные хэши, нет возможности конвертировать один в другой.

Если вы можете сэкономить несколько байтов хранилища, вы все равно можете получить ключ с помощью PKBDF1, а затем зашифровать этот ключ, используя результат PBKDF2. Если размер вывода идентичен, вы даже можете использовать для этого шифрование XOR (одноразовый блокнот), но AES, конечно, также будет работать. Итак, расшифровка выглядит следующим образом: вычислить результат PBKDF2, расшифровать ключ данных, использовать ключ данных для расшифровки зашифрованного текста.

В противном случае вам придется расшифровать, а затем повторно зашифровать результат.


Если вы хотите сравнить результат дешифрования, сравните полученные байты ; не преобразуйте его сначала в строку. Настоятельно рекомендуется использовать аутентифицированное шифрование или MAC, чтобы вместо этого можно было проверить тег аутентификации. Простое игнорирование исключений заполнения с помощью использования Zero Padding - не лучший вариант. Эти ошибки заполнения возникают из-за неправильного ключа .


Общие примечания:

  • PasswordDeriveBytes не следует использовать для любого количества байтов> 20 байтов, поскольку расширение Mickeysoft для PBKDF1 ужасно небезопасно, даже если байты повторяются в выходных данных (!). Если вы сделаете то же самое для PBKDF2, тогда любой противник должен будет сделать половину работы, которую вы должны сделать, так что это тоже не очень хорошая идея.

  • Количество итераций в вопросе очень мало, но, как вы, кажется, используете очень случайный UID вместо пароля, который должен быть в порядке.

1
Maarten Bodewes 22 Янв 2018 в 00:57
Спасибо за ответ, Маартрен. Да, я пытаюсь отойти от PasswordDerivedBytes, но у меня есть много модульных тестов, которые проходят, но не Rfc2898DeriveBytes (сервер лицензий, который используют все продукты). Попытка кодирования на C ++ показала, что PasswordDerivedBytes был плохим / только MS. Решение, которое я придумал, состоит в том, чтобы декодировать с использованием обоих методов и возвращать разные результаты в списке, который проверяется на размер (как вы говорите) с соответствующим результатом, используемым до тех пор, пока все клиенты не будут переключены на Rfc2898DeriveBytes.
 – 
mhapps
22 Янв 2018 в 12:06
Ага, это сработает. Вы также можете попробовать использовать номер версии протокола, чтобы различать версии; затем вы можете выполнить обновление до других алгоритмов / параметров без сравнения размеров. И вы также можете проголосовать, если найдете мой ответ проясняющим.
 – 
Maarten Bodewes
23 Янв 2018 в 03:14