【C#】メモリにファイルの内容を展開せずにAESで暗号化する

以前にデータを AES 暗号化する方法を紹介しましたが、実装例が何らかのデータをアプリのメモリ上に byte 配列として全部に読み取った内容を展開してから AES 暗号化する方法でした。

この方法だと、例えば 1GB のファイルを暗号化しようとすると1GBぶん全てを一度メモリに内容を全て展開することになります。大きいサイズのファイルを暗号化すると場合によって問題が起きます。

なので今回は、ファイルの内容をメモリに展開することなくファイルtoファイルで暗号化、暗号化を解除する方法を紹介したいと思います。

環境

  • .NET Core 3.1
  • VisualStudio 2022
  • Windows11

コンソールアプリで動作確認しています。

ファイルtoファイルで暗号化する実装

以下、この処理で大切なのは Stream.CopyTo() もしくは Stream.CopyToAsync() を使用する箇所です。

private void EncTest()
{
    // (1) 暗号化する対象のファイル
    string tagetPath = @"e:\src.txt";

    // (2) 暗号化した後のファイル
    string destPath1 = @"e:\dest1.txt";
    
    // (3) 暗号化したファイルの暗号化を解除したファイル(確認用)
    string destPath2 = @"e:\dest2.txt";

    byte[] key = new byte[]
    {
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 値は適当
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,
    };
    byte[] iv = new byte[]
    {
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 値は適当
        0x00,0x00,0x00,0x00,0x00,0x00,
    };

    // どういう方式か?
    using var aes = new AesManaged
    {
        KeySize = 256,
        BlockSize = 128,
        Mode = CipherMode.CBC,
        Padding = PaddingMode.PKCS7,
        Key = key,
        IV = iv,
    };

    // 暗号化
    {
        ICryptoTransform encryptor = aes.CreateEncryptor();

        // 暗号化するファイルのストリーム
        using var fsSrc = new FileStream(tagetPath, FileMode.Open, FileAccess.Read);

        // 暗号化して内容をファイルに書き出すストリーム
        using var fsDest = new FileStream(destPath1, FileMode.Create, FileAccess.Write);
        using var cs = new CryptoStream(fsDest, encryptor, CryptoStreamMode.Write);

        fsSrc.CopyTo(cs); // 非同期の場合は → await CopyToAsync();
    }

    // 暗号化の解除
    {
        ICryptoTransform encryptor = aes.CreateDecryptor();

        // 暗号化を解除するファイルのストリーム
        using var fsSrc = new FileStream(destPath1, FileMode.Open, FileAccess.Read);

        // 解除してファイルに書きだすストリーム
        using var fsDest = new FileStream(destPath2, FileMode.Create, FileAccess.Write);
        using var cs = new CryptoStream(fsDest, encryptor, CryptoStreamMode.Write);

        fsSrc.CopyTo(cs); // 非同期の場合は → await CopyToAsync();
    }

    // 暗号化前と暗号化 → 解除して内容が同じかチェックする
    {
        using var fs1 = new FileStream(tagetPath, FileMode.Open, FileAccess.Read);
        using var fs2 = new FileStream(destPath2, FileMode.Open, FileAccess.Read);

        if (fs1.Length != fs2.Length)
        {
            Console.WriteLine("長さが違います。");
            return;
        }
        else
        {
            for (int i = 0; i < fs1.Length; i++)
            {
                int a = fs1.ReadByte();
                int b = fs2.ReadByte();
                if (a != b)
                {
                    Console.WriteLine($"{i}byte目が違います。");
                    return;
                }
            }
        }
        Console.WriteLine("Check OK"); // 問題なければこれが出力される
    }
}

これでメモリに展開することなく暗号化できました。

関連記事

takap-tech.com