複数の画像をパスワードで暗号化、復号化する方法

基本的な仕組み

  • AES暗号化された画像データ内に、暗号化キー・IV・ソルトの各桁を分散配置

  • 配置位置はユーザーパスワードから決定的に生成

  • 復号化前に正確な位置から鍵情報を除去しないと、データが破損して復号化不可

セキュリティ上の工夫

  • ファイルごとに異なる配置位置を使用(パターン分析対策)

  • インデックステーブルチャンクによる位置管理

  • インデックステーブル自体の位置もパスワード依存

技術的な課題と考慮点

  1. パスワード→位置変換の一貫性

    • 同じパスワードから常に同じ位置を生成する必要

    • ハッシュ関数(SHA256など)とモジュロ演算の組み合わせが有効

  2. 衝突回避

    • 鍵情報の各桁が重複する位置に配置されないよう調整

    • インデックステーブル領域との衝突回避

  3. データ完全性

    • 画像データサイズに対する配置位置の妥当性チェック

    • 破損検出のためのチェックサム

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";
            }
        }
    }
}

主要クラスの構成

  1. EncryptionKeys: 暗号化に使用するキー、IV、ソルトを管理

  2. IndexTable: 鍵情報の配置位置を管理し、シリアライズ/デシリアライズ機能を提供

  3. ImageEncryptionSystem: メインの暗号化/復号化処理を実行

  4. 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");

いいなと思ったら応援しよう!

コメント

ログイン または 会員登録 するとコメントできます。
複数の画像をパスワードで暗号化、復号化する方法|古井和雄
word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word word

mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1
mmMwWLliI0fiflO&1