[C#] How to Encrypt Files Using Symmetric Key Encryption (AES)

目次

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 of CopyTo to process large files without blocking the thread.
  • Progress Display: CopyTo does not return until it is finished. To show a progress bar, you must manually implement a loop that calls Read and Write while counting the bytes processed.

Important Notes

  • Closing CryptoStream: It is essential to correctly close (dispose of) the CryptoStream to 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.

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次