今日からすぐに使えるUnity実例集の第3弾「デバイス時間の改ざん対策」を紹介します。
これは「他のアプリでよく見るアレ」を実装していく企画です。いずれあなたがピンポイントで求めていた実装を紹介するかもしれませんので、是非チェックしてください。
≪ 前回の記事
次回の記事 ≫
放置報酬やログインボーナスを実装すると、ほぼ確実に出てくる問題があります。
それは……
「端末の時間を進めれば無限にもらえるのでは?」

という問題。
第二弾で紹介したオフライン時間計算は便利ですが、そのままだと“時間変更チート”に弱いのも事実です。
今回は、最低限入れておきたいデバイス時間改ざん対策を、コピペで動く形で紹介します。
実際に行われる日付改ざんの手法
「時間改ざん」と聞くと大げさに感じますが、やり方はとても単純です。
隠すほどの情報でもないので手法を公開しておきます。
① 端末の日時を未来に変更する
- ゲームを終了
- 端末設定から日時を手動変更
- 1日〜1年未来へ進める
- ゲーム起動 → 放置報酬大量獲得
放置報酬ロジックが DateTime.Now だけで計算されていると、これだけで簡単に突破されます。
② 一度未来に進めてから現在に戻す
- 未来へ進めて報酬を獲得
- 端末時間を元に戻す
単純な実装だと、「未来分の報酬は獲得済み」「現在時刻との差分が負になる」といった矛盾が起きます。
このとき負の時間チェックを入れていないと検出できません。
③ タイムゾーン変更
・日本 → ハワイ
・UTC+9 → UTC-10
このようにタイムゾーンを変更するだけで、実質的に時間をずらせる場合があります。
そのため、DateTime.UtcNow を使うのはほぼ必須です。
対応方針
やることはシンプルです。
- 前回終了時の「UTC時刻(A)」を保存
- 前回終了時の「起動からの経過秒(B)」も保存
- 次回起動時に(A)と(B)に矛盾がないかチェック
(B)は Time.realtimeSinceStartup で取得します。
これはアプリ起動からの経過秒なので、デバイス時刻の変更とは無関係です。
これを併用することで、「時間だけ進めた」ケースを検出できます。
コピペで動く改ざん検出スクリプト
using UnityEngine;
using System;
public class TimeTamperGuard : MonoBehaviour
{
private const string LAST_UTC_KEY = "LastUtcTime";
private const string LAST_REALTIME_KEY = "LastRealtime";
void Start()
{
CheckTimeTamper();
}
void OnApplicationPause(bool pause)
{
if (pause)
{
SaveTimeSnapshot();
}
}
void OnApplicationQuit()
{
SaveTimeSnapshot();
}
void SaveTimeSnapshot()
{
string utcNow = DateTime.UtcNow.ToString("o");
float realtimeNow = Time.realtimeSinceStartup;
PlayerPrefs.SetString(LAST_UTC_KEY, utcNow);
PlayerPrefs.SetFloat(LAST_REALTIME_KEY, realtimeNow);
PlayerPrefs.Save();
}
void CheckTimeTamper()
{
if (!PlayerPrefs.HasKey(LAST_UTC_KEY))
{
SaveTimeSnapshot();
return;
}
string lastUtcStr = PlayerPrefs.GetString(LAST_UTC_KEY);
DateTime lastUtc = DateTime.Parse(lastUtcStr, null, System.Globalization.DateTimeStyles.RoundtripKind);
DateTime utcNow = DateTime.UtcNow;
double utcDiff = (utcNow - lastUtc).TotalSeconds;
float lastRealtime = PlayerPrefs.GetFloat(LAST_REALTIME_KEY);
float realtimeNow = Time.realtimeSinceStartup;
float realtimeDiff = realtimeNow - lastRealtime;
// realtimeはアプリ再起動でほぼ0から始まるため、
// ここでは「負の時間」や「異常な未来時間」を検出する
if (utcDiff < 0)
{
Debug.LogWarning("端末時間が過去に変更された可能性があります");
OnTimeTamperDetected();
}
// 例:30日以上進んでいたら不正扱い
if (utcDiff > 30 * 24 * 60 * 60)
{
Debug.LogWarning("異常な時間経過を検出しました");
OnTimeTamperDetected();
}
}
void OnTimeTamperDetected()
{
// ここにペナルティ処理を書く
// 例:放置報酬を無効化
Debug.Log("時間改ざん検出:報酬を無効化します");
}
}
このスクリプトを
GameManagerなど常駐オブジェクトにアタッチすればOKです。
最低限やるならこれだけでOK
今回の実装は、
- 過去に戻したケースの検出
- 異常に未来へ飛ばしたケースの検出
この2つに絞っています。
完璧な不正対策をするなら、
- サーバー時刻との照合
- Firebase / Cloud Functions での検証
などが必要になりますが、
個人開発レベルなら今回の対策だけでも十分効果があります。
なぜ今回の対策で防げるのか
今回の実装では、
- UTCで保存している
- 負の時間を検出している
- 異常な未来時間に上限を設けている
ため、「過去へ戻す改ざん」「極端な未来ジャンプ」は最低限検出できます。
完全防御ではありませんが、「何もしていない状態」と比べると格段に安全になります。
まとめ
デバイス時間改ざんは、放置系ゲームではほぼ確実に起きます。
最低限やるべきことは、
・UTC時刻を保存する
・負の時間を検出する
・異常な未来時間を検出する
たったこれだけです。
小さな対策ですが、入れておくかどうかでゲームバランスは大きく変わります。


