Version: 2022.2
言語: 日本語
購入レシート
ストアの拡張

レシート検証

レシート検証によって、ユーザーが購入していないコンテンツにアクセスすることを防ぎます。

検証のタイミング

レシートはアプリケーションのコンテンツを配布する時点で検証するのが最も効率的です。

  • ローカル検証 すべてのコンテンツがアプリケーションに含まれ、一度購入したら使用可能になるクライアント側のコンテンツに関しては、検証はリモートサーバーに接続する必要なしに、ターゲットデバイス上で行うべきです。Unity IAP はアプリケーション内のローカル検証をサポートするよう設計されています。詳しくは後述の ローカル検証 を参照してください。
  • リモート検証 購入してからコンテンツをダウンロードするサーバー側のコンテンツに関しては、検証は、コンテンツがリリースされる前にサーバー上で行われるべきです。Unity はサーバー側の検証をサポートしていません。ただし、例えば Nobuyori Takahashi の IAP projectなどのサードパーティーソリューションは可能です。

ローカル検証

重要: Unity IAP はローカルな検証方法を提供していますが、ローカルな検証は不正に対してより脆弱です。可能であれば、機密性の高いトランザクションをサーバーサイドで検証することがベストプラクティスと考えられます。詳細については、Apple および Android の不正防止に関するドキュメントを参照してください。

ユーザーが購入するコンテンツがすでにデバイスに存在する場合は、アプリケーションはただ、それをアンロックするかどうかを決定するだけです。

Unity IAP はコンテンツを隠し、Google Play と App Store をとおしてレシートを検証し分析するツールを提供します。

難解化

レシート検証は、アプリケーションの Google Play 公開キーや Apple のルート証明書などの既知の暗号化キーを使用して実行されます。

攻撃者がこれらの機密情報を置き換えることができると、レシート検証は失敗してしまいます。そのため、攻撃者が簡単にこれらのキーを見つけ、改変できないようにすることが大切です。

Unity IAP はアプリケーション内の秘密鍵を見つかりにくくするツールを提供します。これにより鍵をわかりにくくするため、攻撃者のアクセスをより難しくします。Unity のメニューで Window > Unity IAP > IAP Receipt Validation Obfuscator を選んでアクセスできます。

Obfuscator ウィンドウ
Obfuscator ウィンドウ

このウィンドウでは、Unity IAP に束ねられた Apple の ルート証明書と、アプリケーションのGoogle Play Developer Console の Services & APIs ページから取得する Google Play 公開キーを両方とも、2つの異なる C# ファイル、 AppleTangleGooglePlayTangle にエンコードします。それらのC# ファイルは、プロジェクトに加えられ、次のセクションで使用されます。

注意: Apple ストアだけをターゲットにする場合は、Google Play 公開キーは必要ではありません。同様にGoogle Play だけをターゲットにする場合は、Apple のルート証明書は必要ではありません。

レシートの検証

CrossPlatformValidator クラスは、Google Play と Apple ストア両方の検証に使用することができます。

このクラスを使用するには、Google Play 公開キーか Apple の root 証明書が必要です。また、両方のプラットフォームを検証するなら両方が必要です。

CrossPlatformValidator は 2つの検証を行います。

  • 署名検証を通して、レシートの信頼性が確認されます。
  • レシートアプリケーション bundle identifier がアプリケーションのものと比較され、合致しない場合は、InvalidBundleId 例外が発生します。

バリデーターは Google Play と Apple プラットフォームで作成されたレシートのみを検証することに注意してください。エディターで作成された偽造を含め、その他のプラットフォームで作成されたものには、IAPSecurityException 例外が発生します。

必要な機密を提供していないプラットフォームのレシートを検証しようとすると、MissingStoreSecretException 例外が発生します。

public PurchaseProcessingResult ProcessPurchase (PurchaseEventArgs e)
{
    bool validPurchase = true; // R.V. のないプラットフォームに有効です

    // Unity IAP の検証ロジックはこれらのプラットフォームにのみ含まれます。
# if UNITY_ANDROID || UNITY_IOS || UNITY_STANDALONE_OSX
    // エディターの難読化ウィンドウで準備した機密を持つ
    // バリデーターを準備します。
    var validator = new CrossPlatformValidator(GooglePlayTangle.Data(),
        AppleTangle.Data(), Application.bundleIdentifier);

    try {
        // Google Play で、result は 1 つの product ID を取得します
        // Apple stores で、receipts には複数のプロダクトが含まれます
        var result = validator.Validate(e.purchasedProduct.receipt);
        // 情報提供の目的で、ここにレシートをリストします
        Debug.Log("Receipt is valid. Contents:");
        foreach (IPurchaseReceipt productReceipt in result) {
            Debug.Log(productReceipt.productID);
            Debug.Log(productReceipt.purchaseDate);
            Debug.Log(productReceipt.transactionID);
        }
    } catch (IAPSecurityException) {
        Debug.Log("Invalid receipt, not unlocking content");
        validPurchase = false;
    }
# endif

    if (validPurchase) {
        // 適当なコンテンツをここにアンロックします
    }

    return PurchaseProcessingResult.Complete;
}

レシートの有効性だけをチェックするのではなく、それに含まれる情報をチェックすることも大切です。ハッカーが実際に購入せずにコンテンツにアクセスする一般的な攻撃方法は、他のプロダクトやアプリケーションのレシートを提供することです。それらのレシートは純正なので、検証に引っかかりません。そのため、CrossPlatformValidator で分析したプロダクト ID に基づいて判断する必要があります。

ストア別詳細

それぞれのストアの購入レシートには、異なる欄があります。そのストア特有の欄にアクセスするには、 IPurchaseReceipt を 2つの異なるサブタイプ GooglePlayReceiptAppleInAppPurchaseReceipt にダウンキャストすることができます。

var result = validator.Validate(e.purchasedProduct.receipt);
Debug.Log("Receipt is valid. Contents:");
foreach (IPurchaseReceipt productReceipt in result) {
    Debug.Log(productReceipt.productID);
    Debug.Log(productReceipt.purchaseDate);
    Debug.Log(productReceipt.transactionID);

    GooglePlayReceipt google = productReceipt as GooglePlayReceipt;
    if (null != google) {
        // ここに Google のオーダー ID
        //  sandbox でテストする場合は null にするように注意
        // なぜなら、Google の sandbox はオーダー IDを発行しないため
        Debug.Log(google.transactionID);
        Debug.Log(google.purchaseState);
        Debug.Log(google.purchaseToken);
    }

    AppleInAppPurchaseReceipt apple = productReceipt as AppleInAppPurchaseReceipt;
    if (null != apple) {
        Debug.Log(apple.originalTransactionIdentifier);
        Debug.Log(apple.subscriptionExpirationDate);
        Debug.Log(apple.cancellationDate);
        Debug.Log(apple.quantity);
    }
}

Apple レシートの Raw データの解析

AppleValidator クラスは、Apple レシートの詳細な情報を取り出すのに使用されます。このクラスは、Apple の Deprecated トランザクション レシートではなく、iOS 7 以降のバージョンの App Store レシートにのみ正しく機能することに気を付けてください。

# if UNITY_ANDROID || UNITY_IOS || UNITY_STANDALONE_OSX
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
//  IAP 初期化の間に、IAppleConfiguration への参照を取得
var appleConfig = builder.Configure<IAppleConfiguration>();
var receiptData = System.Convert.FromBase64String(appleConfig.appReceipt);
AppleReceipt receipt = new AppleValidator(AppleTangle.Data()).Validate(receiptData);

Debug.Log(receipt.bundleID);
Debug.Log(receipt.receiptCreationDate);
foreach (AppleInAppPurchaseReceipt productReceipt in receipt.inAppPurchaseReceipts) {
    Debug.Log(productReceipt.transactionIdentifier);
    Debug.Log(productReceipt.productIdentifier);
}
# endif

AppleReceipt タイプは Apple の ASN1 レシート形式をモデルにしています。詳細は Apple のドキュメンテーション を参照してください。

購入レシート
ストアの拡張