Использование шифрования Rijndael для больших файлов

Я нахожусь в ситуации, когда мне нужно безопасно зашифровать / расшифровать файл длиной n, в идеале с использованием Rijndael, но определенно с 256-битным шифрованием.

Я раньше играл с шифрованием и довольно успешно зашифровал / расшифровал строки и байтовые массивы. Однако, поскольку я не знаю размера файла (и вполне возможно, что файлы, о которых идет речь, могут быть довольно большими (~ 2,5 ГБ), я не могу просто загрузить их в массив байтов и зашифровать / расшифровать их в один переплет, как и раньше.

Итак, немного почитав в Google, я понял, что ответ - зашифровать и расшифровать файл по частям, поэтому я создал следующий код:

private static void Enc(string decryptedFileName, string encryptedFileName)
{            
   FileStream fsOutput = File.OpenWrite(encryptedFileName);
   FileStream fsInput = File.OpenRead(decryptedFileName);

   byte[] IVBytes = Encoding.ASCII.GetBytes("1234567890123456");

   fsOutput.Write(BitConverter.GetBytes(fsInput.Length), 0, 8);
   fsOutput.Write(IVBytes, 0, 16);

   RijndaelManaged symmetricKey = new RijndaelManaged() { Mode = CipherMode.CBC};
   ICryptoTransform encryptor = symmetricKey.CreateEncryptor(passwordDB.GetBytes(256 / 8), IVBytes);
   CryptoStream cryptoStream = new CryptoStream(fsOutput, encryptor, CryptoStreamMode.Write);

   for (long i = 0; i < fsInput.Length; i += chunkSize)
   {
      byte[] chunkData = new byte[chunkSize];
      fsInput.Read(chunkData, 0, chunkSize);
      cryptoStream.Write(chunkData, 0, chunkData.Length);
   }
   cryptoStream.Close();
   fsInput.Close();
   fsInput.Dispose();
   cryptoStream.Dispose();
}

private static void Dec(string encryptedFileName, string decryptedFileName)
{
    FileStream fsInput = File.OpenRead(encryptedFileName);
    FileStream fsOutput = File.OpenWrite(decryptedFileName);

    byte[] buffer = new byte[8];
    fsInput.Read(buffer, 0, 8);

    long fileLength = BitConverter.ToInt64(buffer, 0);

    byte[] IVBytes = new byte[16];
    fsInput.Read(IVBytes, 0, 16);

    RijndaelManaged symmetricKey = new RijndaelManaged() { Mode = CipherMode.CBC };
    ICryptoTransform decryptor = symmetricKey.CreateDecryptor(passwordDB.GetBytes(256 / 8), IVBytes);
    CryptoStream cryptoStream = new CryptoStream(fsOutput,decryptor,CryptoStreamMode.Write);

    for (long i = 0; i < fsInput.Length; i += chunkSize)
    {
        byte[] chunkData = new byte[chunkSize];
        fsInput.Read(chunkData, 0, chunkSize);
        cryptoStream.Write(chunkData, 0, chunkData.Length);
    }
    cryptoStream.Close();
    cryptoStream.Dispose();
    fsInput.Close();
    fsInput.Dispose();                      
} 

Мне все это "кажется" хорошо, но, к сожалению, внешность обманчива!

Шифрование работает без ошибок, но во время дешифрования метод «cryptoStream.Close ()» выдает следующее исключение:

System.Security.Cryptography.CryptographicException не было обработано. Сообщение = "Заполнение недопустимо и не может быть удалено".
Source = "mscorlib" StackTrace: в System.Security.Cryptography.RijndaelManagedTransform.DecryptData (Byte [] inputBuffer, Int32 inputOffset, Int32 Int32 inputCount, Byte [] & outputBuffer, Int32 outputOffset, PaddingMode, paddingMode, Boolean fLast) в System.Security.Cryptography.RijndaelManagedTransform.TransformFinalBlock (Byte [] inputBuffer, Int32 inputOffset.CryptoCurf.Cryptoptop) в System.Security. () в System.Security.Cryptography.CryptoStream.Dispose (логическое удаление) в System.IO.Stream.Close ()

Также кажется, что размер незашифрованного файла не соответствует ожидаемому размеру файла (от 8 до 60).

Я «исправил» исключение, изменив строки создания объекта RijndaelManaged, чтобы включить тип заполнения, как показано ниже:

RijndaelManaged symmetricKey = new RijndaelManaged() { Mode = CipherMode.CBC,Padding=PaddingMode.None };

Но размеры файлов по-прежнему не совпадают, и, как и ожидалось, только что незашифрованный файл - ерунда!

Я признаю, что сейчас я за пределами своей зоны комфорта с шифрованием / дешифрованием, и это, вероятно, ошибка новичка, но я не могу ее заметить!

Любая помощь в решении этой проблемы будет принята с благодарностью!


person Sk93    schedule 28.07.2010    source источник


Ответы (3)


Проблема в том, что я использовал:

passwordDB.GetBytes(256 / 8)

внутри конструктора объекта RijndaelManaged как в методах шифрования, так и в методах дешифрования, и я не переинициализировал объект passwordDB перед попыткой дешифрования.

Решением было просто включить создание объекта passwordDB в первые строки методов Enc и Dec следующим образом:

        private static void Enc(string decryptedFileName, string encryptedFileName)
        {
            PasswordDeriveBytes passwordDB = new PasswordDeriveBytes("ThisIsMyPassword", Encoding.ASCII.GetBytes("thisIsMysalt!"), "MD5", 2);
            byte[] passwordBytes = passwordDB.GetBytes(128 / 8);

            using (FileStream fsOutput = File.OpenWrite(encryptedFileName))
            {
                using(FileStream fsInput = File.OpenRead(decryptedFileName))
                {
                    byte[] IVBytes = Encoding.ASCII.GetBytes("1234567890123456");

                    fsOutput.Write(BitConverter.GetBytes(fsInput.Length), 0, 8);
                    fsOutput.Write(IVBytes, 0, 16);

                    RijndaelManaged symmetricKey = new RijndaelManaged() { Mode = CipherMode.CBC,Padding=PaddingMode.ANSIX923};
                    ICryptoTransform encryptor = symmetricKey.CreateEncryptor(passwordBytes, IVBytes);                   

                    using (CryptoStream cryptoStream = new CryptoStream(fsOutput, encryptor, CryptoStreamMode.Write))
                    {
                        for (long i = 0; i < fsInput.Length; i += chunkSize)
                        {
                            byte[] chunkData = new byte[chunkSize];
                            int bytesRead = 0;
                            while ((bytesRead = fsInput.Read(chunkData, 0, chunkSize)) > 0)
                            {
                                if (bytesRead != 16)
                                {
                                    for (int x = bytesRead - 1; x < chunkSize; x++)
                                    {
                                        chunkData[x] = 0;
                                    }
                                }
                                cryptoStream.Write(chunkData, 0, chunkSize);
                            }
                        }
                        cryptoStream.FlushFinalBlock();
                    }
                }
            }            
        }

        private static void Dec(string encryptedFileName, string decryptedFileName)
        {
            PasswordDeriveBytes passwordDB = new PasswordDeriveBytes("ThisIsMyPassword", Encoding.ASCII.GetBytes("thisIsMysalt!"), "MD5", 2);
            byte[] passwordBytes = passwordDB.GetBytes(128 / 8);

            using (FileStream fsInput = File.OpenRead(encryptedFileName))
            {
                using (FileStream fsOutput = File.OpenWrite(decryptedFileName))
                {
                    byte[] buffer = new byte[8];
                    fsInput.Read(buffer, 0, 8);

                    long fileLength = BitConverter.ToInt64(buffer, 0);

                    byte[] IVBytes = new byte[16];
                    fsInput.Read(IVBytes, 0, 16);


                    RijndaelManaged symmetricKey = new RijndaelManaged() { Mode = CipherMode.CBC,Padding=PaddingMode.ANSIX923};
                    ICryptoTransform decryptor = symmetricKey.CreateDecryptor(passwordBytes, IVBytes);

                    using (CryptoStream cryptoStream = new CryptoStream(fsOutput, decryptor, CryptoStreamMode.Write))
                    {
                        for (long i = 0; i < fsInput.Length; i += chunkSize)
                        {
                            byte[] chunkData = new byte[chunkSize];
                            int bytesRead = 0;
                            while ((bytesRead = fsInput.Read(chunkData, 0, chunkSize)) > 0)
                            {
                                cryptoStream.Write(chunkData, 0, bytesRead);
                            }
                        }
                    }
                }
            }
        }

Знал, что это школьная ошибка: P

person Sk93    schedule 28.07.2010
comment
if (bytesRead != 16) должно быть if (bytesRead != chunkSize). Верный? - person Petey B; 24.03.2011
comment
В своем первом цикле вы не создаете постоянно увеличивающийся размер буфера с каждой итерацией вместо статического буфера? - person thepip3r; 21.04.2016
comment
На самом деле вы читаете файл один раз, а затем собираетесь выбросить множество исключений, поскольку указатель потока будет в EOF. - person thepip3r; 22.04.2016
comment
@ thepip3r совсем нет. fsInput.Read () будет постоянно возвращать ноль, когда достигает конца файла. Предложение while заботится о попытке записать нулевые данные, и все это любезно выйдет из цикла без исключений. Этот код в настоящее время без исключений выполняется на сервере в качестве фоновой службы. Попробуй сам ;) - person Sk93; 25.04.2016
comment
Я сделал ... и изменил код, чтобы просто использовать цикл while для чтения и шифрования всего содержимого без ошибок. когда я использую внешний цикл for, он выдает невероятное количество исключений. - person thepip3r; 30.04.2016

Метод Stream.Read возвращает количество байтов, фактически считываемых из потока.

Вы должны использовать это возвращаемое значение в качестве последнего параметра в методе Write в следующей строке.

Мой код будет выглядеть так:

byte[] chunkData = new byte[chunkSize];   
var bytesRead = 0;
while ((bytesRead = fsInput.Read(chunkData, 0, chunkSize)) > 0)
{
    cryptoStream.Write(chunkData, 0, bytesRead);
}
person GvS    schedule 28.07.2010
comment
Хорошо - это справедливый комментарий, но я не думаю, что это сработает, поскольку я читал, что вам нужно записывать блоки байтов, делящиеся на 16, чтобы шифрование работало. Если вы используете упомянутый код, возможно, вы напишете, скажем, кусок всего 4 байта? - person Sk93; 28.07.2010
comment
Как и предполагалось, после того, как это будет реализовано, шифрование не удастся, если вы не заполните конец файла нулевыми байтами, чтобы обеспечить общую длину файла, которая делится на 16. Однако это все еще не решило проблему, как я либо получить такое же исключение относительно недопустимого заполнения, либо, если я удалю заполнение, получится сломанный незашифрованный файл. :( - person Sk93; 28.07.2010
comment
В своем производственном коде я использую цикл do ... while и везде использую using, возможно, в этом и заключается трюк. Используйте using вместо Close and Disposeself. - person GvS; 28.07.2010
comment
как ни странно, это именно то, что я пробую прямо сейчас. Я дам вам знать результат на мгновение - спасибо. - person Sk93; 28.07.2010
comment
к сожалению, все еще без радости. Я думаю, это что-то более очевидное, чем это ... Я сейчас работаю над другой идеей, так что посмотрим, как это пойдет :) - person Sk93; 28.07.2010
comment
да - это тоже не сработало - со всеми предложенными изменениями кода я получаю ту же ошибку. - person Sk93; 28.07.2010
comment
Получил в итоге: D. См. Мой ответ для решения, если вам интересно, но в любом случае спасибо за помощь :) - person Sk93; 28.07.2010

Есть класс CryptoStream для шифрования / дешифрования потоков

person Simon Egli    schedule 09.08.2016
comment
Привет, добро пожаловать в Stackoverflow. Я вижу, ты здесь новенький. Прочтите руководство по написанию хорошего, автономного и воспроизводимого ответа здесь: stackoverflow.com/help/how-to-answer - person UeliDeSchwert; 09.08.2016