Overview
When encrypting files, loading the entire file into memory can lead to out-of-memory errors, especially with large files like videos or backups. By combining CryptoStream and Stream.CopyTo, you can perform “stream processing”—encrypting the file content bit by bit while writing it to another file. This ensures safe processing with a constant memory footprint regardless of the file size.
Specifications (Input/Output)
- Input: The stream of the original file to be encrypted.
- Output: The destination stream where the encrypted data is written.
- Behavior: Reads data from the input stream, encrypts it using AES, and sequentially writes it to the output stream.
Basic Usage
Wrap the output stream (such as a FileStream) with a CryptoStream and copy the content of the input stream into it.
// Destination file
using var fsOut = File.Create("output.dat");
// Stream that passes through the encryption filter
using var cs = new CryptoStream(fsOut, encryptor, CryptoStreamMode.Write);
// Open the source file and pour it into the encryption stream
using var fsIn = File.OpenRead("input.txt");
fsIn.CopyTo(cs);
Full Code Example
The following implementation defines a StreamEncrypter class for general-purpose use. In this example, the source code of the program itself (Program.cs) is encrypted to create a file named crypted.dat.
using System;
using System.IO;
using System.Security.Cryptography;
class Program
{
static void Main()
{
// 1. Initialize the encrypter (Key and IV are generated automatically)
var encryptor = new StreamEncrypter();
string sourceFile = "Program.cs";
string destFile = "crypted.dat";
// Create a dummy file if it doesn't exist (for testing)
if (!File.Exists(sourceFile))
{
File.WriteAllText(sourceFile, "This is a test file content.");
}
Console.WriteLine($"Starting encryption: {sourceFile} -> {destFile}");
try
{
// 2. Open streams and execute encryption
using (var inStream = File.Open(sourceFile, FileMode.Open, FileAccess.Read))
using (var outStream = File.Open(destFile, FileMode.Create, FileAccess.Write))
{
encryptor.Encrypt(inStream, outStream);
}
Console.WriteLine("Encryption completed.");
// 3. Display key information (required for decryption)
Console.WriteLine($"Key: {Convert.ToBase64String(encryptor.Key)}");
Console.WriteLine($"IV : {Convert.ToBase64String(encryptor.IV)}");
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
}
public class StreamEncrypter
{
public byte[] Key { get; private set; }
public byte[] IV { get; private set; }
// Constructor: Uses existing key/iv or generates new ones
public StreamEncrypter(byte[] key = null, byte[] iv = null)
{
using var aes = Aes.Create();
Key = key ?? aes.Key;
IV = iv ?? aes.IV;
}
/// <summary>
/// Encrypts the input stream and writes it to the output stream.
/// </summary>
public void Encrypt(Stream inStream, Stream outStream)
{
using var aes = Aes.Create();
aes.Key = Key;
aes.IV = IV;
// Create the encryptor
using var encryptor = aes.CreateEncryptor();
// Wrap the output stream with CryptoStream (Mode.Write)
// Data written to cryptoStream is encrypted and sent to outStream
using var cryptoStream = new CryptoStream(outStream, encryptor, CryptoStreamMode.Write);
// Copy the input stream content into the CryptoStream
// CopyTo handles buffering automatically, saving memory
inStream.CopyTo(cryptoStream);
// Exiting the using block disposes of CryptoStream and writes final padding
}
}
Customization Points
- Asynchronous Processing: In web applications, use
await inStream.CopyToAsync(cryptoStream)instead ofCopyToto process large files without blocking the thread. - Progress Display:
CopyTodoes not return until it is finished. To show a progress bar, you must manually implement a loop that callsReadandWritewhile counting the bytes processed.
Important Notes
- Closing CryptoStream: It is essential to correctly close (dispose of) the
CryptoStreamto finalize the write operation. Failure to do so may result in data loss at the end of the file or invalid padding that prevents decryption. - Saving Key/IV: If you lose the Key and IV, the encrypted file can never be recovered. These must be stored securely in a separate location or derived from a password using a key derivation function.
Conclusion
When encrypting files, the standard rule is to use streams to process data in a pipeline rather than loading everything into memory. By creating a CryptoStream in write mode and using Stream.CopyTo, you can efficiently and safely generate encrypted files. Always use using statements for proper resource management to ensure the CryptoStream is closed correctly and data integrity is maintained.
