-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSecureGameDataManager.cs
More file actions
199 lines (171 loc) · 7.66 KB
/
SecureGameDataManager.cs
File metadata and controls
199 lines (171 loc) · 7.66 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
namespace HeavyDutySaveSystem
{
// --- 1. 保存するデータ構造(かなり多めに定義) ---
public class GameState
{
public string SaveSlotName { get; set; } = "Slot_01";
public DateTime SaveTimeStamp { get; set; }
public PlayerStats Player { get; set; } = new PlayerStats();
public List<string> Inventory { get; set; } = new List<string>();
public Dictionary<string, bool> QuestFlags { get; set; } = new Dictionary<string, bool>();
public string DataHash { get; set; } = ""; // 改ざんチェック用
}
public class PlayerStats
{
public string Name { get; set; } = "Hero";
public int Level { get; set; } = 1;
public long TotalExp { get; set; } = 0;
public double PlayTimeSeconds { get; set; } = 0.0;
}
// --- 2. 暗号化&保存エンジン ---
public static class SaveEngine
{
// AES暗号化用設定(本来は環境変数や別ファイルで管理すべき秘匿情報)
private static readonly string SecretPass = "Complex_P@ssw0rd_99!#";
private static readonly byte[] Salt = Encoding.UTF8.GetBytes("Super_Long_Salt_For_Security");
private static readonly string FileName = "game_data.bin";
private static readonly string BackupName = "game_data.bak";
/// <summary>
/// データを暗号化して非同期で保存
/// </summary>
public static async Task SaveAsync(GameState data)
{
try
{
data.SaveTimeStamp = DateTime.Now;
// 1. JSON化
string json = JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true });
// 2. ハッシュ値を生成(改ざん防止)
data.DataHash = ComputeHash(json);
json = JsonSerializer.Serialize(data); // ハッシュを含めて再度シリアライズ
// 3. バックアップ作成
if (File.Exists(FileName))
{
File.Copy(FileName, BackupName, true);
}
// 4. 暗号化処理
byte[] encryptedData = EncryptString(json);
// 5. 書き込み
await File.WriteAllBytesAsync(FileName, encryptedData);
Console.WriteLine($"[SUCCESS] {DateTime.Now}: データを暗号化して保存しました。");
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] 保存中に致命的なエラーが発生: {ex.Message}");
}
}
/// <summary>
/// 暗号化ファイルをロードして復号
/// </summary>
public static async Task<GameState> LoadAsync()
{
if (!File.Exists(FileName))
{
Console.WriteLine("[INFO] セーブデータが存在しません。新規作成します。");
return new GameState();
}
try
{
// 1. ファイル読み込み
byte[] encryptedData = await File.ReadAllBytesAsync(FileName);
// 2. 復号
string json = DecryptBytes(encryptedData);
// 3. デシリアライズ
var data = JsonSerializer.Deserialize<GameState>(json);
// 4. 改ざんチェック
string storedHash = data.DataHash;
data.DataHash = ""; // ハッシュ計算のためにクリア
string currentHash = ComputeHash(JsonSerializer.Serialize(data, new JsonSerializerOptions { WriteIndented = true }));
if (storedHash != currentHash)
{
throw new CryptographicException("警告: セーブデータの改ざんが検出されました!");
}
data.DataHash = storedHash; // ハッシュを戻す
Console.WriteLine("[SUCCESS] データの読み込みと整合性チェックに成功しました。");
return data;
}
catch (Exception ex)
{
Console.WriteLine($"[CRITICAL] ロード失敗: {ex.Message}");
// 失敗した場合はバックアップから復旧を試みるなどの処理をここに書く
return new GameState();
}
}
// --- 内部暗号化ロジック (AES-256) ---
private static byte[] EncryptString(string plainText)
{
using (Aes aes = Aes.Create())
{
var keyDerivation = new Rfc2898DeriveBytes(SecretPass, Salt, 10000, HashAlgorithmName.SHA256);
aes.Key = keyDerivation.GetBytes(32);
aes.IV = keyDerivation.GetBytes(16);
using (var ms = new MemoryStream())
{
using (var cs = new CryptoStream(ms, aes.CreateEncryptor(), CryptoStreamMode.Write))
{
byte[] plainBytes = Encoding.UTF8.GetBytes(plainText);
cs.Write(plainBytes, 0, plainBytes.Length);
}
return ms.ToArray();
}
}
}
private static string DecryptBytes(byte[] cipherData)
{
using (Aes aes = Aes.Create())
{
var keyDerivation = new Rfc2898DeriveBytes(SecretPass, Salt, 10000, HashAlgorithmName.SHA256);
aes.Key = keyDerivation.GetBytes(32);
aes.IV = keyDerivation.GetBytes(16);
using (var ms = new MemoryStream(cipherData))
{
using (var cs = new CryptoStream(ms, aes.CreateDecryptor(), CryptoStreamMode.Read))
{
using (var sr = new StreamReader(cs))
{
return sr.ReadToEnd();
}
}
}
}
}
private static string ComputeHash(string input)
{
using (SHA256 sha256 = SHA256.Create())
{
byte[] bytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(input));
return Convert.ToBase64String(bytes);
}
}
}
// --- 3. 実行メイン ---
class Program
{
static async Task Main()
{
Console.WriteLine("=== 高機能セキュア・ストレージ・デモ ===");
// 1. ロード
GameState myGame = await SaveEngine.LoadAsync();
Console.WriteLine($"[現在の状態] 名前:{myGame.Player.Name} | Lv:{myGame.Player.Level} | 所持品数:{myGame.Inventory.Count}");
// 2. データの変更
myGame.Player.Level++;
myGame.Player.TotalExp += 500;
myGame.Inventory.Add($"伝説の剣 +{myGame.Player.Level}");
myGame.QuestFlags["チュートリアル完了"] = true;
Console.WriteLine("データを更新しました。保存中...");
// 3. セーブ
await SaveEngine.SaveAsync(myGame);
Console.WriteLine("\n保存された 'game_data.bin' をバイナリエディタやメモ帳で見ても、");
Console.WriteLine("中身はAES256で守られているため読めません。");
Console.WriteLine("\n終了するには何かキーを押してください...");
Console.ReadKey();
}
}
}