【UE5/チート対策】通信パケットを暗号化するAES-GCM PacketHandlerプラグインを使ってみる
こんにちは、プログラマのサトウです。
今回は、UE5の標準プラグインである「AES GCM network packet handler」を簡単に触ってみたいと思います。
「AES GCM network packet handler」は、サーバーとクライアント間の通信パケットを暗号化するプラグインです。昨今のオンラインゲームにおいて切っても切り離せない問題として”チート”がありますが、中には通信パケットを改竄するチートも存在しています。
そんなわけで、通信パケットを暗号化するこのプラグインは、チート対策として一定の効果がありそうですよね。そんな便利な機能がUE5には標準プラグインとして用意されているのですから、これを使わない手はありません。
ここでは、この「AES GCM network packet handler」を有効化するところから動作確認するところまでを、お手軽な方法で試してみたいと思います。エンジンのバージョンはUnreal Engine 5.4を使用します。
※本稿では、このプラグインで使用されている暗号化アルゴリズム「AES-GCM」については深堀りしません。
プラグイン「AES GCM network packet handler」とは?
先述した通り、このプラグインは通信パケットを暗号化するためのプラグインです。UE5の標準プラグインですが、実はUE4のときから既に用意されていました。
そしてUE5.4では、この他にも「AES network packet handler」や「DTLS network packet handler」などがありますが、現在推奨されているものは「AES GCM network packet handler」となっています。(※1)
※1:Tech Note: Out of Date Networking Info in the UE5 Migration Guide
このプラグインで使用されている暗号化アルゴリズムは「AES-GCM」というものですが、これはUnreal Engineに限らず世の中のあらゆるシステムで利用されている一般的なアルゴリズムです。検索してみると詳細な資料がたくさん見つかりますので、そちらをぜひ参考にしてみてください。
実際に「AES GCM network packet handler」を使ってみる
ここからは実際にプラグインを使ってみたいと思います。ここで紹介する方法は、UE4でも同じような手法で実現することができます。また、プラグインを使用するにはC++が必須となります。
プラグインを有効化する
「AES GCM network packet handler」を使用するには、まずプラグインを有効化する必要があります。
エディタのメニューバーにあるEdit > PluginsからPluginsウィンドウを開き、「AES GCM network packet handler」を探します。
デフォルトでは無効化されているので、チェックを入れて有効化します。(エディタの再起動が必要です)
暗号化処理を有効化する
プラグインを有効化しただけでは実際の暗号化処理は動作しないため、いくつかのステップを経て暗号化処理を有効化してあげる必要があります。このステップには本来、暗号化キーを設定する過程でちょっとしたコツが必要なのですが、今回はお試しとしてお手軽な方法で試してみたいと思います。
まず、プロジェクト内の***Engine.iniファイルでEncryptionComponentを設定します。今回はDefaultEngine.iniに設定します。
[PacketHandlerComponents]
EncryptionComponent=AESGCMHandlerComponent
次に、暗号化キーを設定します。この暗号化キーには256ビットのキーを使用します。
暗号化キーの設定には、先述したようにちょっとしたコツが必要で、本来は以下のような実装が必要になります。
クライアントがシームレストラベルなどでトラベルする際に「?EncryptionToken=****」をURLに含める
UGameInstance::ReceivedNetworkEncryptionToken()をオーバーライドし、「?EncryptionToken=****」を受け取ったサーバーで256ビット長の暗号化キーを設定する(※暗号化キーの生成方法は、プロジェクトに応じて自由に行うことができます)
UGameInstance::ReceivedNetworkEncryptionAck()をオーバーライドし、サーバーが設定した暗号化キーをクライアントで取得する
上記が完了すると暗号化処理が動作するようになります。
この実装の詳細については、UE4の「Shooter Game サンプル プロジェクト」に実装サンプルが記述されており、公式ドキュメント「How to Enable Encryption Using Packet Handler Components」からも読み取ることができます。
しかしこの手法には少し注意が必要で、暗号化キーのやり取りのタイミングでパケロスなどが発生するとキーを上手く取得できなかったり、パケットの中身を読み取ってキー自体を予測されてしまったり、プログラム内に直接キーを埋め込む場合でもプログラムを解析した際に読み取れてしまったりするため、こういった点に留意して実装する必要があります。
予測が困難な手法で暗号化キーを生成してあげるとより安全になるため、例えば、サーバーから都度キーを受け取る形にしたり、外部サービスを用いるなどの手段でサーバーとクライアントの共通鍵を都度生成するなどの手法が考えられるかなと思います。
また、暗号化キーは各クライアント毎に別々のものを設定することも可能なので、各クライアント毎に生成してあげるのも良さそうですね。
今回はお試しですので、上記のような手法は行わず、EncryptionComponentを生成しているUNetConnectionの処理を直接書き換えてお手軽に試してみます。
UNetConnection::InitHandler()に以下のような処理を追加します。
void UNetConnection::InitHandler()
{
//...省略...
Handler->InitializeComponents();
//============================================== 以下の処理を追加 ==============================================
TSharedPtr EncryptionComponent = Handler->GetEncryptionComponent();
if (EncryptionComponent.IsValid())
{
FEncryptionData EncryptionData;
EncryptionData.Key.SetNum(32); // 32バイト(256ビット)分の配列を確保
for (int32 i = 0; i < EncryptionData.Key.Num(); ++i)
{
EncryptionData.Key[i] = i; // 今回の暗号化キーは単純な0123...とします
}
EncryptionComponent->SetEncryptionData(EncryptionData);
EncryptionComponent->EnableEncryption();
UE_LOG(LogNet, Log, TEXT("UNetConnection::InitHandler() Enable encryption."));
}
else
{
UE_LOG(LogNet, Warning, TEXT("UNetConnection::InitHandler() not found EncryptionComponent."));
}
//==============================================================================================================
MaxPacketHandlerBits = Handler->GetTotalReservedPacketBits();
//...省略...
}
これによってUNetConnectionの初期化時にサーバーとクライアントで共通の暗号化キーが設定され、暗号化処理が動作するようになります。
繰り返しになりますが、今回はお試しですのでこの無理やりな方法は正しくなく、また上記のようなSetEncryptionData()やEnableEncryption()を個別に呼び出す必要もありません。
以上で暗号化の準備が完了しました。エディタを起動し、ゲーム上で動作しているか確認してみましょう。
PIEで動作確認してみる
エディタを起動したら、プレイヤーの人数を2人以上にしてゲームを起動します。
暗号化処理が動作しているかを確認するには、ログを見るのがもっともお手軽な方法です。
デフォルトではログ出力が無効になっていますので、以下のコンソールコマンドを入力し有効にします。
log PacketHandlerLog VeryVerbose
ログ出力を有効にできたらアウトプットログを見てみましょう。
アウトプットログウィンドウを表示するには、メニューバーのWindow > Output Logから開くことができます。
すると、以下のようなログが流れていると思います。
PacketHandlerLog: VeryVerbose: AESGCM packet handler received 32 bytes before decryption.
PacketHandlerLog: VeryVerbose: Have 252 bits after decryption.
PacketHandlerLog: VeryVerbose: AESGCM packet handler sending 85 bits before encryption.
PacketHandlerLog: VeryVerbose: AESGCM packet handler sending 40 bytes after encryption.
これで無事に、暗号化処理が動作していることを確認できました。
上記のログでは、パケット送信時と受信時で暗号化前後のパケットサイズを出力しています。
パケット送信時は"AESGCM packet handler sending *** bits before encryption."と"AESGCM packet handler sending *** bytes after encryption."に、パケット受信時は"AESGCM packet handler received *** bytes before decryption."と"Have *** bits after decryption."に暗号化前後のパケットサイズが出力されています。
この数値を見てみると、暗号化後はパケットサイズが増えていることが分かります。
これは、暗号化アルゴリズムの動作原理として、パケットに追加の情報を付与して送信してあげる必要があることと、暗号化する際に一定のブロックサイズでアライメントされるためです。
「AES GCM network packet handler」を使用するメリットとデメリット
ここまで「AES GCM network packet handler」を動作させるところまで試してみましたので、このプラグインを使用する場合のメリット/デメリットを簡単にまとめてみます。
【メリット】
UE5の標準プラグインのため、気軽に利用できる
通信パケットを改竄するチートの対策として一定の効果が見込める
【デメリット】
暗号化後のパケットサイズが大きくなるため、通信量が増加してしまう
暗号化キーを設定するフローに工夫が必要(※デメリットというよりは、ひと手間かかる点)
最後に
さて、今回はUE5の標準プラグインである「AES GCM network packet handler」を使ってみました。
自前で実装するのはかなり大変、かつ専門的な知識が必要となる機能ですが、これを気軽に利用できるのは嬉しいですね。
UE5.4時点では「AES GCM network packet handler」が推奨されていますが、今後のエンジンアップデートによっては、現在試験機能となっている「DTLS network packet handler」が推奨となる可能性もあるかもしれません。
UE5には、これ以外にも便利な機能が数多く標準で装備されています。
効率的に開発すべく、積極的に活用していきましょう!
トイロジックでは現在、一緒に働くプログラマーを募集しています。
不明点などもお気軽にお問い合わせください。ご応募お待ちしております!