マガジン

ゲームのセーブデータを暗号化すべきか

遊びをつくる

執筆にClaudeを使用しています。

スマートフォンとノートパソコンの枠を、赤いゲームの駒と雲のアーチがつなぐ切り絵
記事のテーマをもとに制作したAI生成イラスト。

ブラウザゲームを作っていると、セーブデータの扱いで一度は迷います。「改ざんされたら困る」「でも暗号化は難しそう」——この二つの気持ちがぶつかる場面です。結論から言うと、暗号化が必要かどうかはゲームの設計によって変わります。すべてのゲームに暗号化が必要なわけでも、逆にすべてのゲームで不要なわけでもありません。 この記事では「なぜ暗号化するのか」「どんなときに省略できるのか」「実際にどう実装するのか」の三点を順に整理します。実装の具体例はJavaScriptとlocalStorageを前提にしますが、考え方はほかの環境にも応用できます。

この記事の目次
  1. セーブデータの「守り方」は目的によって変わる
  2. 暗号化を省略してよいケースと必要なケース
  3. 実際にブラウザゲームで使える保護の手法
  4. 実装の前に決めておきたい設計の問い
  5. 次の行動
  6. 参考資料

セーブデータの「守り方」は目的によって変わる

まず整理したいのは、暗号化が解決できる問題とできない問題の違いです。

暗号化(encryption)はデータを読み取れない形に変換する操作で、主に「のぞき見」を防ぐ目的に使います。一方、ゲームのセーブデータで多くの作者が気にするのは「改ざん(tamper)」、つまりプレイヤーがデータを書き換えてスコアや所持アイテムを不正に増やすことです。

この二つは別の問題で、対策も異なります。改ざんを防ぎたい場合に本当に有効なのは、セーブデータの整合性チェック(ハッシュ検証)や、データをサーバー側で保持する設計です。暗号化だけでは、暗号を解読してから書き換えて再暗号化されてしまえば防げません。ブラウザのlocalStorageはユーザー自身がいつでも読み書きできる領域なので、クライアント側の暗号化はあくまで「少し手間を増やす」程度の抑止効果にとどまります。

暗号化を省略してよいケースと必要なケース

省略できる目安

次の条件がすべて当てはまる場合、暗号化の優先度は低いと判断して構いません。

  • ランキングやPvPなど、他プレイヤーとの比較がない純粋なシングルプレイゲームである
  • スコアやクリア状況が不正に増えても、ほかのプレイヤーや作者の利益に影響しない
  • ゲーム内にプレイヤーの個人情報や課金情報を保存しない
  • 「やりこみの記録を自分で楽しむ」用途に特化しており、改ざんは自己責任の範囲に収まる

パズルゲームや一人用のアドベンチャーゲームの多くはこの条件に当てはまります。セーブデータが書き換えられたとしても実害が本人にしか及ばないため、実装コストをかけてまで暗号化する必要はないと考えられます。

暗号化(または追加の保護)を検討すべき目安

一方、次のいずれかに当てはまる場合は、何らかの保護を検討する価値があります。

  • グローバルランキングやスコアボードに書き込まれる数値をローカルに一時保存している
  • プレイヤーが支払った仮想通貨・アイテム数量など金銭的価値を持つデータをローカルに持つ
  • 複数プレイヤーが同じ端末を使うケースを想定しており、あるプレイヤーのデータを別のプレイヤーが覗ける状況を避けたい
  • 体験設計上「ネタバレになるデータ(未解放のエンディング情報など)」をローカルに持つ

ただし、このうち金銭的価値を持つデータや実績の不正については、クライアント側の暗号化だけでは根本解決になりません。サーバー検証が必要な設計かどうかをまず判断してください。

実際にブラウザゲームで使える保護の手法

ハッシュによる改ざん検知

改ざんを「防ぐ」のではなく「検知して無効化する」方針が現実的です。セーブデータをJSON文字列にして保存するとき、同時にそのハッシュ値も一緒に保存し、ロード時に一致を確認します。

// 保存例(概念コード)
const data = JSON.stringify(saveObject);
const hash = simpleHash(data + SECRET_SALT);
localStorage.setItem('save', data);
localStorage.setItem('save_hash', hash);

// 読み込み例
const data = localStorage.getItem('save');
const hash = localStorage.getItem('save_hash');
if (simpleHash(data + SECRET_SALT) !== hash) {
  // 改ざん検知 → 初期化またはエラー表示
}

SECRET_SALTはゲームのJavaScriptコード内に埋め込む文字列です。ソースを読まれれば突破できますが、カジュアルな改ざんを防ぐ抑止力として機能します。simpleHashにはWeb Crypto APIのcrypto.subtle.digest(SHA-256)が使えます。非同期になりますが、現代のブラウザで広く対応しています。

Web Crypto APIを使った対称暗号

本格的に暗号化したい場合、ブラウザ標準のWeb Crypto APIでAES-GCMによる対称暗号が使えます。流れは次の通りです。

  1. crypto.subtle.generateKeyでAES-GCMキーを生成する
  2. crypto.subtle.encryptでJSONデータを暗号化する
  3. キーをcrypto.subtle.exportKeyでJWK形式にし、暗号文と一緒にlocalStorageに保存する
  4. ロード時は逆順にimportKey → decryptを行う

注意点があります。キーをデータと同じlocalStorageに保存するのは、厳密な秘匿性の観点では意味が薄いです(キーも一緒に取得できてしまいます)。あくまで「ファイルをそのままコピーして解析されるのを難しくする」用途と割り切ってください。サーバーでキーを管理する設計にすれば強度が上がりますが、その場合はサーバー実装が必要です。

サーバー側保存という選択肢

アソバースのSDKに対応したゲームはクラウドセーブが使えます。クラウドセーブを使う設計では、セーブデータ本体がサーバー側に置かれるため、ユーザーがブラウザのlocalStorageを操作してもゲームの進行データが書き換わりません。改ざんへの耐性という観点では、クライアント側で暗号化するより確実です。

実装の前に決めておきたい設計の問い

暗号化ライブラリや実装を選ぶ前に、次の問いを自分のゲームに当てはめてみてください。

  • 誰から何を守りたいのか。 好奇心旺盛なプレイヤーによる軽い改ざん? それとも競技性のある場でのスコア不正? 前者と後者では必要な対策の重さが異なります。
  • 改ざんされた場合の影響範囲はどこか。 本人だけか、他プレイヤーや運営に波及するかを確認してください。
  • ゲームの寿命と保守コストは釣り合うか。 短期間の公開ゲームに複雑な暗号実装を入れると、バグや鍵管理の手間が本来の開発時間を圧迫します。

これらの問いに答えた上で「ハッシュ検知で十分」「暗号化まで必要」「サーバーで管理する」のどれが適切か判断すると、実装方針が自然に決まります。

次の行動

自分のゲームのセーブデータに何が含まれているかをまずリストアップし、「改ざんされたとき誰が困るか」を確認してみてください。カジュアルなシングルプレイゲームであればハッシュ検知から試すのが現実的な出発点です。クラウドセーブを使いたい場合はアソバースのSDKドキュメントで対応方法を確認してください。

参考資料

  • MDN Web Docs — Web Crypto API(crypto.subtle)
  • MDN Web Docs — localStorage
  • WHATWG — Web Storage仕様