Почему я не могу зашифровать несколько строк с помощью одного шифратора на MonoTouch?

Я использую следующий (урезанный) класс для шифрования некоторых данных перед их отправкой из приложения iPad в веб-службу WCF.

public class FlawedAlgorithm
{
    protected static byte[] key = { 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42 };
    protected static byte[] vector = { 13, 37, 13, 37, 13, 37, 13, 37, 13, 37, 13, 37, 13, 37, 13, 37 };
    protected ICryptoTransform encryptor, decryptor;
    protected UTF8Encoding encoder;

    public FlawedAlgorithm()
    {
        using (var rijndael = new RijndaelManaged())
        {
            this.encryptor = rijndael.CreateEncryptor(key, vector);
            this.decryptor = rijndael.CreateDecryptor(key, vector);
        }

        this.encoder = new UTF8Encoding();
    }

    public string Encrypt(string unencrypted)
    {
        var buffer = this.encoder.GetBytes(unencrypted);

        return Convert.ToBase64String(Encrypt(buffer));
    }

    public string Decrypt(string encrypted)
    {
        var buffer = Convert.FromBase64String(encrypted);

        return this.encoder.GetString(Decrypt(buffer));
    }

    private byte[] Encrypt(byte[] buffer)
    {
        var encryptStream = new MemoryStream();

        using (var cryptoStream = new CryptoStream(encryptStream, this.encryptor, CryptoStreamMode.Write))
        {
            cryptoStream.Write(buffer, 0, buffer.Length);
        }

        return encryptStream.ToArray();
    }

    private byte[] Decrypt(byte[] buffer)
    {
        var decryptStream = new MemoryStream();

        using (var cryptoStream = new CryptoStream(decryptStream, this.decryptor, CryptoStreamMode.Write))
        {
            cryptoStream.Write(buffer, 0, buffer.Length);
        }

        return decryptStream.ToArray();
    }
}

Когда я запускаю следующий код на сервере и iPad, оба печатают одну и ту же зашифрованную строку.

var algorithm = new FlawedAlgorithm();

Console.WriteLine(algorithm.Encrypt("Some string"));

Однако, когда я пытаюсь зашифровать второе значение, результаты на сервере и iPad различаются.

var algorithm = new FlawedAlgorithm();

// The first encryption still functions correctly.
Console.WriteLine(algorithm.Encrypt("Some string"));

// This second encryption produces a different value on the iPad.
Console.WriteLine(algorithm.Encrypt("This text is a bit longer"));

Когда я расшифровываю отклоненный результат iPad на сервере, часть расшифрованной строки является тарабарщиной. Зашифрованные результаты с сервера расшифровываются корректно.

Проблема не проявляется, если я создаю новый экземпляр FlawedAlgorithm для каждого вызова, например:

// These statements produce the correct results on the iPad.
Console.WriteLine(new FlawedAlgorithm().Encrypt("Some string"));
Console.WriteLine(new FlawedAlgorithm().Encrypt("This text is a bit longer"));

Это наводит меня на мысль, что проблема кроется где-то в состоянии задействованных объектов. Я проверил переменную buffer в методе Encrypt(string), и значения, созданные экземпляром UTF8Encoding, верны. Это означает, что поле encryptor (или его базовая реализация) является виновником.

Когда я начинаю изменять размер первого зашифрованного значения, я вижу изменения в результате второго вызова шифрования. Вероятно, это означает, что какая-то часть потока не очищается или перезаписывается должным образом. Но потоки, которые использует класс FlawedAlgorithm, не являются частью его состояния; они воссоздаются при каждом вызове метода. И объект encryptor не похож на тот тип, который управляет своими потоками.

Кто-нибудь еще сталкивался с проблемой, подобной этой? Является ли класс RijndaelManaged ошибочным? Или здесь есть какие-то подводные камни управления потоками и памятью в MonoTouch, не связанные с этим примером криптографии?

P.S.: я проверял это как на iPad, так и на симуляторе iPad; оба демонстрируют это странное поведение.


person Niels van der Rest    schedule 11.08.2011    source источник


Ответы (1)


При использовании криптографии .NET вы всегда должны проверять ICryptoTransform.CanReuseTransform (или предполагать, что он вернет false). Если он возвращает false, вы не можете повторно использовать один и тот же шифратор/дешифратор и должны создавать новые экземпляры.

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

Вы можете использовать что-то вроде:

 ICryptoTransform Decryptor {
    get {
       if (decryptor == null || !decryptor.CanReuseTransform)
          decryptor = rijndael.CreateDecryptor (key, vector);
        return decryptor;
    }
 }

чтобы скрыть эту сложность от вызывающей стороны ваших криптографических процедур.

person poupou    schedule 11.08.2011
comment
Спасибо, что указали мне на свойство CanReuseTransform. В .NET он возвращает false, в Mono он действительно возвращает true. Это ошибка, которую я больше не совершу! - person Niels van der Rest; 11.08.2011
comment
В наши дни CanReuseTransform является ложным для iOS 10 MonoTouch и истинным для Android 7 MonoDroid. Тем не менее, вопрос OP и этот ответ спасли меня от дальнейших выходных в невежественной агонии. Спасибо вам обоим! - person Mark Larter; 25.02.2017