-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSecureGameDataManager.js
More file actions
156 lines (133 loc) · 5.34 KB
/
SecureGameDataManager.js
File metadata and controls
156 lines (133 loc) · 5.34 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
/**
* セキュア・ストレージ・マネージャー (Pure JavaScript / Web Crypto API)
* 外部ライブラリ一切不要。AES-256-GCMによる暗号化と改ざん検知を実装。
*/
class SecureStorage {
constructor(password) {
this.password = password;
this.storageKey = "APP_SECURE_DATA_V1";
this.salt = new TextEncoder().encode("Fixed_Unique_Salt_99"); // ソルト(固定)
this.iterations = 100000; // PBKDF2の反復回数
}
/**
* パスワード文字列から暗号化用の「鍵」を生成する (PBKDF2)
*/
async _deriveKey() {
const encoder = new TextEncoder();
const passwordBuffer = encoder.encode(this.password);
// 1. パスワードをインポート
const baseKey = await crypto.subtle.importKey(
"raw",
passwordBuffer,
"PBKDF2",
false,
["deriveKey"]
);
// 2. PBKDF2でストレッチングしてAES用の256bit鍵を生成
return await crypto.subtle.deriveKey(
{
name: "PBKDF2",
salt: this.salt,
iterations: this.iterations,
hash: "SHA-256"
},
baseKey,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"]
);
}
/**
* データを暗号化してLocalStorageに保存
* @param {Object} dataObj - 保存したいJSオブジェクト
*/
async save(dataObj) {
try {
const key = await this._deriveKey();
const iv = crypto.getRandomValues(new Uint8Array(12)); // GCM用の初期化ベクトル
const encoder = new TextEncoder();
const plainData = encoder.encode(JSON.stringify(dataObj));
// AES-GCMで暗号化(認証タグも自動付与される)
const encryptedBuffer = await crypto.subtle.encrypt(
{ name: "AES-GCM", iv: iv },
key,
plainData
);
// IV(12byte) + 暗号文 を結合
const combined = new Uint8Array(iv.length + encryptedBuffer.byteLength);
combined.set(iv, 0);
combined.set(new Uint8Array(encryptedBuffer), iv.length);
// バイナリをBase64文字列に変換して保存
const base64String = btoa(String.fromCharCode(...combined));
localStorage.setItem(this.storageKey, base64String);
console.log("%c[Success] データを暗号化してLocalStorageに格納しました。", "color: green; font-weight: bold;");
} catch (error) {
console.error("[Fatal] 保存に失敗しました:", error);
}
}
/**
* LocalStorageからデータを読み込んで復号
* @returns {Object|null} 復号されたオブジェクト
*/
async load() {
const base64 = localStorage.getItem(this.storageKey);
if (!base64) {
console.warn("セーブデータが見つかりません。新規作成用の初期値を返します。");
return null;
}
try {
// Base64をバイナリに戻す
const combined = new Uint8Array(atob(base64).split("").map(c => c.charCodeAt(0)));
// 先頭12バイトがIV、残りが暗号文
const iv = combined.slice(0, 12);
const encryptedData = combined.slice(12);
const key = await this._deriveKey();
// 復号実行(改ざんされているとここでエラーが投下される)
const decryptedBuffer = await crypto.subtle.decrypt(
{ name: "AES-GCM", iv: iv },
key,
encryptedData
);
const decoder = new TextDecoder();
const jsonString = decoder.decode(decryptedBuffer);
console.log("%c[Success] データの復号と整合性チェックに成功しました。", "color: blue; font-weight: bold;");
return JSON.parse(jsonString);
} catch (error) {
console.error("%c[Error] 復号に失敗!データが改ざんされたかパスワードが違います。", "color: red;");
return null;
}
}
/**
* データの完全削除
*/
clear() {
localStorage.removeItem(this.storageKey);
console.log("ストレージをクリアしました。");
}
}
/**
* --- 使用例 ---
*/
(async () => {
// 1. インスタンス化 (強力なパスワードを指定)
const storage = new SecureStorage("P@ssw0rd_Strong_2024!");
// 2. ロードを試みる
let mySaveData = await storage.load();
if (!mySaveData) {
// 初回起動時のデフォルトデータ
mySaveData = {
playerName: "勇者",
level: 1,
inventory: ["ひのきのぼう"],
lastUpdate: new Date().toISOString()
};
}
console.log("読み込んだデータ:", mySaveData);
// 3. データを更新
mySaveData.level += 1;
mySaveData.inventory.push("鋼の剣");
mySaveData.lastUpdate = new Date().toISOString();
// 4. 暗号化してセーブ
await storage.save(mySaveData);
console.log("完了。ブラウザのDevTools > Application > LocalStorage を確認してください。");
})();