【问题标题】:Generating a Signature for Subscription Offers - Xcode - Swift为订阅优惠生成签名 - Xcode - Swift
【发布时间】:2019-03-29 09:55:24
【问题描述】:

我想问是否有人已经为inapp-subscription(自动续订)实施了新的优惠,如果可能的话,创建服务器端系统以使用p8密钥和php创建此签名的困难。我在 Apple 文档中找到了这个,我不确定是否理解它: https://developer.apple.com/documentation/storekit/in-app_purchase/generating_a_signature_for_subscription_offers

【问题讨论】:

    标签: ios swift xcode in-app-purchase in-app-subscription


    【解决方案1】:

    这是来自 RevenueCat 的演练:iOS Subscription Offers

    帖子包含更多细节,但签名生成是:

    import json
    import uuid
    import time
    import hashlib
    import base64
    
    from ecdsa import SigningKey
    from ecdsa.util import sigencode_der
    
    bundle_id = 'com.myapp'
    key_id = 'XWSXTGQVX2'
    product = 'com.myapp.product.a'
    offer = 'REFERENCE_CODE' # This is the code set in ASC
    application_username = 'user_name' # Should be the same you use when
                                       # making purchases
    nonce = uuid.uuid4()
    timestamp = int(round(time.time() * 1000))
    
    payload = '\u2063'.join([bundle_id, 
                             key_id, 
                             product, 
                             offer, 
                             application_username, 
                             str(nonce), # Should be lower case
                             str(timestamp)])
    
    # Read the key file
    with open('cert.der', 'rb') as myfile:
      der = myfile.read()
    
    signing_key = SigningKey.from_der(der)
    
    signature = signing_key.sign(payload.encode('utf-8'), 
                                 hashfunc=hashlib.sha256, 
                                 sigencode=sigencode_der)
    encoded_signature = base64.b64encode(signature)
    
    print(str(encoded_signature, 'utf-8'), str(nonce), str(timestamp), key_id)
    

    这只是一个概念证明。您将希望在您的服务器上使用它,并且可能有一些逻辑来确定,对于给定的用户,请求的报价是否合适。

    生成签名、nonce 和时间戳后,将它们与 key_id 一起发送回您的应用,您可以在其中创建 SKPaymentDiscount

    免责声明:我在 RevenueCat 工作。我们的 SDK 支持开箱即用的订阅优惠,无需代码签名:https://www.revenuecat.com/2019/04/25/signing-ios-subscription-offers

    【讨论】:

    • 感谢举报这个例子,我已经关注了,可惜结果不是肯定的
    • 你知道一些 PHP 的例子吗?
    【解决方案2】:

    我可以确认这是有效的:

    <?php
    use Ramsey\Uuid\Uuid;
    
    class ItunesSignatureGenerator {
        private $appBundleID = 'your.bundle.id';
    
        private $keyIdentifier = 'ZZZZZZZ';
    
        private $itunesPrivateKeyPath = '/path/to/the/file.p8;
    
        /**
         * @see https://developer.apple.com/documentation/storekit/in-app_purchase/generating_a_signature_for_subscription_offers
         *
         * @param $productIdentifier
         * @param $offerIdentifier
         *
         * @return Signature
         */
        public function generateSubscriptionOfferSignature($productIdentifier, $offerIdentifier)
        {
            $nonce = strtolower(Uuid::uuid1()->toString());
            $timestamp = time() * 1000;
            $applicationUsername = 'username';
    
            $message = implode(
                "\u{2063}",
                [
                    $this->appBundleID,
                    $this->keyIdentifier,
                    $productIdentifier,
                    $offerIdentifier,
                    $applicationUsername,
                    $nonce,
                    $timestamp
                ]
            );
    
            $message = $this->sign($message);
    
            return new Signature(
                base64_encode($message),
                $nonce,
                $timestamp,
                $this->keyIdentifier
            );
        }
    
        private function sign($data)
        {
            $signature = '';
    
            openssl_sign(
                $data,
                $signature,
                openssl_get_privatekey('file://' . $this->itunesPrivateKeyPath),
                OPENSSL_ALGO_SHA256
            );
    
            return $signature;
        }
    }
    

    我们在客户端遇到了问题,因为该设备连接到 2 个不同的 iTunes 帐户,一个常规帐户和一个沙盒帐户。它正在创建一个没有意义的无效签名错误。我们断开常规帐户,只使用沙盒帐户,一切正常。

    【讨论】:

    • 这里缺少 Signature 类的定义
    • 签名类只是一个 DTO,里面没有逻辑。
    【解决方案3】:

    我曾经在订阅优惠方面遇到问题,但 GitHub 上的 this issue 帮助我解决了这个问题。我安装了Sop CryptoBridge 库(composer require sop/crypto-bridge),它终于适用于我的 iOS 应用程序客户端。这是我的工作 PHP 代码:

    use Sop\CryptoBridge\Crypto;
    use Sop\CryptoEncoding\PEM;
    use Sop\CryptoTypes\AlgorithmIdentifier\Hash\SHA256AlgorithmIdentifier;
    use Sop\CryptoTypes\AlgorithmIdentifier\Signature\SignatureAlgorithmIdentifierFactory;
    use Sop\CryptoTypes\Asymmetric\PrivateKeyInfo;
    
    // you can copy your p8 file contents here or just use file_get_contents()
    $privateKeyPem = <<<'PEM'
    -----BEGIN PRIVATE KEY-----
    ...
    -----END PRIVATE KEY-----
    PEM;
    
    // load private key
    $privateKeyInfo = PrivateKeyInfo::fromPEM(PEM::fromString($privateKeyPem));
    // you can also load p8 file like this
    // PrivateKeyInfo::fromPEM(PEM::fromFile($pathToP8));
    
    // combine the parameters
    $appBundleId = 'com.github.yaronius'; // iOS app bundle ID
    $keyIdentifier = 'A1B2C3D4E5'; // Key ID from AppStore Connect
    $productIdentifier = 'product_identifier';
    $offerIdentifier = 'offer_identifier';
    $applicationUsername = 'username'; // same as in the iOS app
    $nonce = 'db6ba7a6-9ec2-4504-bcb9-c0dfbdc3d051'; // some UUID in lowercase
    $timestamp = time() * 1000; // milliseconds! as stated in the docs
    
    $dataArray = [
        $appBundleId,
        $keyIdentifier,
        $productIdentifier,
        $offerIdentifier,
        $applicationUsername,
        $nonce,
        $timestamp
    ];
    $data = implode("\u{2063}", $dataArray);
    
    // signature algorithm
    $algo = SignatureAlgorithmIdentifierFactory::algoForAsymmetricCrypto(
        $privateKeyInfo->algorithmIdentifier(),
        new SHA256AlgorithmIdentifier()
    );
    // generate signature
    $signature = Crypto::getDefault()->sign($data, $privateKeyInfo, $algo);
    
    // encode as base64 encoded DER
    $encodedSignature = base64_encode($signature->toDER());
    // send signature to your app
    echo $encodedSignature;
    

    记住几点:

    • 作为分隔符,您必须使用PHP Unicode codepoint,即"\u{2063}"。使用 '\u2063' 对我不起作用。
    • $nonce 是小写 UUID
    • $timestamp 以毫秒为单位(即time() * 1000)。

    它应该像一个魅力。

    【讨论】:

    • 我正在尝试安装您报告的库,但我总是收到错误op/crypto-types 0.2.1 requires ext-gmp * -> 您的系统缺少请求的 PHP 扩展 gmp。
    • 是的,大多数加密库都需要 GMP(GNU 多精度)PHP 扩展。您可以从这里按照以下步骤安装:stackoverflow.com/questions/40010197/…
    【解决方案4】:

    我正在尝试在 php 中创建一些东西,但这似乎不切实际,也许在编码中发现了一些东西?

    <?php
    
    class Key_offer {
    
        var $appBundleId;
        var $keyIdentifier;
        var $productIdentifier;
        var $offerIdentifier;
        var $applicationUsername;
        var $nonce;
        var $timestamp;
    
        function __construct() {
            // Setting Data
            $this->appBundleId = 'bundle-app-id';
            $this->keyIdentifier = '0123456789';
            $this->productIdentifier = $_POST["productIdentifier"] ?? "";
            $this->offerIdentifier = $_POST["offerIdentifier"] ?? "";
            $this->applicationUsername = $_POST["usernameHash"] ?? "";  // usare lo stesso anche nella chiamata che si effettua da Xcode
            $this->nonce = strtolower( $this->gen_uuid() ); // genera UUID formato 4;
            $this->timestamp = time(); // get timeStump
        }
    
        function rsa_sign($policy, $private_key_filename) {
            $signature = "";
            // load the private key
            $fp = fopen($private_key_filename, "r");
            $priv_key = fread($fp, 8192);
            fclose($fp);
            $pkeyid = openssl_get_privatekey($priv_key);
            // compute signature
            openssl_sign($policy, $signature, $pkeyid);
            // free the key from memory
            openssl_free_key($pkeyid);
            return $signature;
         }
    
         function gen_uuid() {
            return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
                // 32 bits for "time_low"
                mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ),
                // 16 bits for "time_mid"
                mt_rand( 0, 0xffff ),
                // 16 bits for "time_hi_and_version",
                // four most significant bits holds version number 4
                mt_rand( 0, 0x0fff ) | 0x4000,
                // 16 bits, 8 bits for "clk_seq_hi_res",
                // 8 bits for "clk_seq_low",
                // two most significant bits holds zero and one for variant DCE1.1
                mt_rand( 0, 0x3fff ) | 0x8000,
                // 48 bits for "node"
                mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff ), mt_rand( 0, 0xffff )
            );
        }
    
        function get() {
            $text = utf8_encode($this->appBundleId.'\u2063'.$this->keyIdentifier.'\u2063'.$this->productIdentifier.'\u2063'.$this->offerIdentifier.'\u2063'.$this->applicationUsername.'\u2063'.$this->nonce.'\u2063'.$this->timestamp);
    
            $signature0 = $this->rsa_sign($text, "key.pem"); // SubscriptionKey_43PF4FTV2X.p8
            $signature = hash('sha256', $signature0);
            $array = array(
                'lowUUid' => $this->nonce,
                'timeStump' => $this->timestamp,
                'identifier' => $this->offerIdentifier,
                'keyid' => $this->keyIdentifier,
                'signature' => base64_encode($signature)
            );
    
            return json_encode($array);
        }
    
    
    }
    
    $obj = new Key_offer();
    echo $obj->get();
    
    ?>
    

    【讨论】:

    • 您最好不要重新发明轮子,并为 UUID 和加密操作使用适当的库。 UUID github.com/ramsey/uuid 有一个很棒的 PHP 库,通过 composer 安装为 composer require ramsey/uuid。此外,您需要 ECDSA 算法进行签名,最好使用 PHPECC 或任何其他库(我使用这个 packagist.org/packages/sop/crypto-bridge)。
    【解决方案5】:

    我在网上找到了这个例子,但不幸的是结果不是肯定的。 Tutorial Example

    【讨论】:

      猜你喜欢
      • 2017-09-24
      • 2022-01-19
      • 2021-02-18
      • 1970-01-01
      • 1970-01-01
      • 2015-08-23
      • 1970-01-01
      • 2021-01-21
      • 2016-05-03
      相关资源
      最近更新 更多