複数の画像をパスワードで暗号化、復号化する方法
基本的な仕組み
AES暗号化された画像データ内に、暗号化キー・IV・ソルトの各桁を分散配置
配置位置はユーザーパスワードから決定的に生成
復号化前に正確な位置から鍵情報を除去しないと、データが破損して復号化不可
セキュリティ上の工夫
ファイルごとに異なる配置位置を使用(パターン分析対策)
インデックステーブルチャンクによる位置管理
インデックステーブル自体の位置もパスワード依存
技術的な課題と考慮点
パスワード→位置変換の一貫性
同じパスワードから常に同じ位置を生成する必要
ハッシュ関数(SHA256など)とモジュロ演算の組み合わせが有効
衝突回避
鍵情報の各桁が重複する位置に配置されないよう調整
インデックステーブル領域との衝突回避
データ完全性
画像データサイズに対する配置位置の妥当性チェック
破損検出のためのチェックサム
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
namespace ImageEncryptionSystem
{
/// <summary>
/// 暗号化に使用する鍵情報を格納するクラス
/// AES暗号化に必要なキー、IV、ソルトの情報を保持
/// </summary>
public class EncryptionKeys
{
/// <summary>AES暗号化キー(32バイト)</summary>
public byte[] key { get; set; }
/// <summary>初期化ベクタ(16バイト)</summary>
public byte[] iv { get; set; }
/// <summary>パスワード派生用ソルト(32バイト)</summary>
public byte[] salt { get; set; }
public EncryptionKeys()
{
// AES256用のキーサイズ(32バイト)
key = new byte[32];
// AESのブロックサイズ(16バイト)
iv = new byte[16];
// ソルトサイズ(32バイト)
salt = new byte[32];
}
}
/// <summary>
/// 各ファイルごとの鍵情報配置位置を管理するインデックステーブル
/// パスワードに基づいて決定的に配置位置を生成し、管理する
/// </summary>
public class IndexTable
{
/// <summary>鍵情報の各バイトの配置位置リスト</summary>
public List<int> key_positions { get; set; }
/// <summary>IV情報の各バイトの配置位置リスト</summary>
public List<int> iv_positions { get; set; }
/// <summary>ソルト情報の各バイトの配置位置リスト</summary>
public List<int> salt_positions { get; set; }
/// <summary>インデックステーブル自体の配置開始位置</summary>
public int table_start_position { get; set; }
/// <summary>データ完全性チェック用のチェックサム</summary>
public uint checksum { get; set; }
public IndexTable()
{
key_positions = new List<int>();
iv_positions = new List<int>();
salt_positions = new List<int>();
}
/// <summary>
/// インデックステーブルをバイト配列にシリアライズ
/// 復号化時に同じ形式で読み込めるよう、固定フォーマットで保存
/// </summary>
/// <returns>シリアライズされたバイト配列</returns>
public byte[] ToByteArray()
{
using (var memory_stream = new MemoryStream())
using (var writer = new BinaryWriter(memory_stream))
{
// チェックサムを先頭に書き込み(データ完全性確認用)
writer.Write(checksum);
// テーブル開始位置を記録
writer.Write(table_start_position);
// 鍵位置リストのサイズと内容を書き込み
writer.Write(key_positions.Count);
foreach (int position in key_positions)
{
writer.Write(position);
}
// IV位置リストのサイズと内容を書き込み
writer.Write(iv_positions.Count);
foreach (int position in iv_positions)
{
writer.Write(position);
}
// ソルト位置リストのサイズと内容を書き込み
writer.Write(salt_positions.Count);
foreach (int position in salt_positions)
{
writer.Write(position);
}
return memory_stream.ToArray();
}
}
/// <summary>
/// バイト配列からインデックステーブルをデシリアライズ
/// 暗号化時に保存した形式と同じフォーマットで読み込み
/// </summary>
/// <param name="data_">デシリアライズするバイト配列</param>
/// <returns>復元されたインデックステーブル</returns>
public static IndexTable FromByteArray(byte[] data_)
{
var table = new IndexTable();
using (var memory_stream = new MemoryStream(data_))
using (var reader = new BinaryReader(memory_stream))
{
// チェックサムを読み込み
table.checksum = reader.ReadUInt32();
// テーブル開始位置を読み込み
table.table_start_position = reader.ReadInt32();
// 鍵位置リストを読み込み
int key_count = reader.ReadInt32();
for (int i = 0; i < key_count; i++)
{
table.key_positions.Add(reader.ReadInt32());
}
// IV位置リストを読み込み
int iv_count = reader.ReadInt32();
for (int i = 0; i < iv_count; i++)
{
table.iv_positions.Add(reader.ReadInt32());
}
// ソルト位置リストを読み込み
int salt_count = reader.ReadInt32();
for (int i = 0; i < salt_count; i++)
{
table.salt_positions.Add(reader.ReadInt32());
}
}
return table;
}
/// <summary>
/// データ完全性チェック用のチェックサムを計算
/// 位置データの改ざんを検出するため
/// </summary>
/// <returns>計算されたチェックサム</returns>
public uint CalculateChecksum()
{
uint checksum_value = 0;
// テーブル開始位置をチェックサムに含める
checksum_value += (uint)table_start_position;
// 全ての位置情報をチェックサムに含める
foreach (int position in key_positions)
{
checksum_value += (uint)position;
}
foreach (int position in iv_positions)
{
checksum_value += (uint)position;
}
foreach (int position in salt_positions)
{
checksum_value += (uint)position;
}
return checksum_value;
}
}
/// <summary>
/// 画像暗号化システムのメインクラス
/// パスワードベースの鍵配置システムを使用して、安全な画像暗号化を実現
/// </summary>
public class ImageEncryptionSystem
{
/// <summary>インデックステーブルの識別用マジックナンバー</summary>
private readonly byte[] magic_number = Encoding.UTF8.GetBytes("IMGCRYPT");
/// <summary>暗号化強度を高めるためのPBKDF2イテレーション回数</summary>
private const int pbkdf2_iterations = 100000;
/// <summary>
/// パスワードから決定的に暗号化キーを生成
/// 同じパスワードからは常に同じキーが生成される
/// </summary>
/// <param name="password_">ユーザーが入力したパスワード</param>
/// <param name="salt_">キー派生用のソルト</param>
/// <returns>生成された暗号化キー</returns>
private byte[] GenerateKeyFromPassword(string password_, byte[] salt_)
{
using (var pbkdf2 = new Rfc2898DeriveBytes(password_, salt_, pbkdf2_iterations))
{
// AES256用の32バイトキーを生成
return pbkdf2.GetBytes(32);
}
}
/// <summary>
/// パスワードから決定的に位置を生成するハッシュ関数
/// 同じパスワードと種子からは常に同じ位置が生成される
/// </summary>
/// <param name="password_">ユーザーパスワード</param>
/// <param name="seed_">位置生成用の種子</param>
/// <param name="max_position_">生成する位置の最大値</param>
/// <returns>生成された位置</returns>
private int GeneratePositionFromPassword(string password_, int seed_, int max_position_)
{
// パスワードと種子を組み合わせてハッシュを計算
string combined_input = password_ + seed_.ToString();
byte[] hash_bytes;
using (var sha256 = SHA256.Create())
{
hash_bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(combined_input));
}
// ハッシュ値を整数に変換し、範囲内に収める
int hash_value = BitConverter.ToInt32(hash_bytes, 0);
if (hash_value < 0) hash_value = -hash_value;
return hash_value % max_position_;
}
/// <summary>
/// 使用済み位置との衝突を避けて新しい位置を生成
/// 重複を防ぐため、使用済み位置リストをチェックしながら生成
/// </summary>
/// <param name="password_">ユーザーパスワード</param>
/// <param name="seed_">位置生成用の種子</param>
/// <param name="max_position_">生成する位置の最大値</param>
/// <param name="used_positions_">既に使用済みの位置リスト</param>
/// <returns>衝突しない新しい位置</returns>
private int GenerateUniquePosition(string password_, int seed_, int max_position_, HashSet<int> used_positions_)
{
int attempt = 0;
int position;
// 衝突しない位置が見つかるまで試行を続ける
do
{
position = GeneratePositionFromPassword(password_, seed_ + attempt, max_position_);
attempt++;
// 無限ループを防ぐため、最大試行回数を設定
if (attempt > 10000)
{
throw new InvalidOperationException("一意な位置の生成に失敗しました。データサイズが小さすぎる可能性があります。");
}
} while (used_positions_.Contains(position));
return position;
}
/// <summary>
/// 暗号化処理を実行
/// 画像データを暗号化し、鍵情報を分散配置してインデックステーブルを埋め込む
/// </summary>
/// <param name="image_data_">暗号化する画像データ</param>
/// <param name="password_">ユーザーパスワード</param>
/// <param name="file_identifier_">ファイル識別子(同じパスワードでも異なる配置にするため)</param>
/// <returns>暗号化された画像データ</returns>
public byte[] EncryptImage(byte[] image_data_, string password_, string file_identifier_)
{
// 暗号化キーの生成
var keys = new EncryptionKeys();
// ランダムなソルトとIVを生成
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(keys.salt);
rng.GetBytes(keys.iv);
}
// パスワードからキーを派生
keys.key = GenerateKeyFromPassword(password_, keys.salt);
// 画像データをAES暗号化
byte[] encrypted_data;
using (var aes = Aes.Create())
{
aes.Key = keys.key;
aes.IV = keys.iv;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
using (var encryptor = aes.CreateEncryptor())
{
encrypted_data = encryptor.TransformFinalBlock(image_data_, 0, image_data_.Length);
}
}
// 鍵情報の配置位置を決定
var index_table = GenerateIndexTable(password_, file_identifier_, encrypted_data.Length);
// 暗号化データに鍵情報を分散配置
byte[] result_data = EmbedKeyInformation(encrypted_data, keys, index_table);
// インデックステーブルを埋め込み
result_data = EmbedIndexTable(result_data, index_table);
return result_data;
}
/// <summary>
/// 復号化処理を実行
/// 埋め込まれたインデックステーブルを読み込み、鍵情報を復元して復号化
/// </summary>
/// <param name="encrypted_data_">暗号化された画像データ</param>
/// <param name="password_">ユーザーパスワード</param>
/// <param name="file_identifier_">ファイル識別子</param>
/// <returns>復号化された画像データ</returns>
public byte[] DecryptImage(byte[] encrypted_data_, string password_, string file_identifier_)
{
// インデックステーブルを抽出
var index_table = ExtractIndexTable(encrypted_data_, password_, file_identifier_);
// データ完全性をチェック
if (index_table.checksum != index_table.CalculateChecksum())
{
throw new InvalidOperationException("インデックステーブルが破損しています。");
}
// 鍵情報を抽出
var keys = ExtractKeyInformation(encrypted_data_, index_table);
// 鍵情報を除去して元の暗号化データを復元
byte[] clean_encrypted_data = RemoveKeyInformation(encrypted_data_, index_table);
// インデックステーブルも除去
clean_encrypted_data = RemoveIndexTable(clean_encrypted_data, index_table);
// 復号化を実行
byte[] decrypted_data;
using (var aes = Aes.Create())
{
aes.Key = keys.key;
aes.IV = keys.iv;
aes.Mode = CipherMode.CBC;
aes.Padding = PaddingMode.PKCS7;
using (var decryptor = aes.CreateDecryptor())
{
decrypted_data = decryptor.TransformFinalBlock(clean_encrypted_data, 0, clean_encrypted_data.Length);
}
}
return decrypted_data;
}
/// <summary>
/// パスワードとファイル識別子に基づいてインデックステーブルを生成
/// 各ファイルごとに異なる配置位置を決定的に生成
/// </summary>
/// <param name="password_">ユーザーパスワード</param>
/// <param name="file_identifier_">ファイル識別子</param>
/// <param name="data_length_">データサイズ</param>
/// <returns>生成されたインデックステーブル</returns>
private IndexTable GenerateIndexTable(string password_, string file_identifier_, int data_length_)
{
var index_table = new IndexTable();
var used_positions = new HashSet<int>();
// パスワードとファイル識別子を組み合わせて一意性を確保
string combined_seed = password_ + file_identifier_;
// 必要な総配置数を計算(キー32 + IV16 + ソルト32 = 80バイト)
int total_positions_needed = 32 + 16 + 32;
// データサイズが十分かチェック
if (data_length_ < total_positions_needed + 1000)
{
throw new ArgumentException("データサイズが小さすぎます。最低でも" + (total_positions_needed + 1000) + "バイト必要です。");
}
// 鍵情報の配置位置を生成(キー:32バイト)
for (int i = 0; i < 32; i++)
{
int position = GenerateUniquePosition(combined_seed, i, data_length_, used_positions);
index_table.key_positions.Add(position);
used_positions.Add(position);
}
// IV情報の配置位置を生成(IV:16バイト)
for (int i = 0; i < 16; i++)
{
int position = GenerateUniquePosition(combined_seed, i + 1000, data_length_, used_positions);
index_table.iv_positions.Add(position);
used_positions.Add(position);
}
// ソルト情報の配置位置を生成(ソルト:32バイト)
for (int i = 0; i < 32; i++)
{
int position = GenerateUniquePosition(combined_seed, i + 2000, data_length_, used_positions);
index_table.salt_positions.Add(position);
used_positions.Add(position);
}
// インデックステーブル自体の配置位置を決定
index_table.table_start_position = GenerateUniquePosition(combined_seed, 9999, data_length_, used_positions);
// チェックサムを計算
index_table.checksum = index_table.CalculateChecksum();
return index_table;
}
/// <summary>
/// 暗号化データに鍵情報を分散配置
/// インデックステーブルに基づいて各バイトを指定位置に配置
/// </summary>
/// <param name="encrypted_data_">暗号化された画像データ</param>
/// <param name="keys_">配置する鍵情報</param>
/// <param name="index_table_">配置位置を示すインデックステーブル</param>
/// <returns>鍵情報が埋め込まれたデータ</returns>
private byte[] EmbedKeyInformation(byte[] encrypted_data_, EncryptionKeys keys_, IndexTable index_table_)
{
// データをコピーして変更用のバッファを作成
byte[] result_data = new byte[encrypted_data_.Length];
Array.Copy(encrypted_data_, result_data, encrypted_data_.Length);
// 鍵情報を分散配置
for (int i = 0; i < keys_.key.Length; i++)
{
if (index_table_.key_positions[i] < result_data.Length)
{
result_data[index_table_.key_positions[i]] = keys_.key[i];
}
}
// IV情報を分散配置
for (int i = 0; i < keys_.iv.Length; i++)
{
if (index_table_.iv_positions[i] < result_data.Length)
{
result_data[index_table_.iv_positions[i]] = keys_.iv[i];
}
}
// ソルト情報を分散配置
for (int i = 0; i < keys_.salt.Length; i++)
{
if (index_table_.salt_positions[i] < result_data.Length)
{
result_data[index_table_.salt_positions[i]] = keys_.salt[i];
}
}
return result_data;
}
/// <summary>
/// インデックステーブルをデータに埋め込み
/// マジックナンバーと共に識別可能な形式で保存
/// </summary>
/// <param name="data_">埋め込み対象のデータ</param>
/// <param name="index_table_">埋め込むインデックステーブル</param>
/// <returns>インデックステーブルが埋め込まれたデータ</returns>
private byte[] EmbedIndexTable(byte[] data_, IndexTable index_table_)
{
byte[] table_data = index_table_.ToByteArray();
byte[] result_data = new byte[data_.Length];
Array.Copy(data_, result_data, data_.Length);
// インデックステーブルの配置位置を確認
if (index_table_.table_start_position + magic_number.Length + 4 + table_data.Length > result_data.Length)
{
throw new InvalidOperationException("インデックステーブルを配置するスペースが不足しています。");
}
// マジックナンバーを埋め込み
Array.Copy(magic_number, 0, result_data, index_table_.table_start_position, magic_number.Length);
// テーブルサイズを埋め込み
byte[] size_bytes = BitConverter.GetBytes(table_data.Length);
Array.Copy(size_bytes, 0, result_data, index_table_.table_start_position + magic_number.Length, 4);
// テーブルデータを埋め込み
Array.Copy(table_data, 0, result_data, index_table_.table_start_position + magic_number.Length + 4, table_data.Length);
return result_data;
}
/// <summary>
/// 暗号化データからインデックステーブルを抽出
/// パスワードに基づいて配置位置を特定し、テーブルを復元
/// </summary>
/// <param name="encrypted_data_">暗号化されたデータ</param>
/// <param name="password_">ユーザーパスワード</param>
/// <param name="file_identifier_">ファイル識別子</param>
/// <returns>抽出されたインデックステーブル</returns>
private IndexTable ExtractIndexTable(byte[] encrypted_data_, string password_, string file_identifier_)
{
// パスワードとファイル識別子からテーブル位置を計算
string combined_seed = password_ + file_identifier_;
int table_position = GeneratePositionFromPassword(combined_seed, 9999, encrypted_data_.Length);
// マジックナンバーを確認
if (table_position + magic_number.Length + 4 > encrypted_data_.Length)
{
throw new InvalidOperationException("インデックステーブルが見つかりません。");
}
// マジックナンバーの一致を確認
for (int i = 0; i < magic_number.Length; i++)
{
if (encrypted_data_[table_position + i] != magic_number[i])
{
throw new InvalidOperationException("インデックステーブルのマジックナンバーが一致しません。");
}
}
// テーブルサイズを読み込み
int table_size = BitConverter.ToInt32(encrypted_data_, table_position + magic_number.Length);
// テーブルデータを抽出
if (table_position + magic_number.Length + 4 + table_size > encrypted_data_.Length)
{
throw new InvalidOperationException("インデックステーブルのサイズが不正です。");
}
byte[] table_data = new byte[table_size];
Array.Copy(encrypted_data_, table_position + magic_number.Length + 4, table_data, 0, table_size);
// インデックステーブルを復元
var index_table = IndexTable.FromByteArray(table_data);
index_table.table_start_position = table_position;
return index_table;
}
/// <summary>
/// 分散配置された鍵情報を抽出
/// インデックステーブルの位置情報に基づいて各バイトを収集
/// </summary>
/// <param name="data_">鍵情報が埋め込まれたデータ</param>
/// <param name="index_table_">位置情報を持つインデックステーブル</param>
/// <returns>抽出された鍵情報</returns>
private EncryptionKeys ExtractKeyInformation(byte[] data_, IndexTable index_table_)
{
var keys = new EncryptionKeys();
// 鍵情報を抽出
for (int i = 0; i < keys.key.Length; i++)
{
if (index_table_.key_positions[i] < data_.Length)
{
keys.key[i] = data_[index_table_.key_positions[i]];
}
}
// IV情報を抽出
for (int i = 0; i < keys.iv.Length; i++)
{
if (index_table_.iv_positions[i] < data_.Length)
{
keys.iv[i] = data_[index_table_.iv_positions[i]];
}
}
// ソルト情報を抽出
for (int i = 0; i < keys.salt.Length; i++)
{
if (index_table_.salt_positions[i] < data_.Length)
{
keys.salt[i] = data_[index_table_.salt_positions[i]];
}
}
return keys;
}
/// <summary>
/// 分散配置された鍵情報を除去
/// 復号化前に鍵情報を取り除いて元の暗号化データを復元
/// </summary>
/// <param name="data_">鍵情報が埋め込まれたデータ</param>
/// <param name="index_table_">位置情報を持つインデックステーブル</param>
/// <returns>鍵情報が除去されたデータ</returns>
private byte[] RemoveKeyInformation(byte[] data_, IndexTable index_table_)
{
// 除去する位置のセットを作成
var positions_to_remove = new HashSet<int>();
// 鍵情報の位置を追加
positions_to_remove.UnionWith(index_table_.key_positions);
positions_to_remove.UnionWith(index_table_.iv_positions);
positions_to_remove.UnionWith(index_table_.salt_positions);
// 範囲チェック
positions_to_remove.RemoveWhere(pos => pos >= data_.Length);
// 除去後のデータサイズを計算
int new_size = data_.Length - positions_to_remove.Count;
byte[] result_data = new byte[new_size];
// 除去対象でない位置のデータをコピー
int result_index = 0;
for (int i = 0; i < data_.Length; i++)
{
if (!positions_to_remove.Contains(i))
{
result_data[result_index] = data_[i];
result_index++;
}
}
return result_data;
}
/// <summary>
/// インデックステーブルを除去
/// 復号化前にインデックステーブル部分を取り除く
/// </summary>
/// <param name="data_">インデックステーブルが埋め込まれたデータ</param>
/// <param name="index_table_">除去するインデックステーブル</param>
/// <returns>インデックステーブルが除去されたデータ</returns>
private byte[] RemoveIndexTable(byte[] data_, IndexTable index_table_)
{
// インデックステーブルのサイズを計算
byte[] table_data = index_table_.ToByteArray();
int table_total_size = magic_number.Length + 4 + table_data.Length;
// 除去後のデータサイズを計算
int new_size = data_.Length - table_total_size;
byte[] result_data = new byte[new_size];
// テーブル部分を除去してコピー
int result_index = 0;
for (int i = 0; i < data_.Length; i++)
{
// インデックステーブルの範囲をスキップ
if (i >= index_table_.table_start_position &&
i < index_table_.table_start_position + table_total_size)
{
continue;
}
result_data[result_index] = data_[i];
result_index++;
}
return result_data;
}
}
/// <summary>
/// 使用例を示すサンプルクラス
/// 実際のアプリケーションでの使用方法を示す
/// </summary>
public class Program
{
public static void Main(string[] args)
{
try
{
// サンプル画像データ(実際の使用ではファイルから読み込み)
byte[] sample_image_data = CreateSampleImageData();
// 暗号化システムのインスタンスを作成
var encryption_system = new ImageEncryptionSystem();
// ユーザーが設定するパスワード
string user_password = "MySecurePassword123!";
// ファイル識別子(ファイル名やパスから生成)
string file_identifier = "sample_image.jpg";
Console.WriteLine("画像暗号化システムのテストを開始します...");
Console.WriteLine($"元画像データサイズ: {sample_image_data.Length} バイト");
// 暗号化処理を実行
Console.WriteLine("暗号化処理を実行中...");
byte[] encrypted_data = encryption_system.EncryptImage(sample_image_data, user_password, file_identifier);
Console.WriteLine($"暗号化データサイズ: {encrypted_data.Length} バイト");
// 暗号化されたデータをファイルに保存(実際の使用例)
string encrypted_file_path = "encrypted_image.dat";
File.WriteAllBytes(encrypted_file_path, encrypted_data);
Console.WriteLine($"暗号化データを {encrypted_file_path} に保存しました。");
// 復号化処理を実行
Console.WriteLine("復号化処理を実行中...");
byte[] decrypted_data = encryption_system.DecryptImage(encrypted_data, user_password, file_identifier);
Console.WriteLine($"復号化データサイズ: {decrypted_data.Length} バイト");
// データの完全性を確認
bool data_integrity_check = CompareByteArrays(sample_image_data, decrypted_data);
Console.WriteLine($"データ完全性チェック: {(data_integrity_check ? "成功" : "失敗")}");
// 復号化されたデータをファイルに保存(検証用)
string decrypted_file_path = "decrypted_image.jpg";
File.WriteAllBytes(decrypted_file_path, decrypted_data);
Console.WriteLine($"復号化データを {decrypted_file_path} に保存しました。");
// 間違ったパスワードでの復号化テスト
Console.WriteLine("間違ったパスワードでの復号化テストを実行中...");
try
{
byte[] failed_decrypt = encryption_system.DecryptImage(encrypted_data, "WrongPassword", file_identifier);
Console.WriteLine("警告: 間違ったパスワードで復号化が成功してしまいました。");
}
catch (Exception ex)
{
Console.WriteLine($"正常: 間違ったパスワードで復号化に失敗しました。({ex.GetType().Name})");
}
Console.WriteLine("テストが完了しました。");
}
catch (Exception ex)
{
Console.WriteLine($"エラーが発生しました: {ex.Message}");
Console.WriteLine($"詳細: {ex.StackTrace}");
}
}
/// <summary>
/// テスト用のサンプル画像データを作成
/// 実際の使用では実際の画像ファイルを読み込む
/// </summary>
/// <returns>サンプル画像データ</returns>
private static byte[] CreateSampleImageData()
{
// 実際の画像ファイルを模擬したサンプルデータ
// 実際の使用では File.ReadAllBytes("image.jpg") などを使用
byte[] sample_data = new byte[50000]; // 50KB のサンプルデータ
// ランダムなデータで埋める(実際の画像データを模擬)
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(sample_data);
}
// 先頭部分にJPEGヘッダーを模擬したデータを配置
sample_data[0] = 0xFF;
sample_data[1] = 0xD8;
sample_data[2] = 0xFF;
sample_data[3] = 0xE0;
return sample_data;
}
/// <summary>
/// 2つのバイト配列が同じ内容かどうかを比較
/// データの完全性確認に使用
/// </summary>
/// <param name="array1_">比較対象の配列1</param>
/// <param name="array2_">比較対象の配列2</param>
/// <returns>同じ内容の場合はtrue、異なる場合はfalse</returns>
private static bool CompareByteArrays(byte[] array1_, byte[] array2_)
{
if (array1_.Length != array2_.Length)
{
return false;
}
for (int i = 0; i < array1_.Length; i++)
{
if (array1_[i] != array2_[i])
{
return false;
}
}
return true;
}
}
/// <summary>
/// Windows フォームアプリケーション向けのファイル処理ヘルパークラス
/// 実際のデスクトップアプリケーションで使用する際の補助機能を提供
/// </summary>
public class FileProcessingHelper
{
private readonly ImageEncryptionSystem encryption_system;
public FileProcessingHelper()
{
encryption_system = new ImageEncryptionSystem();
}
/// <summary>
/// 画像ファイルを暗号化してファイルに保存
/// ユーザーインターフェースから呼び出されることを想定
/// </summary>
/// <param name="input_file_path_">入力画像ファイルのパス</param>
/// <param name="output_file_path_">出力暗号化ファイルのパス</param>
/// <param name="password_">ユーザーが設定したパスワード</param>
/// <returns>処理が成功した場合はtrue</returns>
public bool EncryptImageFile(string input_file_path_, string output_file_path_, string password_)
{
try
{
// 入力ファイルの存在確認
if (!File.Exists(input_file_path_))
{
throw new FileNotFoundException($"入力ファイルが見つかりません: {input_file_path_}");
}
// 画像データを読み込み
byte[] image_data = File.ReadAllBytes(input_file_path_);
// ファイル名から識別子を生成
string file_identifier = Path.GetFileName(input_file_path_);
// 暗号化を実行
byte[] encrypted_data = encryption_system.EncryptImage(image_data, password_, file_identifier);
// 暗号化データをファイルに保存
File.WriteAllBytes(output_file_path_, encrypted_data);
return true;
}
catch (Exception ex)
{
// ログ出力やユーザーへの通知処理を追加
Console.WriteLine($"暗号化処理中にエラーが発生しました: {ex.Message}");
return false;
}
}
/// <summary>
/// 暗号化ファイルを復号化して画像ファイルに保存
/// ユーザーインターフェースから呼び出されることを想定
/// </summary>
/// <param name="input_file_path_">入力暗号化ファイルのパス</param>
/// <param name="output_file_path_">出力画像ファイルのパス</param>
/// <param name="password_">ユーザーが入力したパスワード</param>
/// <param name="original_file_name_">元のファイル名(識別子として使用)</param>
/// <returns>処理が成功した場合はtrue</returns>
public bool DecryptImageFile(string input_file_path_, string output_file_path_, string password_, string original_file_name_)
{
try
{
// 入力ファイルの存在確認
if (!File.Exists(input_file_path_))
{
throw new FileNotFoundException($"入力ファイルが見つかりません: {input_file_path_}");
}
// 暗号化データを読み込み
byte[] encrypted_data = File.ReadAllBytes(input_file_path_);
// 復号化を実行
byte[] decrypted_data = encryption_system.DecryptImage(encrypted_data, password_, original_file_name_);
// 復号化データをファイルに保存
File.WriteAllBytes(output_file_path_, decrypted_data);
return true;
}
catch (Exception ex)
{
// ログ出力やユーザーへの通知処理を追加
Console.WriteLine($"復号化処理中にエラーが発生しました: {ex.Message}");
return false;
}
}
/// <summary>
/// 複数のファイルを同じパスワードで一括暗号化
/// バッチ処理機能を提供
/// </summary>
/// <param name="input_file_paths_">入力ファイルパスのリスト</param>
/// <param name="output_directory_">出力ディレクトリのパス</param>
/// <param name="password_">共通のパスワード</param>
/// <returns>処理結果のリスト(成功/失敗)</returns>
public List<bool> BatchEncryptFiles(List<string> input_file_paths_, string output_directory_, string password_)
{
var results = new List<bool>();
// 出力ディレクトリが存在しない場合は作成
if (!Directory.Exists(output_directory_))
{
Directory.CreateDirectory(output_directory_);
}
foreach (string input_path in input_file_paths_)
{
try
{
// 出力ファイル名を生成
string file_name = Path.GetFileNameWithoutExtension(input_path);
string output_path = Path.Combine(output_directory_, file_name + ".encrypted");
// 個別に暗号化処理を実行
bool success = EncryptImageFile(input_path, output_path, password_);
results.Add(success);
}
catch (Exception ex)
{
Console.WriteLine($"ファイル {input_path} の処理中にエラーが発生しました: {ex.Message}");
results.Add(false);
}
}
return results;
}
/// <summary>
/// 暗号化ファイルの検証
/// ファイルが正しく暗号化されているかを確認
/// </summary>
/// <param name="encrypted_file_path_">検証対象の暗号化ファイルパス</param>
/// <param name="password_">検証用のパスワード</param>
/// <param name="original_file_name_">元のファイル名</param>
/// <returns>検証結果(成功/失敗)</returns>
public bool ValidateEncryptedFile(string encrypted_file_path_, string password_, string original_file_name_)
{
try
{
// ファイルの存在確認
if (!File.Exists(encrypted_file_path_))
{
return false;
}
// 暗号化データを読み込み
byte[] encrypted_data = File.ReadAllBytes(encrypted_file_path_);
// 最小サイズチェック
if (encrypted_data.Length < 1000)
{
return false;
}
// 復号化を試行(例外が発生しなければ有効)
byte[] decrypted_data = encryption_system.DecryptImage(encrypted_data, password_, original_file_name_);
// 復号化データが空でないことを確認
return decrypted_data.Length > 0;
}
catch (Exception)
{
// 復号化に失敗した場合は無効なファイル
return false;
}
}
/// <summary>
/// サポートされている画像形式かどうかを確認
/// ファイル拡張子に基づいて判定
/// </summary>
/// <param name="file_path_">確認対象のファイルパス</param>
/// <returns>サポートされている場合はtrue</returns>
public bool IsSupportedImageFormat(string file_path_)
{
string extension = Path.GetExtension(file_path_).ToLower();
// サポートされている画像形式のリスト
var supported_extensions = new HashSet<string>
{
".jpg", ".jpeg", ".png", ".bmp", ".gif", ".tiff", ".tif", ".webp"
};
return supported_extensions.Contains(extension);
}
/// <summary>
/// ファイルサイズを人間が読みやすい形式に変換
/// ユーザーインターフェースでの表示用
/// </summary>
/// <param name="file_path_">ファイルパス</param>
/// <returns>読みやすい形式のファイルサイズ文字列</returns>
public string GetReadableFileSize(string file_path_)
{
if (!File.Exists(file_path_))
{
return "ファイルが見つかりません";
}
long file_size = new FileInfo(file_path_).Length;
if (file_size < 1024)
{
return $"{file_size} バイト";
}
else if (file_size < 1024 * 1024)
{
return $"{file_size / 1024.0:F1} KB";
}
else if (file_size < 1024 * 1024 * 1024)
{
return $"{file_size / (1024.0 * 1024.0):F1} MB";
}
else
{
return $"{file_size / (1024.0 * 1024.0 * 1024.0):F1} GB";
}
}
}
}主要クラスの構成
EncryptionKeys: 暗号化に使用するキー、IV、ソルトを管理
IndexTable: 鍵情報の配置位置を管理し、シリアライズ/デシリアライズ機能を提供
ImageEncryptionSystem: メインの暗号化/復号化処理を実行
FileProcessingHelper: 実際のファイル処理を行うヘルパークラス
重要な実装ポイント
セキュリティ機能
PBKDF2: パスワードから強力な暗号化キーを生成(100,000回イテレーション)
AES-256-CBC: 強力な暗号化アルゴリズムを使用
決定的位置生成: 同じパスワードから常に同じ配置位置を生成
衝突回避: 鍵情報が重複する位置に配置されることを防止
データ完全性
チェックサム: インデックステーブルの改ざんを検出
マジックナンバー: インデックステーブルの識別用
範囲チェック: 配置位置の妥当性を確認
ユーザビリティ
バッチ処理: 複数ファイルの一括暗号化
ファイル検証: 暗号化ファイルの有効性確認
エラーハンドリング: 詳細なエラーメッセージと例外処理
使用方法
// 基本的な使用例
var encryptionSystem = new ImageEncryptionSystem();
byte[] imageData = File.ReadAllBytes("image.jpg");
byte[] encryptedData = encryptionSystem.EncryptImage(imageData, "password", "image.jpg");
// 復号化
byte[] decryptedData = encryptionSystem.DecryptImage(encryptedData, "password", "image.jpg");

コメント