Использование большого количества памяти при шифровании
Есть такие методы для шифрования и перезаписи файлов
private static byte[] Encrypt(byte[] fileData, byte[] key, byte[] salt)
{
using (Aes aes = Aes.Create())
{
using (Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(key, salt, 10000))
{
aes.Key = rfc2898.GetBytes(aes.KeySize / 8);
}
aes.GenerateIV();
byte[] iv = aes.IV;
using (MemoryStream encryptedStream = new MemoryStream())
{
encryptedStream.Write(salt, 0, salt.Length);
encryptedStream.Write(iv, 0, iv.Length);
using (CryptoStream cryptoStream = new CryptoStream(encryptedStream, aes.CreateEncryptor(), CryptoStreamMode.Write))
{
cryptoStream.Write(fileData, 0, fileData.Length);
cryptoStream.FlushFinalBlock();
}
byte[] encryptedData = encryptedStream.ToArray();
return encryptedData;
}
}
}
public void EncryptFile(string filePath, byte[] key, byte[] salt)
{
byte[] data = File.ReadAllBytes(filePath);
byte[] encryptedData = Encrypt(data, key, salt);
using (FileStream encryptedFileStream = new FileStream(filePath, FileMode.Create))
{
encryptedFileStream.Write(encryptedData, 0, encryptedData.Length);
}
Console.WriteLine($"Файл {filePath} был успешно зашифрован.");
data = null;
encryptedData = null;
GC.Collect();
}
И такие методы для расшифровки и перезаписи файлов
private static byte[] Decrypt(byte[] encryptedData, byte[] key, byte[] salt)
{
using (Aes aes = Aes.Create())
{
using (Rfc2898DeriveBytes rfc2898 = new Rfc2898DeriveBytes(key, salt, 10000))
{
aes.Key = rfc2898.GetBytes(aes.KeySize / 8);
}
byte[] iv = new byte[aes.IV.Length];
Buffer.BlockCopy(encryptedData, salt.Length, iv, 0, iv.Length);
aes.IV = iv;
using (MemoryStream encryptedStream = new MemoryStream(encryptedData, salt.Length + iv.Length, encryptedData.Length - salt.Length - iv.Length))
using (MemoryStream decryptedStream = new MemoryStream())
{
using (CryptoStream cryptoStream = new CryptoStream(encryptedStream, aes.CreateDecryptor(), CryptoStreamMode.Read))
{
cryptoStream.CopyTo(decryptedStream);
}
byte[] decryptedData = decryptedStream.ToArray();
return decryptedData;
}
}
}
public void DecryptFile(string filePath, byte[] key, byte[] salt)
{
byte[] encryptedData = File.ReadAllBytes(filePath);
byte[] decryptedData = Decrypt(encryptedData, key, salt);
using (MemoryStream decryptedStream = new MemoryStream(decryptedData))
{
using (FileStream fs = new FileStream(filePath, FileMode.Create))
{
decryptedStream.CopyTo(fs);
}
}
Console.WriteLine($"Файл {filePath} был успешно расшифрован.");
encryptedData = null;
decryptedData = null;
GC.Collect();
}
И у меня такой вопрос, нормально ли что при запуске процесса шифрования директории весом в 200мб, используется около 700мб ОЗУ, но при последующих операциях используемая память не увеличивается, и не уменьшается. Т.е как будто остается зарезервированная память которая была выделена при первой операции, и после этого она используется для всех последующий операций, это нормально, или все же у меня в коде есть утечка памяти ?
Ответы (2 шт):
Попробуйте собирать мусор так:
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();
Если я правильно понимаю, по умолчанию GC не сжимает кучу больших объектов, потому что это занимает довольно много времени. Он просто помечает занимаемое большими объектами место как пригодное к переиспользованию и потом это место реально используется под новые объекты. И если не повезёт и сначала будет создан объект поменьше, а потом побольше, то среда исполнения будет держать как будто занятым место под оба эти объекта суммарно. Но дальше уже занимаемое место не будет расти, если новые объекты будут умещаться в это суммарное место.
А вообще рекомендуется не заниматься самодеятельностью и не вызывать GC вручную. Разве что у вас есть какие-то критичные по времени операции и вы хотите, чтобы GC обязательно отработал до этих операций, а не в какое-то случайное, не удобное для вас время. Ну или если вам нужно стараться не занимать программой память, когда она не нужна. Т.е. когда программа всё время висит в памяти, а функции эти работают не всё время, а с большими промежутками, тогда это имеет смысл, если нужно освободить память для других программ.
Нужно просто перестать грузить все данные целиком в память.
private static void Encrypt(Stream source, Stream target, byte[] key)
{
using var aes = Aes.Create();
byte[] iv = aes.IV;
target.Write(iv);
using (Rfc2898DeriveBytes rfc2898 = new(key, iv, 1000, HashAlgorithmName.SHA256))
{
aes.Key = rfc2898.GetBytes(aes.KeySize / 8);
}
using CryptoStream cryptoStream = new(target, aes.CreateEncryptor(), CryptoStreamMode.Write);
source.CopyTo(cryptoStream);
}
public void EncryptFile(string filePath, byte[] key)
{
string tmp = $"{filePath}.tmp";
using (var source = File.OpenRead(filePath))
using (var target = File.Create(tmp))
{
Encrypt(source, target, key);
}
File.Move(tmp, filePath, true);
Console.WriteLine($"Файл {filePath} был успешно зашифрован.");
}
Этот код ни одного лишнего килобайта памяти не съест, так как в принципе не будет полностью вычитывать содержимое файла в память.
Дальше по образу и подобию.
private static void Decrypt(Stream source, Stream target, byte[] key)
{
using var aes = Aes.Create();
byte[] iv = new byte[aes.BlockSize / 8];
source.Read(iv);
aes.IV = iv;
using (Rfc2898DeriveBytes rfc2898 = new(key, iv, 1000, HashAlgorithmName.SHA256))
{
aes.Key = rfc2898.GetBytes(aes.KeySize / 8);
}
using CryptoStream cryptoStream = new(source, aes.CreateDecryptor(), CryptoStreamMode.Read);
cryptoStream.CopyTo(target);
}
public void DecryptFile(string filePath, byte[] key)
{
string tmp = $"{filePath}.tmp";
using (var source = File.OpenRead(filePath))
using (var target = File.Create(tmp))
{
Decrypt(source, target, key);
}
File.Move(tmp, filePath, true);
Console.WriteLine($"Файл {filePath} был успешно расшифрован.");
}
Лишнее повыкидывал. Готово. Можно хоть фильмы по 40 Гигабайт весом шифровать, потребление памяти при этом не вырастет.
Возня с солью не нужна, на её роль прекрасно подходит рандомный IV. Это никак не снижает безопасность шифрования, а скорее наоборот, вам не придется везде хранить или хардкодить эту соль.