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

レシート検証

レシート検証を利用して、ハッカーが支払いなしにコンテンツにアクセスするのを防ぎます。

どこで検証するか

どこに検証ロジックを設定するかは、アプリケーションの仕様によります。しかし、一般的には、レシート検証はコンテンツが販売された時点で行われるべきです。

例えば、アプリケーションと一緒にまとめられたコンテンツを売る場合、そのアプリケーションにはすでに、プレイヤーがそれを購入できるかを決定するのに必要なすべてのロジックが備わっていなければなりません。

ただし、もしダウンロード可能なコンテンツやマルチプレイヤー機能など、サーバーから提供されるコンテンツを売る場合、検証を行う場所としてはサーバーが適切です。

ローカル検証

このシナリオでは、すでにデバイスに存在するコンテンツは販売されており、アプリケーションは、単に開錠するかどうかを決定するだけです。

Unity IAP は、Google Play と App Store 上で機密を保持するのを助け、レシートを検証し分析するツールを提供します。

難解化

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

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

Unity IAP はアプリケーション内の秘密鍵を見つかりにくくするツールを提供します。Obfuscator ウィンドウは、 Window > Unity IAP > IAP Receipt Validation Obfuscator でアクセス可能です。

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

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

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

レシートの検証

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

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

CrossPlatformValidator によって以下が確認されます。

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

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

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

public PurchaseProcessingResult ProcessPurchase (PurchaseEventArgs e)
{
    bool validPurchase = true; // Presume valid for platforms with no R.V.

    // Unity IAP's validation logic is only included on these platforms.
#if UNITY_ANDROID || UNITY_IOS || UNITY_STANDALONE_OSX
    // Prepare the validator with the secrets we prepared in the Editor
    // obfuscation window.
    var validator = new CrossPlatformValidator(GooglePlayTangle.Data(),
        AppleTangle.Data(), Application.bundleIdentifier);

    try {
        // On Google Play, result will have a single product Id.
        // On Apple stores receipts contain multiple products.
        var result = validator.Validate(e.purchasedProduct.receipt);
        // For informational purposes, we list the receipt(s)
        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) {
        // Unlock the appropriate content here.
    }

    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) {
        Debug.Log(google.purchaseState);
        Debug.Log(google.purchaseToken);
    }

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

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

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

#if UNITY_ANDROID || UNITY_IOS || UNITY_STANDALONE_OSX
var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
// Get a reference to IAppleConfiguration during IAP initialization.
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 receipt format をモデルにしています。欄に関する説明は、ASN1 receipt format の文書を参照してください。

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