Memory issue while encrypting files - c#

I have a memory issue while reading a file and encrypt it in C# UWP.
This is my code:
using System;
using System.Text;
using System.IO;
using System.Security.Cryptography;
using Windows.Storage;
using System.Runtime.InteropServices.WindowsRuntime;
public async void encrypt_file(StorageFile file, string key, string pw_salt)
{
try
{
SymmetricAlgorithm algorithm = Aes.Create();
DeriveBytes rgb = new Rfc2898DeriveBytes(key, Encoding.Unicode.GetBytes(pw_salt));//create password
byte[] rgbKey = rgb.GetBytes(algorithm.KeySize >> 3);
byte[] rgbIV = rgb.GetBytes(algorithm.BlockSize >> 3);
ICryptoTransform transform = algorithm.CreateEncryptor(rgbKey, rgbIV);//create encrytor/decryptor
using (MemoryStream memStream = new MemoryStream())
{
using (CryptoStream cryptoStream = new CryptoStream(memStream, transform, CryptoStreamMode.Write))
{
byte[] plainContent = (await FileIO.ReadBufferAsync(file)).ToArray();//read bytes
cryptoStream.Write(plainContent, 0, plainContent.Length);
cryptoStream.FlushFinalBlock();
await FileIO.WriteBufferAsync(file, memStream.ToArray().AsBuffer());//write bytes
await file.RenameAsync(file.Name + ".myfile");
plainContent = null;
cryptoStream.Dispose();
memStream.Dispose();
transform.Dispose();
}
}
GC.Collect();
}
catch { }
}
After I run this code, my application is using too much memory. What am I doing wrong?

Posted as an answer, because as a comment it would be illegible.
I don't have experience with UWP, so unfortunately I can't make the exact changes to your code, but in general .NET you would do:
var buffer = new byte[1024 * 1024]; // 1MB buffer
using (var encryptedStream = new FileStream("FileName.ext.aes", FileMode.OpenOrCreate, FileAccess.Write, FileShare.None, buffer.Length, FileOptions.Asynchronous))
{
using (var crypto = new CryptoStream(encryptedStream, encryptor, CryptoStreamMode.Write))
{
using (var unencryptedStream = new FileStream("FileName.ext", FileMode.Open, FileAccess.Read, FileShare.Read, buffer.Length, FileOptions.Asynchronous))
{
int bytesRead;
do
{
bytesRead = await unencryptedStream.ReadAsync(buffer, 0, buffer.Length);
await crypto.WriteAsync(buffer, 0, bytesRead);
} while (bytesRead == buffer.Length);
}
}
}
So, you read a block from your unencrypted stream and write it to your crypro stream, who in turn will encrypt the data and writes this to your output stream. Then you check if the amount of data read equals the requested amount of data to be read (buffer.Length). If its less it means you reached the end of the stream.
This way your memory footprint is restricted to the buffer size (in this example 1MB).

Related

How to encrypt / decrypt binary data in .NET 6.0 with AES in C#?

I'm trying to encrypt binary data with AES in .Net 6.0. Unfortunately, the decryption does not bring back the original data:
public static byte[] Encrypt(byte[] plainBytes)
{
using (var aes = System.Security.Cryptography.Aes.Create())
{
byte[] cipherBytes;
using (MemoryStream cipherStream = new MemoryStream())
using (CryptoStream cryptoStream = new CryptoStream(cipherStream, aes.CreateEncryptor(aes.Key, aes.IV), CryptoStreamMode.Write))
{
cryptoStream.Write(plainBytes, 0, plainBytes.Length);
cryptoStream.FlushFinalBlock();
cipherStream.Seek(0, SeekOrigin.Begin);
cipherBytes = new byte[cipherStream.Length];
cipherStream.Read(cipherBytes, 0, cipherBytes.Length);
}
byte[] wrongPlainBytes;
using (MemoryStream cipherStream = new MemoryStream(cipherBytes))
using (CryptoStream cryptoStream = new CryptoStream(cipherStream, aes.CreateDecryptor(aes.Key, aes.IV), CryptoStreamMode.Read))
{
wrongPlainBytes = cipherStream.ToArray();
}
bool shouldBeTrue = plainBytes.Equals(wrongPlainBytes);
return cipherBytes;
}
}
After executing this code, shouldBeTrue should be true, but it's false. Also, plainBytes.Length is different from wrongPlainBytes.Length.
What do I wrong while encryption / decryption?
public static byte[] Encrypt( byte[] plainBytes )
{
using ( var aes = System.Security.Cryptography.Aes.Create() )
{
byte[] cipherBytes;
using ( MemoryStream cipherStream = new MemoryStream() )
using ( CryptoStream cryptoStream = new CryptoStream( cipherStream, aes.CreateEncryptor( aes.Key, aes.IV ), CryptoStreamMode.Write ) )
{
cryptoStream.Write( plainBytes, 0, plainBytes.Length );
cryptoStream.FlushFinalBlock();
cipherBytes = cipherStream.ToArray();
}
byte[] wrongPlainBytes;
using ( MemoryStream cipherStream = new MemoryStream() )
using ( CryptoStream cryptoStream = new CryptoStream( cipherStream, aes.CreateDecryptor( aes.Key, aes.IV ), CryptoStreamMode.Write ) )
{
cryptoStream.Write( cipherBytes, 0, cipherBytes.Length );
cryptoStream.FlushFinalBlock();
wrongPlainBytes = cipherStream.ToArray();
}
bool shouldBeTrue = plainBytes.SequenceEqual( wrongPlainBytes );
return cipherBytes;
}
}
Try to avoid direct Read on streams where possible - this operation is not guaranteed to read the amount of bytes you requested, so when you do use it - you need to check the return value (which you ignore) to figure out actual number of bytes that were read, and then act accordingly (do more reads if that amount is less than what you need).
There are several issues in your code, which you can avoid this way:
public static byte[] Encrypt(byte[] plainBytes)
{
using (var aes = System.Security.Cryptography.Aes.Create())
{
byte[] cipherBytes;
using (MemoryStream cipherStream = new MemoryStream()) {
// use leaveOpen parameter here, that way it will NOT dispose cipherStream when closing cryptoStream
using (CryptoStream cryptoStream = new CryptoStream(cipherStream, aes.CreateEncryptor(aes.Key, aes.IV), CryptoStreamMode.Write, true)) {
// just write
cryptoStream.Write(plainBytes, 0, plainBytes.Length);
}
// now, cryptoStream is disposed, so we can be sure all data is propertly written to the cipherStream
// no need to flush or anything
// do not manage buffer yourself, use ToArray
cipherBytes = cipherStream.ToArray();
}
byte[] wrongPlainBytes;
using (MemoryStream cipherStream = new MemoryStream(cipherBytes)) {
using (CryptoStream cryptoStream = new CryptoStream(cipherStream, aes.CreateDecryptor(aes.Key, aes.IV), CryptoStreamMode.Read)) {
using (var output = new MemoryStream()) {
// same story here, avoid direct reads, use CopyTo, it will copy all data, decrypted, to memory stream
cryptoStream.CopyTo(output);
// again, just ToArray to avoid manage buffer directly
wrongPlainBytes = output.ToArray();
}
}
}
bool shouldBeTrue = plainBytes.SequenceEqual(wrongPlainBytes);
return cipherBytes;
}
}

Add cleartext bytes to the beginning of a CryptoStream?

I have an interface defined like so:
public interface IEncryptionService
{
Stream Encrypt(Stream cleartext);
Stream Decrypt(Stream encrypted);
}
I am implementing this interface with an AesCryptoServiceProvider, but there's clearly a problem here. The IV (Initialization Vector) is not returned on the interface... so encrypting something would work fine, as long as I have no desire to decrypt it ever again. The Decrypt() method has no chance at all of working.
What I want to do is include the IV in cleartext at the beginning of the stream, then add the CryptoStream to it, so it is essentially encrypted data with a "header" that I could strip off and use for decrypting the stream.
So... how would I do that? I can create the CryptoStream easy enough, but it looks like this would encrypt the IV, which kinda defeats the purpose. I could load the CryptoStream into memory, prepend the IV, and then stream it out as a MemoryStream, but this would be really inefficient, and would die on large streams.
What is a good, secure, scalable practice for this?
Here is what I had in mind. See how you write the IV to the MemoryStream and then follow it with the crypto? Then when you want to decrypt, pull the IV off first in the same way.
Sorry, been a long time. This one is working. It should scale well if you don't cast ms toArray(); at the end. For example write to FileStream as you go and you should not need much memory at all. This is just to demo prepending the IV.
private byte[] encrypt(byte[] originalPlaintextBytes)
{
using (SymmetricAlgorithm algorithm = AesCryptoServiceProvider.Create())
{
algorithm.GenerateKey();
algorithm.GenerateIV();
byte[] iv = algorithm.IV;
byte[] key = algorithm.Key;
using (ICryptoTransform encryptor = algorithm.CreateEncryptor(key, iv))
{
using (MemoryStream ms = new MemoryStream())
using (CryptoStream cs = new CryptoStream(ms, encryptor,CryptoStreamMode.Write))
{
BinaryWriter bw = new BinaryWriter(ms);
bw.Write(iv);
cs.Write(originalPlaintextBytes, 0, originalPlaintextBytes.Length);
return ms.ToArray();
}
}
}
}
OK rather than edit the above code, here is a whole program that randomly creates a plaintext file of 1 megabyte. Then it encrypts it into ciphertext. Note that this program does not ever need 1 megabyte of memory in which to operate. It is completely scalable. Again, as before, this program is to demonstrate the concept, and you would do better with a readBuffer larger than 1 byte. But I did not want to create that and obscure the core answer. I hope this helps. I think it is exactly the kind of approach you need.
using System;
using System.IO;
using System.Security.Cryptography;
using System.Windows.Forms;
namespace SO_AES
{
public partial class Form1 : Form
{
Random ran = new Random();
public Form1()
{
InitializeComponent();
using (var file = File.Open("Plaintext.txt", FileMode.OpenOrCreate))
{
byte[] junkBytes = new byte[1000];
for (int i = 0; i < 1000; i++)
{
for (int j = 0; j < 1000; j++)
{
junkBytes[j] = (byte)ran.Next(0, 255);
}
file.Write(junkBytes, 0, junkBytes.Length);
}
}
using (var plainTextFile = File.Open("Plaintext.txt", FileMode.Open))
{
using (var cryptoTextFile = File.Open("Crypto.txt", FileMode.OpenOrCreate))
{
encrypt(plainTextFile, cryptoTextFile);
}
}
}
void encrypt(Stream inStream, Stream outStream)
{
using (SymmetricAlgorithm algorithm = AesCryptoServiceProvider.Create())
{
algorithm.GenerateKey();
algorithm.GenerateIV();
byte[] iv = algorithm.IV;
byte[] key = algorithm.Key;
using (ICryptoTransform encryptor = algorithm.CreateEncryptor(key, iv))
{
using (CryptoStream cs = new CryptoStream(outStream, encryptor, CryptoStreamMode.Write))
{
BinaryWriter bw = new BinaryWriter(outStream);
bw.Write(iv);
byte[] readBuffer = new byte[1];
BinaryReader br = new BinaryReader(inStream);
while (br.Read(readBuffer, 0, readBuffer.Length) != 0)
{
cs.Write(readBuffer, 0, 1);
}
}
}
}
inStream.Close();
outStream.Close();
}
}
}

C# DES File Decryption Breaking Non-Text Files

I have these two methods which are pretty much copy+pastes from http://support.microsoft.com/kb/307010.
When I decrypt the files, if they are any type of text file such as .txt, .xml, .html, etc. I can open them up and everything is fine. Any type of file not just text, such as .exe, .jpg, .pdf, etc. all break when decrypted. Is there anything I am doing wrong? Are these methods using binary to encrypt/decrypt the files? If not is there a way I can make it binary?
Any help is greatly appreciated!
public static void EncryptFile(string sInputFilename,
string sOutputFilename,
string sKey)
{
FileStream fsInput = new FileStream(sInputFilename,
FileMode.Open,
FileAccess.Read);
FileStream fsEncrypted = new FileStream(sOutputFilename,
FileMode.Create,
FileAccess.Write);
DESCryptoServiceProvider DES = new DESCryptoServiceProvider();
DES.Key = ASCIIEncoding.ASCII.GetBytes(sKey);
DES.IV = ASCIIEncoding.ASCII.GetBytes(sKey);
ICryptoTransform desencrypt = DES.CreateEncryptor();
CryptoStream cryptostream = new CryptoStream(fsEncrypted,
desencrypt,
CryptoStreamMode.Write);
byte[] bytearrayinput = new byte[fsInput.Length];
fsInput.Read(bytearrayinput, 0, bytearrayinput.Length);
cryptostream.Write(bytearrayinput, 0, bytearrayinput.Length);
cryptostream.Close();
fsInput.Close();
fsEncrypted.Close();
}
public static void DecryptFile(string sInputFilename,
string sOutputFilename,
string sKey)
{
DESCryptoServiceProvider DES = new DESCryptoServiceProvider();
//A 64 bit key and IV is required for this provider.
//Set secret key For DES algorithm.
DES.Key = ASCIIEncoding.ASCII.GetBytes(sKey);
//Set initialization vector.
DES.IV = ASCIIEncoding.ASCII.GetBytes(sKey);
//Create a file stream to read the encrypted file back.
FileStream fsread = new FileStream(sInputFilename,
FileMode.Open,
FileAccess.Read);
//Create a DES decryptor from the DES instance.
ICryptoTransform desdecrypt = DES.CreateDecryptor();
//Create crypto stream set to read and do a
//DES decryption transform on incoming bytes.
CryptoStream cryptostreamDecr = new CryptoStream(fsread,
desdecrypt,
CryptoStreamMode.Read);
//Print the contents of the decrypted file.
StreamWriter fsDecrypted = new StreamWriter(sOutputFilename);
fsDecrypted.Write(new StreamReader(cryptostreamDecr).ReadToEnd());
fsDecrypted.Flush();
fsDecrypted.Close();
fsread.Close();
cryptostreamDecr.Close();
}
I don't know what the guy that wrote that article was smoking, but:
DESCryptoServiceProvider desCrypto =
(DESCryptoServiceProvider)DESCryptoServiceProvider.Create();
return ASCIIEncoding.ASCII.GetString(desCrypto.Key);
will not get you a valid key. At least one problem is the fact that the key you use to encrypt is not the same key that you're using to decrypt, because you can't convert bytes to ASCII and back like that.
If you want to treat the key as a string, what you probably want is:
string keyAsString = Convert.ToBase64String(desCrypto.Key);
Then when you want to turn it back into bytes, instead of ASCIIEncoding.ASCII.GetBytes, you'll do:
byte[] key = Convert.FromBase64String(keyAsString);
EDIT
There's a ton more wrong with that article too. I'd say ignore that one and find a better example.
EDIT
Here's a very clean basic AES working example that I use for my standard encryption needs. Some of the major improvements over the article are:
Proper creation of a key
Current algorithm (AES 256-bit key)
Random IV
Buffered file access instead of reading/writing the entire file in one chunk
Wrapping all the disposable objects in using
Aside from that, it's the same basic idea.
using System;
using System.IO;
using System.Security.Cryptography;
namespace ConsoleApplication12
{
class Program
{
private const int KEY_SIZE_BYTES = 32;
private const int IV_SIZE_BYTES = 16;
static void Main(string[] args)
{
var rand = new Random();
using (var fs = File.Open(#"C:\temp\input.bin", FileMode.Create, FileAccess.Write, FileShare.None))
{
byte[] buffer = new byte[10000];
for (int i = 0; i < 100; ++i)
{
rand.NextBytes(buffer);
fs.Write(buffer, 0, buffer.Length);
}
}
string key = GenerateRandomKey();
Encrypt(#"C:\temp\input.bin", #"C:\temp\encrypted.bin", key);
Decrypt(#"C:\temp\encrypted.bin", #"C:\temp\decyrypted.bin", key);
}
static string GenerateRandomKey()
{
byte[] key = new byte[KEY_SIZE_BYTES];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(key);
}
return Convert.ToBase64String(key);
}
static void Encrypt(string inputFile, string outputFile, string key)
{
const int BUFFER_SIZE = 8192;
byte[] buffer = new byte[BUFFER_SIZE];
byte[] keyBytes = Convert.FromBase64String(key);
byte[] ivBytes = new byte[IV_SIZE_BYTES];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(ivBytes);
}
using (var inputStream = File.Open(inputFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (var outputStream = File.Open(outputFile, FileMode.Create, FileAccess.Write, FileShare.None))
{
outputStream.Write(ivBytes, 0, ivBytes.Length);
using (var cryptoAlgo = Aes.Create())
{
using (var encryptor = cryptoAlgo.CreateEncryptor(keyBytes, ivBytes))
{
using (var cryptoStream = new CryptoStream(outputStream, encryptor, CryptoStreamMode.Write))
{
int count;
while ((count = inputStream.Read(buffer, 0, buffer.Length)) > 0)
{
cryptoStream.Write(buffer, 0, count);
}
}
}
}
}
}
}
static void Decrypt(string inputFile, string outputFile, string key)
{
const int BUFFER_SIZE = 8192;
byte[] buffer = new byte[BUFFER_SIZE];
byte[] keyBytes = Convert.FromBase64String(key);
byte[] ivBytes = new byte[IV_SIZE_BYTES];
using (var inputStream = File.Open(inputFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
inputStream.Read(ivBytes, 0, ivBytes.Length);
using (var outputStream = File.Open(outputFile, FileMode.Create, FileAccess.Write, FileShare.None))
{
using (var cryptoAlgo = Aes.Create())
{
using (var decryptor = cryptoAlgo.CreateDecryptor(keyBytes, ivBytes))
{
using (var cryptoStream = new CryptoStream(inputStream, decryptor, CryptoStreamMode.Read))
{
int count;
while ((count = cryptoStream.Read(buffer, 0, buffer.Length)) > 0)
{
outputStream.Write(buffer, 0, count);
}
}
}
}
}
}
}
}
}
Because the IV is random, you'll see another small difference in technique. When encrypting the file, you first write the IV to the encrypted file (it's not a secret, so you just write it straight out). When decrypting the file, you read the first few bytes to retrieve the IV, then the rest of the file contains the actual encrypted data. The purpose of a random IV is so the same plaintext file will encrypt into a different encrypted file every time you run it.
The Main method here demonstrates encryption with a random key. If you want to use a password, it's a little more work, but you can implement PBKDF2 with maybe a dozen or so extra lines of code.

Crypto stream: read data error

I got this (i also tried crStream.CopyTo(ms)):
var cryptic = new DESCryptoServiceProvider();
cryptic.Key = ASCIIEncoding.ASCII.GetBytes(passKey);
cryptic.IV = ASCIIEncoding.ASCII.GetBytes(passKey);
Stream crStream = new CryptoStream(data, cryptic.CreateEncryptor(), CryptoStreamMode.Write);
Stream ms = new MemoryStream();
var buffer = new byte[0x10000];
int n;
while ((n = crStream.Read(buffer, 0, buffer.Length)) != 0) // Exception occurs here
ms.Write(buffer, 0, n);
crStream.Close();
Data = Stream and contains a binary serialized class
The following exception occurs when i run it:
"Stream does not support reading."
What i am trying to accomplish is simply encrypt data from a stream. So i have an incoming stream and i want to encrypt that data and put it into the memory stream. This will then be compressed and saved to a file.
the error says everything: you create the stream for encryption (= put plain-text into and get encrypted output, in write):
Stream crStream = new CryptoStream(data, cryptic.CreateEncryptor(), CryptoStreamMode.Write);
Just have a look at the MSDN-Documentation for CryptoStream - there is a example included of how to do it right - it's basically this part (right from MSDN):
using (MemoryStream msEncrypt = new MemoryStream())
using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
using (StreamWriter swEncrypt = new StreamWriter(csEncrypt))
{
//Write all data to the stream.
swEncrypt.Write(plainText);
}
encrypted = msEncrypt.ToArray();
}

Most efficient way of reading data from a stream

I have an algorithm for encrypting and decrypting data using symmetric encryption. anyways when I am about to decrypt, I have:
CryptoStream cs = new CryptoStream(ms, cryptoTransform, CryptoStreamMode.Read);
I have to read data from the cs CryptoStream and place that data into a array of bytes. So one method could be:
System.Collections.Generic.List<byte> myListOfBytes = new System.Collections.Generic.List<byte>();
while (true)
{
int nextByte = cs.ReadByte();
if (nextByte == -1) break;
myListOfBytes.Add((Byte)nextByte);
}
return myListOfBytes.ToArray();
another technique could be:
ArrayList chuncks = new ArrayList();
byte[] tempContainer = new byte[1048576];
int tempBytes = 0;
while (tempBytes < 1048576)
{
tempBytes = cs.Read(tempContainer, 0, tempContainer.Length);
//tempBytes is the number of bytes read from cs stream. those bytes are placed
// on the tempContainer array
chuncks.Add(tempContainer);
}
// later do a for each loop on chunks and add those bytes
I cannot know in advance the length of the stream cs:
or perhaps I should implement my stack class. I will be encrypting a lot of information therefore making this code efficient will save a lot of time
You could read in chunks:
using (var stream = new MemoryStream())
{
byte[] buffer = new byte[2048]; // read in chunks of 2KB
int bytesRead;
while((bytesRead = cs.Read(buffer, 0, buffer.Length)) > 0)
{
stream.Write(buffer, 0, bytesRead);
}
byte[] result = stream.ToArray();
// TODO: do something with the result
}
Since you are storing everything in memory anyway you can just use a MemoryStream and CopyTo():
using (MemoryStream ms = new MemoryStream())
{
cs.CopyTo(ms);
return ms.ToArray();
}
CopyTo() will require .NET 4

Categories