目次
概要
暗号化されたファイルを元の状態に戻す(復号する)には、暗号化時と逆のストリーム処理を行います。 ファイルから読み込んだ暗号データを CryptoStream(読み取りモード)に通すことで平文に戻し、その結果を別のファイルへ書き出します。この処理もストリーム間コピーを利用するため、メモリ消費を抑えながら巨大なファイルを復元できます。
仕様(入出力)
- 入力: 暗号化されたファイルのストリーム、Key、IV。
- 出力: 復号されたデータを書き込む先のファイルストリーム。
- 動作: 暗号化ストリームから読み出したデータをAESで復号し、順次出力ファイルへ書き込む。
基本の使い方
暗号化された入力ファイルストリームを CryptoStream でラップし、復号モード(Read)で読み出します。
// 入力元(暗号化ファイル)
using var fsIn = File.OpenRead("encrypted.dat");
// 復号用フィルタを通すストリーム
// 入力(fsIn) -> 復号(cs) -> 読み出し
using var cs = new CryptoStream(fsIn, decryptor, CryptoStreamMode.Read);
// 出力先(復元ファイル)
using var fsOut = File.Create("restored.txt");
// 復号されたデータを書き込む
cs.CopyTo(fsOut);
コード全文
前回の暗号化コードと対になる、ファイル復号の実装です。 KeyとIVは、必ず暗号化時と同じバイト配列を指定する必要があります。
using System;
using System.IO;
using System.Security.Cryptography;
class Program
{
static void Main()
{
// 動作確認のため、事前に正規のKeyとIVを用意したと仮定します
// (本来は暗号化時に出力されたKey/IVを使用してください)
// ここではサンプル用に新規生成していますが、これでは既存のファイルは復号できません
using var aes = Aes.Create();
aes.GenerateKey();
aes.GenerateIV();
byte[] validKey = aes.Key;
byte[] validIv = aes.IV;
// 1. 復号クラスの初期化
var encrypter = new StreamEncrypter(validKey, validIv);
string encryptedFile = "crypted.dat";
string restoredFile = "original.txt";
// (テスト用:ファイルがないとエラーになるため、ダミー作成)
if (!File.Exists(encryptedFile))
{
// 本来は暗号化されたファイルが必要
File.WriteAllBytes(encryptedFile, new byte[16]);
Console.WriteLine("※テスト用のダミーファイルを作成しました。正しく復号できません。");
}
Console.WriteLine($"復号開始: {encryptedFile} -> {restoredFile}");
try
{
// 2. ストリームを開いて復号を実行
using (var inStream = File.Open(encryptedFile, FileMode.Open, FileAccess.Read))
using (var outStream = File.Open(restoredFile, FileMode.Create, FileAccess.Write))
{
encrypter.Decrypt(inStream, outStream);
}
Console.WriteLine("復号が完了しました。");
}
catch (CryptographicException ex)
{
Console.WriteLine($"暗号化エラー: KeyまたはIVが間違っているか、ファイルが破損しています。\n{ex.Message}");
}
catch (Exception ex)
{
Console.WriteLine($"エラー: {ex.Message}");
}
}
}
public class StreamEncrypter
{
public byte[] Key { get; private set; }
public byte[] IV { get; private set; }
public StreamEncrypter(byte[] key = null, byte[] iv = null)
{
using var aes = Aes.Create();
Key = key ?? aes.Key;
IV = iv ?? aes.IV;
}
/// <summary>
/// 入力ストリーム(暗号)を復号して出力ストリーム(平文)に書き込みます
/// </summary>
public void Decrypt(Stream inStream, Stream outStream)
{
using var aes = Aes.Create();
aes.Key = Key;
aes.IV = IV;
// 復号器(Decryptor)を作成
using var decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
// 入力ストリームをCryptoStreamでラップする (Mode.Read)
// cryptoStreamから「読む」ときに復号処理が行われる
using var cryptoStream = new CryptoStream(inStream, decryptor, CryptoStreamMode.Read);
// 復号されたデータをすべて出力ストリームにコピーする
cryptoStream.CopyTo(outStream);
}
}
カスタムポイント
- ストリームの向き: 暗号化時は
outStreamをラップしてWriteしましたが、復号時はinStreamをラップしてReadする構成が一般的です(直感的に「暗号データから読み出して元に戻す」流れになるため)。 - パディング: AESはブロック単位(16バイト)で処理するため、ファイル末尾にはパディング(埋め草)が含まれています。
CryptoStreamを読み切る(CloseまたはDisposeされる)タイミングで、このパディングが自動的に削除され、元のファイルサイズに戻ります。
注意点
- 鍵の不一致: 最も多いエラーは
CryptographicException: Padding is invalid and cannot be removed.です。これはKeyまたはIVが暗号化時と異なる場合に発生します。 - 途中終了:
CopyToが最後まで完了せずに処理が中断した場合、復元されたファイルは不完全な状態(破損ファイル)となります。usingブロックで確実に完了させる必要があります。
まとめ
ファイルの復号処理には CryptoStream の Read モードを使用します。入力ストリーム(暗号化ファイル)を CryptoStream で包み込み、そこから出てくる復号データを CopyTo メソッドで出力ストリーム(復元ファイル)へ流し込むことで完了します。正常に復号するためには、暗号化時に使用したものと完全に一致するKeyとIVが不可欠であり、これらが異なるとパディングエラーにより処理は失敗します。
