Проблема с шифрованием файлов Rijndael

Проблема с шифрованием файлов Rijndael

Я хочу зашифровать большие файлы с помощью Rijndael, но получаю ошибку исключения из памяти. Есть идеи? Вот мой код

     public void Rijndael_EncryptFile(string password, string filepath, int opt)
    {
        try
        {
            byte[] keyBytes;
            keyBytes = Encoding.Unicode.GetBytes(password);
            Rfc2898DeriveBytes derivedKey = new Rfc2898DeriveBytes(password, keyBytes);
            RijndaelManaged rijndaelCSP = new RijndaelManaged();
            rijndaelCSP.BlockSize = opt; //128 256
            rijndaelCSP.KeySize = opt;   //128 256
            rijndaelCSP.Key = derivedKey.GetBytes(rijndaelCSP.KeySize / 8);
            rijndaelCSP.IV = derivedKey.GetBytes(rijndaelCSP.BlockSize / 8);
            rijndaelCSP.Mode = CipherMode.CFB;
            rijndaelCSP.Padding = PaddingMode.Zeros;
            ICryptoTransform encryptor = rijndaelCSP.CreateEncryptor();
            FileStream inputFileStream = new FileStream(filepath, FileMode.Open, FileAccess.Read);
            byte[] inputFileData = new byte[(int)inputFileStream.Length];
            inputFileStream.Read(inputFileData, 0, (int)inputFileStream.Length);
            FileStream outputFileStream = new FileStream(filepath + ".enc", FileMode.Create, FileAccess.Write);
            CryptoStream encryptStream = new CryptoStream(outputFileStream, encryptor, CryptoStreamMode.Write);
            encryptStream.Write(inputFileData, 0, (int)inputFileStream.Length);
            encryptStream.FlushFinalBlock();
            rijndaelCSP.Clear();
            encryptStream.Close();
            inputFileStream.Close();
            outputFileStream.Close();
        }
    }

person Community    schedule 15.08.2016    source источник
comment
IV должен быть уникальным для режима CFB. Не используйте статический IV, потому что это делает шифр детерминированным и, следовательно, небезопасным семантически. Злоумышленник, который наблюдает за зашифрованными текстами, может определить, когда тот же префикс сообщения был отправлен ранее. И что еще хуже, с потоковым режимом, таким как CFB, злоумышленник может даже вывести сообщение, если IV используется повторно, даже если сообщения разные. IV не является секретом, поэтому вы можете отправить его вместе с зашифрованным текстом. Обычно он просто добавляется к зашифрованному тексту и отсекается перед расшифровкой.   -  person Artjom B.    schedule 15.08.2016


Ответы (2)


Хорошо, сначала ваша настоящая проблема.

Вы читаете весь файл в память, шифруете его, а затем записываете на диск. Большой байтовый массив может не влезть в память (даже если у вас достаточно "оперативки").

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

Хорошо, другие вещи.

Во-первых, не используйте RijndaelManaged. Используйте AesManaged. Rijndael было названием AES до того, как оно было стандартизировано как AES. AES идентичен Rijndael, за исключением того, что AES не допускает размер блока, отличный от 128, поэтому не изменяйте размер блока. Единственный раз, когда следует использовать Rijndael, это когда вам нужен размер блока, отличный от 128 бит, для совместимости с существующей системой.

Затем ваш вектор инициализации должен быть уникальным для режима CFB. IV не является секретом, вы можете хранить его в виде обычного текста вместе с зашифрованным текстом. Не выводите его из пароля. Важно отметить, что никогда не следует повторно использовать капельницу. Это также не должно быть случайным. Это может быть простой инкрементный счетчик. Обратите внимание, что для других режимов AES, таких как CBC, IV должен быть случайным.

person vcsjones    schedule 15.08.2016

Вам не нужно читать весь файл в память. Наилучший подход состоит в том, чтобы читать порцию данных за раз со входа и записывать эту порцию на выход.

Что-то вроде следующего, хотя я не тестировал нижеследующее:

byte[] keyBytes;
keyBytes = Encoding.Unicode.GetBytes(password);
Rfc2898DeriveBytes derivedKey = new Rfc2898DeriveBytes(password, keyBytes);
RijndaelManaged rijndaelCSP = new RijndaelManaged();
// setup key parameters, i.e. IV, etc
ICryptoTransform encryptor = rijndaelCSP.CreateEncryptor();
using (FileStream inputFileStream = new FileStream(filepath, FileMode.Open, FileAccess.Read))
using (FileStream outputFileStream = new FileStream(filepath + ".enc", FileMode.Create, FileAccess.Write))
using (CryptoStream encryptStream = new CryptoStream(outputFileStream, encryptor, CryptoStreamMode.Write))
{ 
  byte[] buffer = new byte[bufSize];
  int readSize = 0;
  while ((readSize = inputFileStream.Read(buffer, 0, buffer.Length)) > 0)
  {
      encryptStream.Write(buffer, 0, readSize);
  }
  encryptStream.FlushFinalBlock();
  rijndaelCSP.Clear();
}

ОБНОВЛЕНИЕ: удален код инициализации (скопирован из исходного вопроса), поскольку он небезопасен.

person Mike Sackton    schedule 15.08.2016
comment
Я предполагаю, что часть инициализации предназначена для демонстрационных целей. Это не совсем безопасно. Вам нужно использовать случайную соль для вывода ключа и случайный IV. Оба не должны быть секретными, поэтому их можно записать перед зашифрованным текстом и использовать во время расшифровки, прочитав их перед тем, как начать расшифровку. - person Artjom B.; 15.08.2016
comment
Да, это не предназначено для демонстрации правильного использования шифрования, а для решения вопроса о том, как избежать считывания всего блока в память. Я обновил ответ, чтобы удалить код инициализации, чтобы прояснить это. - person Mike Sackton; 15.08.2016