【GDevelop】セーブデータは暗号化しよう!
GDevelop でセーブ機能を実装したいときは、多くの場合「ストレージ」機能を使うことになると思います。
このストレージ機能は、PC でもスマホでも、アプリでもブラウザ(HTML5)でも、ちゃんと機能してくれるのでとても重宝するのですが、実は簡単にセーブデータをいじれてしまうという弱点があります。
特に HTML5 ゲームがやばい
GDevelop のストレージ機能はゲームが HTML5 で配信される場合は、Web Storage という機能を利用してデータを保存します。
しかし、この Web Storage はブラウザの標準機能で簡単に閲覧・書き換えができてしまいます。
- Chrome ならデベロッパー ツール(DevTools)
- Edge なら開発者ツール(DevTools)
- Firefox なら開発ツール
- Safari なら Web インスペクタ
つまりそれを知っているプレイヤーは、勝手にセーブデータを書き換えてズルができてしまうのです😥
そのため HTML5 で配信される、いわゆるブラウザゲームのほとんどは、Web Storage に保存するデータを暗号化しています。
(そもそも大事なデータは Web Storage ではなく、サーバー側に保存していたりしますが……)
実はセーブデータを暗号化してくれるゲームエンジンは意外と少なく、多くの場合、各自で独自の防衛策を講じる必要があります。
(国産ゲームエンジンはしてくれるの多いけど😉)
残念ながら今の GDevelop(v5.0.121)のストレージ機能にも、暗号化をしてくれる機能はありません。
そこでこのページでは、簡単にセーブデータを改竄(かいざん)されないようにするための、基本的な暗号化方法についてご紹介します。
なお、アプリで配信する場合も、セーブデータがどのように保存されているか私が知らないだけで、もしかしたら Web Storage 同様、簡単に操作できてしまう状態かもしれないので、HTML5 ゲームと同様に暗号化しておくことをお勧めします。
暗号化に利用できる拡張機能の紹介
そのものズバリな拡張機能はありませんが、組み合わせることでセーブデータを暗号化することができる拡張機能をご紹介します。
拡張機能をインストールする方法については「新しい拡張機能のインストールの仕方」をご覧ください。
・Compressor 拡張機能
文字列を zip 圧縮した文字列にしてくれます。
もちろん解凍もできます。
詳しい使い方については「Compressor 拡張機能の使い方」をご覧ください。
・Unicode (UnicodeConversion) 拡張機能
文字列を数字(Unicode のコードポイント)に変えてくれる拡張機能です。
もちろんその逆、数字を文字列に戻すこともできます。
詳しい使い方については「Unicode (UnicodeConversion) 拡張機能の使い方」をご覧ください。
・Hash 拡張機能
文字列からハッシュ値を生成します。
このハッシュ値は改竄(かいざん)検出に利用できます。
詳しい使い方については「Hash 拡張機能の使い方」をご覧ください。
これらの拡張機能を使えば、比較的簡単にセーブデータを暗号化する事ができます。
ほんと GDevelop の貢献者さんたち優秀すぎ!😆
Compressor 拡張機能を利用した暗号化の例
圧縮するためには、まずセーブデータをテキスト化する必要があります。
具体的なテキスト化の方法については、「【GDevelop】セーブデータを JSON 化するとメリットしか無い件」をご覧ください。
以下でも、そのページの例を元に説明します。
圧縮するために変更したのは、次の画像の赤線の個所です。
保存するときは、JSON 化したシーン変数を Compressor::Compress() で圧縮して、それを保存しています。
逆に読み込むときは、セーブデータを Compressor::Decompress() で解凍してから、JSON 解析してシーン変数へ戻しています。
このようにイベントを組んだ結果、セーブデータは次の画像のような値になります。
いい感じに意味不明なデータになりましたね👍(ストレージのグループ名によりセーブデータであることがバレバレですが😅)
このように、拡張機能を追加して式をちょこっと修正するだけでも、普通の人にはナンノコッチャ分からないデータにできるので、これだけでも改竄による不正行為をかなり防止できるのではないかと思います。
ただしこの方法は、改竄されて正しくない ZIP データになってしまった場合、解凍時にエラーが発生しゲームは正しく機能しなくなります😥
あくまでも「読めなくなれば OK!」というレベルの、簡易的な対策であることに注意してください。
ハッシュ値で完全性をチェック!
たとえ暗号化していても、セーブデータは編集できてしまうので、変にいじられて正しくないデータになってしまうと、JSON の解析に失敗したり、変数がおかしくなったりして、ゲームの進行に影響が出てしまいます。
そこで利用するのがハッシュ値です。
保存したい文字列からハッシュ値を生成し、そのハッシュ値も保存しておくことで、セーブデータが改竄されていないかチェックすることができます。
Hash 拡張機能を利用したデータ完全性チェックの例
GDevelop でも、Hash 拡張機能をインストールすればハッシュ生成ができるようになるので、それを利用した例です。
また、この例では改竄によりセーブデータが破壊されていても、読み込み時にエラーとなりにくい Unicode 拡張機能を利用しています。
※ Compressor 拡張機能に比べ、構造上、不正なデータになりにくいだけです。
それぞれの機能拡張の詳しい使い方は「Hash 拡張機能の使い方」と「Unicode (UnicodeConversion) 拡張機能の使い方」をご覧ください。
保存するときは、JSON 化したシーン変数を UnicodeConversion::TextToUnicode() で数字化して、それを保存しています。
また、JSON 化したシーン変数のハッシュ値も、合わせて保存しています。
逆に読み込むときは、セーブデータを UnicodeConversion::UnicodeToText() で文字列に戻してから、JSON 解析してシーン変数へ戻しています。
そして、その直後に JSON 化したシーン変数のハッシュ値と、読み込んだハッシュ値を比較して、一致しない場合はエラー処理(この例ではストレージを削除し、シーン「エラー」へ移動)を行うようにしています。
このようにイベントを組んだ結果、セーブデータは次の画像のような値になります。
セーブデータと合わせて、ハッシュ値も保存されていることが確認できますね。(実際にはもっと分かりにくい名称にしましょう😅)
なお、この例では日本語を含む JSON をそのまま利用してハッシュ値を生成していますが、この Hash 拡張機能のハッシュ関数は、一部の文字を正しく処理することができません。
(常に等しく正しい処理ができていないので、一応実用性はあります😅)
(SHA-256 に至っては英数字にしか対応していません)
そのため、すべての文字を正しく処理できる他のシステムで生成したハッシュ値とは、元データが同じでも一致しない場合があるのでご注意ください。
また、この例のように保存したハッシュ値をそのまま保存する場合は、暗号化する前の文字列を利用してハッシュ値を生成するようにしましょう。
暗号化後の文字列、つまり保存されている文字列からハッシュ値を生成し、それをチェックする方法だと、改竄した文字列から生成したハッシュ値に書き換えてしまえば、チェックをすり抜けることができてしまい、意味がなくなります。
これは暗号化と言えるのか?
セキュリティや暗号化の専門家でもなんでもない私ですら「同じプロセスを踏むだけで復号できてしまうこれは暗号と言えるのか?」と感じるので、正確には暗号化しているとは言えないかもしれません😅
強いて言うなら難読化かな?
しかし、それでも一見しただけではなんだか分からなくなる上に、少しでも改竄するとエラーとなるので、やらないよりは全然マシだと思います。
もし、もう一歩踏み込んだ暗号化をしたいという方は、「シーザー暗号」の採用を検討してみてください。
例えば文字列を数値化し、その数値に独自の数値を足す(あるいは引く)といった手法です。
単純ですが独自の数値が分からないと、正しく復号できないので効果的です。
まぁ、独自の数値をいかに秘匿するか、という問題もありますが……😅
といった感じで、みなさんも独自の防衛策を考えてみてくださいね。
おしまい!
コメント
コメントを投稿