JsonUtility で書き出したセーブファイルは、メモ帳で開くと「”gold”:1200」とそのまま読めてしまいます。数字を 999999 に書き換えて保存し直せば、それだけで大金持ち。個人で作ったゲームを公開すると、意外と早い段階でこういう改造をされます。
この記事では、Unityのセーブデータを暗号化する方法を「自作」と「アセット」に分けて比べます。分かるのは、暗号化で防げることと防げないこと、自作するときのAESのコード例、暗号化に対応したアセット4本の違い(2026年10月時点の価格と更新日つき)、そして自分のゲームにはどれが合うか、の4つです。
セーブ以外も含めて、定番アセットをまとめて見ておきたい人はこちらもどうぞ。
セーブデータの暗号化で防げること・防げないこと
先に、暗号化の限界をはっきりさせておきます。ここを知らずにアセットを買うと、「暗号化したのに改造された」とがっかりすることになりかねません。
防げること:ファイルを開いて数字を書き換える改造
暗号化すると、セーブファイルは意味のないバイト列になります。メモ帳やバイナリエディタで開いても、どこが所持金でどこがレベルなのか見分けがつきません。ネットで配られている「セーブエディタ」のような手軽な改造ツールは、だいたいこの段階で使えなくなります。
防げないこと:鍵を抜き出す改造と、メモリの書き換え
ゲームはセーブを読み込むために、復号の鍵(パスワード)を必ず自分の中に持っています。つまり、ビルドしたゲームを解析されれば鍵は見つかる、ということです。Easy Save の公式ドキュメントでも、コードに書いたパスワードはリバースエンジニアリングで取り出されうる、と注意書きがあります。
もう1つ、Cheat Engine のようなツールで、プレイ中のメモリ上の数値を直接書き換える改造もあります。こちらはセーブファイルを経由しないので、ファイルの暗号化では止まりません。
ランキング、課金アイテム、対戦の戦績のように「他のプレイヤーに影響するデータ」は、端末の中のセーブを信用しない作りにするのが基本です。サーバー側で記録・検証するしかありません。暗号化は、あくまで改造の手間を大きく増やすための対策です。
「暗号化」と「改ざんの検知」は別もの
意外と見落とされがちなのがここ。AESで暗号化しただけのファイルでも、バイトを1つ書き換えることはできます。復号すると壊れたデータになるか、運が悪いとエラーにならずに妙な値が読み込まれることもあります。
書き換えられたことを確実に見抜きたいなら、暗号化に加えて「署名(HMAC)」を付けて、読み込み時に照合する必要があります。後で紹介する Anti-Cheat Toolkit は、この改ざん検知を売りにしているアセットです。
暗号化は自作とアセット、どちらを選ぶ?
判断の目安は「すでにセーブの仕組みがあるかどうか」だと思います。
JSON や PlayerPrefs でセーブ機能を作り終えていて、ファイルを読めなくしたいだけなら、自作で十分です。.NET に入っている AES のクラスを使えば、100行もかからずに書けます。JSONでのセーブの作り方は、こちらの記事で手順を追って説明しています。
【Unity】JSONを使ったセーブ・ロード:初心者でも簡単に実装!
反対に、これからセーブ機能を作る段階なら、暗号化つきのセーブアセットを入れてしまうほうが早いです。セーブスロットやバックアップも一緒に手に入りますし、鍵の作り方やIVの扱いで間違える心配もありません。メモリ改造まで対策したいなら、選択肢は実質 Anti-Cheat Toolkit になります。
自作するなら:AESとHMACでセーブを暗号化するコード例
ここでは、JsonUtility で作った文字列を AES で暗号化し、さらに HMAC-SHA256 の署名を付けてファイルに保存するクラスを作ります。使っているのは System.Security.Cryptography の Aes、Rfc2898DeriveBytes、HMACSHA256 で、どれも Unity 6 の既定の API 互換レベル(.NET Standard 2.1)で使えるクラスです。
using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;
[Serializable]
public class SaveData
{
public int gold;
public int level;
}
public static class SecureSave
{
// 鍵の元になる合言葉とソルト(自分のゲームでは必ず別の文字列に変える)
const string Password = "change-this-password";
static readonly byte[] Salt = Encoding.UTF8.GetBytes("my-game-salt-01");
static string FilePath => Path.Combine(Application.persistentDataPath, "save.dat");
// 合言葉から「暗号化用」と「改ざんチェック用」の鍵を32バイトずつ作る
static void MakeKeys(out byte[] encKey, out byte[] macKey)
{
using (var kdf = new Rfc2898DeriveBytes(Password, Salt, 100000, HashAlgorithmName.SHA256))
{
encKey = kdf.GetBytes(32);
macKey = kdf.GetBytes(32);
}
}
public static void Save(SaveData data)
{
byte[] plain = Encoding.UTF8.GetBytes(JsonUtility.ToJson(data));
MakeKeys(out byte[] encKey, out byte[] macKey);
byte[] iv, cipher;
using (Aes aes = Aes.Create())
{
aes.Key = encKey;
aes.GenerateIV(); // 保存のたびに新しいIVを作る
iv = aes.IV;
using (ICryptoTransform enc = aes.CreateEncryptor())
{
cipher = enc.TransformFinalBlock(plain, 0, plain.Length);
}
}
// ファイルの中身は [IV 16バイト][暗号文][署名 32バイト] の順
byte[] file = new byte[iv.Length + cipher.Length + 32];
Buffer.BlockCopy(iv, 0, file, 0, iv.Length);
Buffer.BlockCopy(cipher, 0, file, iv.Length, cipher.Length);
using (var hmac = new HMACSHA256(macKey))
{
byte[] mac = hmac.ComputeHash(file, 0, iv.Length + cipher.Length);
Buffer.BlockCopy(mac, 0, file, iv.Length + cipher.Length, 32);
}
File.WriteAllBytes(FilePath, file);
}
// 読めなかった時(書き換え・破損)は null を返す
public static SaveData Load()
{
if (!File.Exists(FilePath)) return new SaveData();
byte[] file = File.ReadAllBytes(FilePath);
if (file.Length < 16 + 16 + 32) return null;
MakeKeys(out byte[] encKey, out byte[] macKey);
int bodyLength = file.Length - 32;
using (var hmac = new HMACSHA256(macKey))
{
byte[] mac = hmac.ComputeHash(file, 0, bodyLength);
int diff = 0;
for (int i = 0; i < 32; i++) diff |= mac[i] ^ file[bodyLength + i];
if (diff != 0)
{
Debug.LogWarning("セーブデータが書き換えられています");
return null;
}
}
using (Aes aes = Aes.Create())
{
aes.Key = encKey;
byte[] iv = new byte[16];
Buffer.BlockCopy(file, 0, iv, 0, 16);
aes.IV = iv;
using (ICryptoTransform dec = aes.CreateDecryptor())
{
byte[] plain = dec.TransformFinalBlock(file, 16, bodyLength - 16);
return JsonUtility.FromJson<SaveData>(Encoding.UTF8.GetString(plain));
}
}
}
}
使う側は、SecureSave.Save(data) で保存し、var data = SecureSave.Load() で読み込むだけ。Load が null を返したら「データが壊れているか書き換えられている」ので、新しいデータで始め直すか、バックアップから戻す処理につなげます。
このコードは、Unityのエディタではなく .NET の環境で、.NET Standard 2.1 向けのビルドが通ること、保存したファイルの1バイトを書き換えると Load が null を返すことを確かめました(JsonUtility と Application の部分は代わりの処理に置き換えています)。
コードの中でやっていること
合言葉をそのまま鍵にせず、Rfc2898DeriveBytes で32バイトの鍵を2本作っているのは、暗号化用と署名用で鍵を分けるためです。IV(初期化ベクトル)は保存のたびに作り直してファイルの先頭に書いておきます。こうすると、同じデータを2回保存してもファイルの中身が毎回変わり、「どこが所持金か」を比べて探す手がかりがなくなります。
署名の照合で、1バイトずつ見比べて途中で抜けずに最後まで回しているのは、比べるのにかかった時間から正しい署名を推測される手口への備えです。セーブデータでそこまで気にする場面は少ないものの、書き方として覚えておいて損はないと思います。
自作でつまずきやすい3つのこと
1つ目は、合言葉やソルトを後から変えると、それまでのセーブが全部読めなくなること。公開後に変えるなら、古い鍵で読んで新しい鍵で保存し直す移行の処理が必要です。
2つ目は、暗号化を入れる前のセーブファイルが残っていること。エディタで試すときは、一度セーブファイルを消してから確かめてください。セーブファイルがどこにあるかは、こちらの記事にOS別でまとめています。
【Unity】セーブデータの保存場所|Windows・Android別の確認と削除・変更
3つ目は、鍵を作る処理(Rfc2898DeriveBytes)が、わざと時間がかかるように作られていることです。オートセーブを数秒おきに呼ぶようなゲームなら、最初に1回だけ鍵を作って変数に持っておく形に直すと軽くなります。
Unityのセーブデータ暗号化アセット4選
ここからはアセットです。価格はUSD、更新日は2026年10月時点のストア表示で、無料の2本から順に並べました。セーブ機能そのものの比較は月曜の記事でやったので、今回は「暗号化まわり」に絞って見ていきます。
1. Easy Save(暗号化の設定が一番かんたん)
2011年からストアにある定番のセーブアセットで、価格は65ドル。最新版は2026年7月の公開です。
公式ドキュメントによると、暗号化の方式は128ビットのAESで、初期状態ではオフ。デフォルト設定でオンにするか、ES3Settings を渡して保存・読み込みのたびに指定します。
// 合言葉を指定してAESで暗号化する
var settings = new ES3Settings(ES3.EncryptionType.AES, "myPassword");
ES3.Save("gold", gold, settings);
gold = ES3.Load<int>("gold", defaultValue: 0, settings: settings);
既定値は defaultValue: と名前を付けて渡すのがポイント。名前を付けずに書くと、2番目の引数がファイルのパスとして扱われることがある、とドキュメントにも注意があります。
自作だと30行以上かかった処理が、設定1行で済むわけです。圧縮やセーブスロット、バックアップも同じアセットの中で完結します。
気をつけたいのは、暗号化をオンにしても、それまでの暗号化されていないセーブはそのまま残ること。ドキュメントでは、エディタなら Tools > Easy Save 3 > Clear Persistent Data Path で消すよう案内されています。また、アプリストアによっては暗号化を使っていることの申告が必要になる場合がある、という注意書きもありました。iOS で出す人は確認しておきましょう。
Easy Save の基本の使い方は、こちらの記事でセーブポイントを作りながら紹介しています。
【EasySave】セーブポイントを作成してセーブ&ロードする
2. ESave – Save System for Unity(無料で暗号化を選べる)
Esper Code が無料で出しているセーブアセットです。最新版は2026年8月公開、互換性の欄は Unity 6000.3。
ストアの説明では、AESで「すべてのセーブ、または選んだセーブだけ」を暗号化できるとされています。設定データは平文、進行データだけ暗号化、といった分け方ができるのは便利ですね。大きなファイルを裏で読み書きする機能もあり、無料でここまでそろっているのは貴重だと思います。
一方で、改ざんの検知についてはストアの説明に記載がありません。書き換えを見抜く仕組みまで欲しいなら、自作のHMACを足すか、4本目の Anti-Cheat Toolkit を検討することになります。
3. Quick Save(無料・AESとGzip圧縮)
無料で公開されているセーブ用のアセットです。ストアの説明によると、保存するファイルをAESで暗号化でき、Gzip圧縮でファイルを小さくする機能も付いています。JSONでのシリアライズやCSVの読み書きにも対応し、ソースコードもすべて入っています。
ただ、最新版は2024年2月の公開で、互換性の欄は Unity 2019.4 のまま。対応プラットフォームの一覧に WebGL の記載もありません。ストアのページにあるドキュメントへの導線も今はたどりにくく、Unity 6 で新しく始めるなら、更新が続いている ESave を先に試すほうが安心だと思います。古いプロジェクトですでに使っている人向け、という位置づけです。
4. Anti-Cheat Toolkit(改ざん検知とメモリ改造の対策まで)
Code Stage が2013年から更新を続けているチート対策のアセットで、価格は88ドル。2026年10月にも新しい版が出ていて、Unity 2022.3 以降と Unity 6 に対応しています。
セーブまわりでは、PlayerPrefs の代わりに使う ObscuredPrefs と、ファイル用の ObscuredFile が入っています。どちらも暗号化に加えて改ざんを検知でき、セーブを特定の端末やユーザーIDに結びつける(他人のセーブを持ち込めなくする)こともできます。
using CodeStage.AntiCheat.Storage;
// PlayerPrefs と同じ感覚で書ける
ObscuredPrefs.Set("gold", 1200);
int gold = ObscuredPrefs.Get("gold", 0);
// 書き換えられたデータを読んだ時に呼ばれる
ObscuredPrefs.NotGenuineDataDetected += () => Debug.LogWarning("改ざんを検知");
公式ドキュメントによると、既存の PlayerPrefs のデータを移す仕組みもあるので、PlayerPrefs で作ったゲームに後から入れやすいのも特徴です。さらに Obscured Types(メモリ上の値を隠す型)や、スピードハック・時刻改ざんの検知など、セーブ以外のチート対策もまとめて入っています。
ストアの説明でも「100%防げるアンチチートはない」とはっきり書かれています。課金やランキングがあるゲームで、手軽な改造をまとめて止めたい人向けです。セーブを読めなくしたいだけなら、ここまでの機能はいりません。
暗号化アセット4本の比較表
| アセット | 価格 | 最新版 | 暗号化 | 改ざん検知 | 向いている人 |
|---|---|---|---|---|---|
| Easy Save | 65ドル | 2026年7月 | AES(設定1行) | 記載なし | セーブ機能ごと任せたい |
| ESave | 無料 | 2026年8月 | AES(ファイルごとに選べる) | 記載なし | 無料で暗号化を試したい |
| Quick Save | 無料 | 2024年2月 | AES+Gzip圧縮 | 記載なし | 古いプロジェクトで使っている |
| Anti-Cheat Toolkit | 88ドル | 2026年10月 | ObscuredPrefs / ObscuredFile | あり(端末ロックも) | 課金・ランキングがある |
「改ざん検知」の列は、ストアの説明とドキュメントで確認できたかどうかです。記載なしのアセットでも、暗号化されていれば書き換えた部分は正しく読めなくなります。
目的別:セーブの暗号化はこう選ぶ
セーブ機能をもう作ってある → 自作のAES+HMAC(この記事のコード)で十分です。
これからセーブ機能を作る・無料で済ませたい → ESave
セーブスロットやクラウド保存も含めて、長く使える定番がほしい → Easy Save
課金・ランキングがあって、メモリ改造やスピードハックも気になる → Anti-Cheat Toolkit(セーブは Easy Save などと併用も可)
個人的には、ソロで遊ぶゲームなら暗号化だけで十分で、改ざん検知まで必要になるのはランキングや課金が絡むときだと考えています。守りたいものの大きさに合わせて選ぶのが、お金と手間をかけすぎないコツです。
セーブ機能そのものをアセットで選びたい人は、無料の2本と定番の Easy Save を比べたこちらの記事が参考になります。
有料の暗号化アセットはセールのタイミングで
Easy Save の65ドル、Anti-Cheat Toolkit の88ドルは、個人開発だと気軽に出せる金額ではありません。Unity Asset Store では年に何回か大きなセールがあり、ツール系のアセットも半額前後になることがあります。急ぎでなければ、無料の ESave や自作のコードでつないでおいて、セールで買い替えるのも手です。
今どんなセールをやっているかは、毎月更新しているこちらのページで確認できます。
まとめ:セーブの暗号化は「何を守りたいか」で決める
セーブデータの暗号化で防げるのは、ファイルを開いて数字を書き換える手軽な改造まで。鍵はゲームの中にあるので、本気で解析されれば破られますし、メモリの書き換えは別の対策が必要です。
ファイルを読めなくしたいだけなら、自作のAES+HMACか無料の ESave で足ります。セーブ機能ごと任せたいなら Easy Save、課金やランキングを守りたいなら Anti-Cheat Toolkit。この順番で考えると迷いにくいはずです。
有料の2本を買うなら、今月のAsset Storeセール情報を先にのぞいてみてください。




