九尾空間

この世はとてもキュービックな空間ですね

【UE5】暗号論的に安全な疑似乱数を作りたい!(with OpenSSL)

はじめに

Unreal Engineでゲーム開発しているとき、ふと乱数を使いたくなるときってありますよね。 サイコロを振りたいとき、攻撃の成功/失敗判定をしたいとき、ドロップするアイテムを確率で決めたいとき……。挙げていくときりがありません。

大体のケースでは FMath::RandRange() だったり FRandomStream だったりの簡易的な疑似乱数生成器で済むことも多いですが、たまに「暗号論的に安全な」「十分なエントロピーを持つ」乱数を生成したくなることがあります。

例えば、データの暗号化に使う秘密鍵を生成したかったり、OAuth 2.0 PKCEで使う認可コード横取り防止の code_verifier を生成する時などです。 こういった時に必要となる乱数は、悪意のある第三者に値を推測されたり操作されては非常にマズいので、暗号論的に安全だと広く認められている方法で生成しなければなりません。

今回は、そんな「きちんとした乱数を使わないとセキュリティ的にマズい」時に使用できる乱数生成方法について解説します。

三行でまとめると

  • Unreal Engine 5にはOpenSSLがバンドルされているので、それを使いましょう。
  • #include "openssl/rand.h" して RAND_bytes(unsigned char *buf, int num) を呼び出すだけ。かんたん。
  • Build.cs に依存関係を追加するのを忘れないようにしましょう。

手順

今回は簡単に検証したかっただけなので、適当なActor (AMyActor) のBeginPlayで生成した乱数をログに書き出すコードを書きました。

1. Build.cs の依存関係に OpenSSL を追加。

今回はヘッダファイルで使わないので private dependency に追加します。

<Project Name>.Build.cs

using UnrealBuildTool;

public class MyProject : ModuleRules
{
    public MyProject(ReadOnlyTargetRules Target) : base(Target)
    {
        PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
    
        PublicDependencyModuleNames.AddRange(new string[]
        {
            "Core",
            "CoreUObject",
            "Engine",
            "InputCore",
            "EnhancedInput"
        });

        PrivateDependencyModuleNames.AddRange(new string[]
        {
            "OpenSSL", // ← 追加
        });
    }
}

2. 実装を作成

MyActor.h

UCLASS()
class MYPROJECT_API AMyActor : public AActor
{
    GENERATED_BODY()

    // ... 省略 ...

protected:
    virtual void BeginPlay() override;

private:
    /// <summary>
    /// InLengthで指定したバイト数の暗号学的に安全な乱数を生成します。
    /// 生成した乱数はBASE64でエンコードし、OutStringに格納して返します。
    /// </summary>
    /// <param name="InLength">生成する乱数のバイト数</param>
    /// <param name="OutString">生成した乱数列を格納する先のバッファ</param>
    /// <returns>生成に成功したらtrue, 失敗したらfalse</returns>
    static bool GenerateRandomBase64(uint32 InLength, FString& OutString);
};

MyActor.cpp

// OpenSSLをインクルード。
// 定義しておかないとエラーになるマクロがあるので定義しておく。
#define UI UI_ST
THIRD_PARTY_INCLUDES_START
#include "openssl/rand.h"
THIRD_PARTY_INCLUDES_END
#undef UI

void AMyActor::BeginPlay()
{
    Super::BeginPlay();

    FString RandomString;
    if (!GenerateRandomBase64(32, RandomString))
    {
        // 乱数生成失敗
        UE_LOG(LogTemp, Error, TEXT("Failed to generate random string"));
    }
    else
    {
        // 乱数生成に成功したのでログに出力
        UE_LOG(LogTemp, Display, TEXT("Random string is: %s"), *RandomString);
    }
}

bool AMyActor::GenerateRandomBase64(uint32 InLength, FString& OutString)
{
    // 乱数データを書き出すためのバッファを確保
    TArray<uint8> RandData;
    RandData.SetNumUninitialized(InLength);

    // 乱数生成。成功したら 1 が、失敗ならそれ以外の値(0 or -1)が返ってくる。
    if (RAND_bytes(RandData.GetData(), InLength) != 1)
    {
        // 失敗したのでfalse。
        return false;
    }

    // BASE64エンコード
    OutString = FBase64::Encode(RandData);
    return true;
}

3. 動作確認

AMyActorをレベル上に配置し、PIEのプレイボタンを押して実行。

ちゃんと生成されていそう

ここで1点だけ注意。私の環境だけかもしれないのですが、Unreal エディタ右下のコンパイルボタンからビルドしようとするとOpenSSLのリンク周りで沢山のエラーが出ました。 一度Unrealエディタを落として、Visual Studio側からリビルドをかけたら正常に動作するようになりましたので、もしうまく動かないようであればそちらを試していただければと。

「未解決の外部シンボル」エラーが沢山出ている

まとめ

以上の手順で、Unreal Engineで暗号論的に安全な方法で乱数を生成することができます。 外部のAPIを叩くなどの目的で安全な疑似乱数生成器をお探しだった方は、是非この方法を試してみてはいかがでしょうか。

また、ゲーム内でUnreal Engine標準の疑似乱数生成器の出力では満たせない、高品質で推測が困難な疑似乱数をお探しだった方にとっても、ここで紹介した手法が役に立つかもしれません。

とはいったものの、暗号学的に安全ということはそれなりに高コストな実装になっているはずですし、あまり濫用してしまうと疑似乱数生成器の出力から内部状態を類推する攻撃等を受けるリスクもあります。 あまり濫用しすぎるのは控えたほうがいいかもしれません……。RAND_bytes() は本来、Unreal Engine内部でSSL通信するため等の用途で使っているはずなので、攻撃されるとマズいです。

是非ともOpenSSL公式のドキュメントをご確認の上、用法用量を正しく守ってお使いください。

docs.openssl.org

それはともかく、OpenSSLは暗号通信用ライブラリなので、疑似乱数生成器以外にもいろいろ使えそうな関数が沢山ありそうです。 特に、APIで外とやりとりしようとする時には、HMACを生成できる関数などは非常に役に立つのではないでしょうか。

今回の記事で示したとおり、依存関係の設定さえしてしまえばUnreal EngineC++から普通にOpenSSLの関数が呼べます。 皆様も是非、使えそうなOpenSSLの関数を探して試しに使って見てはいかがでしょうか。

関連記事

先行研究その1。暗号学的に安全な疑似乱数の生成やHMACの計算はサーバー側で行っているようだ。

mizuame.works


先行研究その2。OpenSSLの関数をUnreal EngineC++から呼ぶ方法。

qiita.com


Unreal EngineC++からOpenSSLの関数を呼ぶ方法について、Unreal Engineの公式フォーラムのスレッド。

forums.unrealengine.com