Solusi lengkap untuk LOCALLY memvalidasi penerimaan dalam aplikasi dan bundel penerimaan di iOS 7

160

Saya telah membaca banyak dokumen dan kode yang secara teori akan memvalidasi tanda terima dalam aplikasi dan / atau bundel.

Mengingat bahwa pengetahuan saya tentang SSL, sertifikat, enkripsi, dll., Hampir nol, semua penjelasan yang saya baca, seperti yang menjanjikan ini , saya merasa sulit untuk dipahami.

Mereka mengatakan penjelasannya tidak lengkap karena setiap orang harus mencari tahu bagaimana melakukannya, atau para peretas akan memiliki pekerjaan yang mudah untuk membuat aplikasi cracker yang dapat mengenali dan mengidentifikasi pola dan menambal aplikasi. OK, saya setuju dengan ini sampai titik tertentu. Saya pikir mereka dapat menjelaskan sepenuhnya bagaimana melakukannya dan memberikan peringatan yang mengatakan "modifikasi metode ini", "modifikasi metode lain ini", "hapus variabel ini", "ubah nama ini dan itu", dll.

Bisakah orang baik di luar sana berbaik hati untuk menjelaskan cara memvalidasi secara LOKAL, kwitansi bundel, dan kwitansi pembelian dalam aplikasi di iOS 7 karena saya berusia lima tahun (oke, jadikan 3), dari atas ke bawah, jelas?

Terima kasih!!!


Jika Anda memiliki versi yang berfungsi pada aplikasi Anda dan kekhawatiran Anda adalah bahwa peretas akan melihat bagaimana Anda melakukannya, cukup ubah metode sensitif Anda sebelum menerbitkan di sini. Mengaburkan string, mengubah urutan garis, mengubah cara Anda melakukan loop (dari menggunakan untuk memblokir enumerasi dan sebaliknya) dan hal-hal seperti itu. Jelas, setiap orang yang menggunakan kode yang dapat diposting di sini, harus melakukan hal yang sama, tidak mengambil risiko diretas dengan mudah.

bebek
sumber
1
Peringatan yang adil: melakukannya secara lokal membuatnya jauh lebih mudah untuk menambal fungsi ini dari aplikasi Anda.
NinjaLikesCheez
2
OK, saya tahu, tapi intinya di sini adalah melakukan hal-hal yang sulit dan mencegah cracking / patching otomatis. Pertanyaannya adalah jika seorang peretas benar-benar ingin memecahkan aplikasi Anda ia akan melakukannya, metode apa pun yang Anda gunakan, lokal atau jarak jauh. Idenya adalah untuk mengubahnya sedikit setiap versi baru yang Anda rilis, untuk mencegah penambalan otomatis lagi.
Bebek
4
@NinjaLikesCheez - seseorang dapat NOP cek bahkan jika verifikasi dilakukan pada server.
Bebek
14
maaf, tapi ini bukan alasan. Satu-satunya hal yang penulis harus lakukan adalah untuk mengatakan JANGAN GUNAKAN KODE SAJA. Tanpa contoh apa pun, tidak mungkin untuk memahami ini tanpa menjadi ilmuwan roket.
Bebek
3
Jika Anda tidak ingin repot menerapkan DRM, jangan repot-repot dengan verifikasi lokal. Cukup POSTKAN tanda terima langsung ke Apple dari aplikasi Anda, dan mereka akan mengirimkannya kembali kepada Anda dalam format JSON yang mudah diurai. Sepele bagi perompak untuk memecahkan ini, tetapi jika Anda hanya beralih ke freemium dan tidak peduli tentang pembajakan, itu hanya beberapa baris kode yang sangat mudah.
Dan Fabulich

Jawaban:

146

Berikut ini adalah langkah-langkah bagaimana saya memecahkan ini di perpustakaan pembelian dalam aplikasi RMStore saya . Saya akan menjelaskan cara memverifikasi transaksi, yang mencakup memverifikasi seluruh tanda terima.

Sekilas

Dapatkan tanda terima dan verifikasi transaksi. Jika gagal, segarkan tanda terima dan coba lagi. Ini membuat proses verifikasi tidak sinkron seperti menyegarkan tanda terima tidak sinkron.

Dari RMStoreAppReceiptVerifier :

RMAppReceipt *receipt = [RMAppReceipt bundleReceipt];
const BOOL verified = [self verifyTransaction:transaction inReceipt:receipt success:successBlock failure:nil]; // failureBlock is nil intentionally. See below.
if (verified) return;

// Apple recommends to refresh the receipt if validation fails on iOS
[[RMStore defaultStore] refreshReceiptOnSuccess:^{
    RMAppReceipt *receipt = [RMAppReceipt bundleReceipt];
    [self verifyTransaction:transaction inReceipt:receipt success:successBlock failure:failureBlock];
} failure:^(NSError *error) {
    [self failWithBlock:failureBlock error:error];
}];

Mendapatkan data tanda terima

Tanda terima ada di dalam [[NSBundle mainBundle] appStoreReceiptURL]dan sebenarnya adalah wadah PCKS7. Saya payah pada kriptografi jadi saya menggunakan OpenSSL untuk membuka wadah ini. Yang lain rupanya telah melakukannya murni dengan kerangka kerja sistem .

Menambahkan OpenSSL ke proyek Anda bukanlah hal sepele. The RMStore wiki harus membantu.

Jika Anda memilih untuk menggunakan OpenSSL untuk membuka wadah PKCS7, kode Anda bisa seperti ini. Dari RMAppReceipt :

+ (NSData*)dataFromPKCS7Path:(NSString*)path
{
    const char *cpath = [[path stringByStandardizingPath] fileSystemRepresentation];
    FILE *fp = fopen(cpath, "rb");
    if (!fp) return nil;

    PKCS7 *p7 = d2i_PKCS7_fp(fp, NULL);
    fclose(fp);

    if (!p7) return nil;

    NSData *data;
    NSURL *certificateURL = [[NSBundle mainBundle] URLForResource:@"AppleIncRootCertificate" withExtension:@"cer"];
    NSData *certificateData = [NSData dataWithContentsOfURL:certificateURL];
    if ([self verifyPKCS7:p7 withCertificateData:certificateData])
    {
        struct pkcs7_st *contents = p7->d.sign->contents;
        if (PKCS7_type_is_data(contents))
        {
            ASN1_OCTET_STRING *octets = contents->d.data;
            data = [NSData dataWithBytes:octets->data length:octets->length];
        }
    }
    PKCS7_free(p7);
    return data;
}

Kami akan masuk ke detail verifikasi nanti.

Mendapatkan bidang tanda terima

Tanda terima dinyatakan dalam format ASN1. Ini berisi informasi umum, beberapa bidang untuk keperluan verifikasi (kami akan membahasnya nanti) dan informasi spesifik dari setiap pembelian dalam aplikasi yang berlaku.

Sekali lagi, OpenSSL datang untuk menyelamatkan ketika datang untuk membaca ASN1. Dari RMAppReceipt , menggunakan beberapa metode pembantu:

NSMutableArray *purchases = [NSMutableArray array];
[RMAppReceipt enumerateASN1Attributes:asn1Data.bytes length:asn1Data.length usingBlock:^(NSData *data, int type) {
    const uint8_t *s = data.bytes;
    const NSUInteger length = data.length;
    switch (type)
    {
        case RMAppReceiptASN1TypeBundleIdentifier:
            _bundleIdentifierData = data;
            _bundleIdentifier = RMASN1ReadUTF8String(&s, length);
            break;
        case RMAppReceiptASN1TypeAppVersion:
            _appVersion = RMASN1ReadUTF8String(&s, length);
            break;
        case RMAppReceiptASN1TypeOpaqueValue:
            _opaqueValue = data;
            break;
        case RMAppReceiptASN1TypeHash:
            _hash = data;
            break;
        case RMAppReceiptASN1TypeInAppPurchaseReceipt:
        {
            RMAppReceiptIAP *purchase = [[RMAppReceiptIAP alloc] initWithASN1Data:data];
            [purchases addObject:purchase];
            break;
        }
        case RMAppReceiptASN1TypeOriginalAppVersion:
            _originalAppVersion = RMASN1ReadUTF8String(&s, length);
            break;
        case RMAppReceiptASN1TypeExpirationDate:
        {
            NSString *string = RMASN1ReadIA5SString(&s, length);
            _expirationDate = [RMAppReceipt formatRFC3339String:string];
            break;
        }
    }
}];
_inAppPurchases = purchases;

Mendapatkan pembelian dalam aplikasi

Setiap pembelian dalam aplikasi juga dalam ASN1. Mem-parsingnya sangat mirip dengan menguraikan informasi penerimaan umum.

Dari RMAppReceipt , menggunakan metode pembantu yang sama:

[RMAppReceipt enumerateASN1Attributes:asn1Data.bytes length:asn1Data.length usingBlock:^(NSData *data, int type) {
    const uint8_t *p = data.bytes;
    const NSUInteger length = data.length;
    switch (type)
    {
        case RMAppReceiptASN1TypeQuantity:
            _quantity = RMASN1ReadInteger(&p, length);
            break;
        case RMAppReceiptASN1TypeProductIdentifier:
            _productIdentifier = RMASN1ReadUTF8String(&p, length);
            break;
        case RMAppReceiptASN1TypeTransactionIdentifier:
            _transactionIdentifier = RMASN1ReadUTF8String(&p, length);
            break;
        case RMAppReceiptASN1TypePurchaseDate:
        {
            NSString *string = RMASN1ReadIA5SString(&p, length);
            _purchaseDate = [RMAppReceipt formatRFC3339String:string];
            break;
        }
        case RMAppReceiptASN1TypeOriginalTransactionIdentifier:
            _originalTransactionIdentifier = RMASN1ReadUTF8String(&p, length);
            break;
        case RMAppReceiptASN1TypeOriginalPurchaseDate:
        {
            NSString *string = RMASN1ReadIA5SString(&p, length);
            _originalPurchaseDate = [RMAppReceipt formatRFC3339String:string];
            break;
        }
        case RMAppReceiptASN1TypeSubscriptionExpirationDate:
        {
            NSString *string = RMASN1ReadIA5SString(&p, length);
            _subscriptionExpirationDate = [RMAppReceipt formatRFC3339String:string];
            break;
        }
        case RMAppReceiptASN1TypeWebOrderLineItemID:
            _webOrderLineItemID = RMASN1ReadInteger(&p, length);
            break;
        case RMAppReceiptASN1TypeCancellationDate:
        {
            NSString *string = RMASN1ReadIA5SString(&p, length);
            _cancellationDate = [RMAppReceipt formatRFC3339String:string];
            break;
        }
    }
}]; 

Perlu dicatat bahwa pembelian dalam aplikasi tertentu, seperti barang habis pakai dan langganan tidak terbarukan, akan muncul hanya sekali dalam tanda terima. Anda harus memverifikasi ini tepat setelah pembelian (sekali lagi, RMStore membantu Anda dengan ini).

Sekilas tentang verifikasi

Sekarang kami mendapatkan semua bidang dari tanda terima dan semua pembelian dalam aplikasi. Pertama kita memverifikasi tanda terima itu sendiri, dan kemudian kita hanya memeriksa apakah tanda terima itu mengandung produk transaksi.

Di bawah ini adalah metode yang kami panggil kembali di awal. Dari RMStoreAppReceiptVerificator :

- (BOOL)verifyTransaction:(SKPaymentTransaction*)transaction
                inReceipt:(RMAppReceipt*)receipt
                           success:(void (^)())successBlock
                           failure:(void (^)(NSError *error))failureBlock
{
    const BOOL receiptVerified = [self verifyAppReceipt:receipt];
    if (!receiptVerified)
    {
        [self failWithBlock:failureBlock message:NSLocalizedString(@"The app receipt failed verification", @"")];
        return NO;
    }
    SKPayment *payment = transaction.payment;
    const BOOL transactionVerified = [receipt containsInAppPurchaseOfProductIdentifier:payment.productIdentifier];
    if (!transactionVerified)
    {
        [self failWithBlock:failureBlock message:NSLocalizedString(@"The app receipt doest not contain the given product", @"")];
        return NO;
    }
    if (successBlock)
    {
        successBlock();
    }
    return YES;
}

Memverifikasi tanda terima

Memverifikasi tanda terima itu sendiri bermuara ke:

  1. Memeriksa bahwa tanda terima valid PKCS7 dan ASN1. Kami telah melakukan ini secara implisit.
  2. Memverifikasi bahwa tanda terima ditandatangani oleh Apple. Ini dilakukan sebelum menguraikan tanda terima dan akan dirinci di bawah.
  3. Memeriksa apakah pengidentifikasi bundel yang disertakan dalam tanda terima sesuai dengan pengidentifikasi bundel Anda. Anda harus membuat hardcode pengenal bundel Anda, karena sepertinya tidak terlalu sulit untuk memodifikasi bundel aplikasi Anda dan menggunakan beberapa tanda terima lainnya.
  4. Memeriksa bahwa versi aplikasi yang termasuk dalam tanda terima sesuai dengan pengenal versi aplikasi Anda. Anda harus membuat hardcode versi aplikasi, untuk alasan yang sama yang ditunjukkan di atas.
  5. Periksa hash tanda terima untuk memastikan tanda terima sesuai dengan perangkat saat ini.

5 langkah dalam kode di tingkat tinggi, dari RMStoreAppReceiptVerificator :

- (BOOL)verifyAppReceipt:(RMAppReceipt*)receipt
{
    // Steps 1 & 2 were done while parsing the receipt
    if (!receipt) return NO;   

    // Step 3
    if (![receipt.bundleIdentifier isEqualToString:self.bundleIdentifier]) return NO;

    // Step 4        
    if (![receipt.appVersion isEqualToString:self.bundleVersion]) return NO;

    // Step 5        
    if (![receipt verifyReceiptHash]) return NO;

    return YES;
}

Mari kita telusuri langkah 2 dan 5.

Memverifikasi tanda tangan tanda terima

Kembali ketika kami mengekstraksi data, kami melirik verifikasi tanda tangan tanda terima. Tanda terima ditandatangani dengan Apple Inc. Root Certificate, yang dapat diunduh dari Apple Root Certificate Authority . Kode berikut mengambil wadah PKCS7 dan sertifikat root sebagai data dan memeriksa jika cocok:

+ (BOOL)verifyPKCS7:(PKCS7*)container withCertificateData:(NSData*)certificateData
{ // Based on: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW17
    static int verified = 1;
    int result = 0;
    OpenSSL_add_all_digests(); // Required for PKCS7_verify to work
    X509_STORE *store = X509_STORE_new();
    if (store)
    {
        const uint8_t *certificateBytes = (uint8_t *)(certificateData.bytes);
        X509 *certificate = d2i_X509(NULL, &certificateBytes, (long)certificateData.length);
        if (certificate)
        {
            X509_STORE_add_cert(store, certificate);

            BIO *payload = BIO_new(BIO_s_mem());
            result = PKCS7_verify(container, NULL, store, NULL, payload, 0);
            BIO_free(payload);

            X509_free(certificate);
        }
    }
    X509_STORE_free(store);
    EVP_cleanup(); // Balances OpenSSL_add_all_digests (), per http://www.openssl.org/docs/crypto/OpenSSL_add_all_algorithms.html

    return result == verified;
}

Ini dilakukan kembali di awal, sebelum tanda terima diuraikan.

Memverifikasi hash tanda terima

Hash yang termasuk dalam tanda terima adalah SHA1 dari id perangkat, beberapa nilai buram yang termasuk dalam tanda terima dan bundel id.

Ini adalah bagaimana Anda akan memverifikasi tanda terima pada iOS. Dari RMAppReceipt :

- (BOOL)verifyReceiptHash
{
    // TODO: Getting the uuid in Mac is different. See: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW5
    NSUUID *uuid = [[UIDevice currentDevice] identifierForVendor];
    unsigned char uuidBytes[16];
    [uuid getUUIDBytes:uuidBytes];

    // Order taken from: https://developer.apple.com/library/content/releasenotes/General/ValidateAppStoreReceipt/Chapters/ValidateLocally.html#//apple_ref/doc/uid/TP40010573-CH1-SW5
    NSMutableData *data = [NSMutableData data];
    [data appendBytes:uuidBytes length:sizeof(uuidBytes)];
    [data appendData:self.opaqueValue];
    [data appendData:self.bundleIdentifierData];

    NSMutableData *expectedHash = [NSMutableData dataWithLength:SHA_DIGEST_LENGTH];
    SHA1(data.bytes, data.length, expectedHash.mutableBytes);

    return [expectedHash isEqualToData:self.hash];
}

Dan itulah intinya. Saya mungkin kehilangan sesuatu di sini atau di sana, jadi saya mungkin kembali ke posting ini nanti. Bagaimanapun, saya sarankan menelusuri kode lengkap untuk lebih jelasnya.

hpique
sumber
2
Penafian keamanan: menggunakan kode sumber terbuka membuat aplikasi Anda lebih rentan. Jika keamanan menjadi perhatian, Anda mungkin ingin menggunakan RMStore dan kode di atas hanya sebagai panduan.
hpique
6
Akan luar biasa jika di masa depan Anda menyingkirkan OpenSSL dan membuat perpustakaan Anda kompak dengan hanya menggunakan kerangka kerja sistem.
Bebek
2
@RubberDuck Lihat github.com/robotmedia/RMStore/issues/16 . Jangan ragu untuk berpadu, atau berkontribusi. :)
hpique
1
@RubberDuck Saya tidak memiliki pengetahuan tentang OpenSSL sampai saat ini. Siapa tahu, Anda bahkan mungkin menyukainya. : P
hpique
2
Ini rentan terhadap Man In The Middle Attack, di mana permintaan dan / atau respons dapat dicegat dan dimodifikasi. Misalnya, permintaan dapat dialihkan ke server pihak ke-3, dan respons yang salah dapat dikembalikan, mengelabui aplikasi agar berpikir bahwa suatu produk dibeli, padahal sebenarnya tidak, dan mengaktifkan fungsi secara gratis.
Jasarien
13

Saya terkejut tidak ada yang menyebut Receigen di sini. Ini adalah alat yang secara otomatis menghasilkan kode validasi penerimaan yang dikaburkan, berbeda setiap kali; mendukung operasi GUI dan command-line. Sangat dianjurkan.

(Tidak berafiliasi dengan Receigen, hanya pengguna yang bahagia.)

Saya menggunakan Rakefile seperti ini untuk secara otomatis menjalankan kembali Receigen (karena itu perlu dilakukan pada setiap perubahan versi) ketika saya mengetik rake receigen:

desc "Regenerate App Store Receipt validation code using Receigen app (which must be already installed)"
task :receigen do
  # TODO: modify these to match your app
  bundle_id = 'com.example.YourBundleIdentifierHere'
  output_file = File.join(__dir__, 'SomeDir/ReceiptValidation.h')

  version = PList.get(File.join(__dir__, 'YourProjectFolder/Info.plist'), 'CFBundleVersion')
  command = %Q</Applications/Receigen.app/Contents/MacOS/Receigen --identifier #{bundle_id} --version #{version} --os ios --prefix ReceiptValidation --success callblock --failure callblock>
  puts "#{command} > #{output_file}"
  data = `#{command}`
  File.open(output_file, 'w') { |f| f.write(data) }
end

module PList
  def self.get file_name, key
    if File.read(file_name) =~ %r!<key>#{Regexp.escape(key)}</key>\s*<string>(.*?)</string>!
      $1.strip
    else
      nil
    end
  end
end
Andrey Tarantsov
sumber
1
Bagi mereka yang tertarik dengan Receigen, ini solusi berbayar, yang tersedia di App Store seharga $ 29,99. Meskipun belum diperbarui sejak September 2014.
DevGansta
Benar, kurangnya pembaruan sangat mengkhawatirkan. Namun tetap bekerja; FWIW, saya menggunakannya di aplikasi saya.
Andrey Tarantsov
Periksa aplikasi Anda di instrumen untuk kebocoran, dengan Receigen saya mendapatkannya banyak.
Pendeta
Receigen adalah ujung tombak, tapi ya sayang itu sepertinya telah dijatuhkan.
Fattie
1
Sepertinya belum dijatuhkan. Diperbarui tiga minggu lalu!
Oleg Korzhukov
2

Catatan: Tidak disarankan untuk melakukan verifikasi jenis ini di sisi klien

Ini adalah versi Swift 4 untuk validasi tanda terima dalam aplikasi ...

Mari kita membuat enum untuk mewakili kemungkinan kesalahan validasi tanda terima

enum ReceiptValidationError: Error {
    case receiptNotFound
    case jsonResponseIsNotValid(description: String)
    case notBought
    case expired
}

Kemudian, mari kita buat fungsi yang memvalidasi tanda terima, itu akan menimbulkan kesalahan jika tidak dapat memvalidasinya.

func validateReceipt() throws {
    guard let appStoreReceiptURL = Bundle.main.appStoreReceiptURL, FileManager.default.fileExists(atPath: appStoreReceiptURL.path) else {
        throw ReceiptValidationError.receiptNotFound
    }

    let receiptData = try! Data(contentsOf: appStoreReceiptURL, options: .alwaysMapped)
    let receiptString = receiptData.base64EncodedString()
    let jsonObjectBody = ["receipt-data" : receiptString, "password" : <#String#>]

    #if DEBUG
    let url = URL(string: "https://sandbox.itunes.apple.com/verifyReceipt")!
    #else
    let url = URL(string: "https://buy.itunes.apple.com/verifyReceipt")!
    #endif

    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.httpBody = try! JSONSerialization.data(withJSONObject: jsonObjectBody, options: .prettyPrinted)

    let semaphore = DispatchSemaphore(value: 0)

    var validationError : ReceiptValidationError?

    let task = URLSession.shared.dataTask(with: request) { data, response, error in
        guard let data = data, let httpResponse = response as? HTTPURLResponse, error == nil, httpResponse.statusCode == 200 else {
            validationError = ReceiptValidationError.jsonResponseIsNotValid(description: error?.localizedDescription ?? "")
            semaphore.signal()
            return
        }
        guard let jsonResponse = (try? JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions.mutableContainers)) as? [AnyHashable: Any] else {
            validationError = ReceiptValidationError.jsonResponseIsNotValid(description: "Unable to parse json")
            semaphore.signal()
            return
        }
        guard let expirationDate = self.expirationDate(jsonResponse: jsonResponse, forProductId: <#String#>) else {
            validationError = ReceiptValidationError.notBought
            semaphore.signal()
            return
        }

        let currentDate = Date()
        if currentDate > expirationDate {
            validationError = ReceiptValidationError.expired
        }

        semaphore.signal()
    }
    task.resume()

    semaphore.wait()

    if let validationError = validationError {
        throw validationError
    }
}

Mari kita gunakan fungsi pembantu ini, untuk mendapatkan tanggal kedaluwarsa dari produk tertentu. Fungsi menerima respons JSON dan id produk. Respons JSON dapat berisi beberapa info kwitansi untuk produk yang berbeda, sehingga mendapatkan info terakhir untuk parameter yang ditentukan.

func expirationDate(jsonResponse: [AnyHashable: Any], forProductId productId :String) -> Date? {
    guard let receiptInfo = (jsonResponse["latest_receipt_info"] as? [[AnyHashable: Any]]) else {
        return nil
    }

    let filteredReceipts = receiptInfo.filter{ return ($0["product_id"] as? String) == productId }

    guard let lastReceipt = filteredReceipts.last else {
        return nil
    }

    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd HH:mm:ss VV"

    if let expiresString = lastReceipt["expires_date"] as? String {
        return formatter.date(from: expiresString)
    }

    return nil
}

Sekarang Anda dapat memanggil fungsi ini dan menangani kasus kesalahan yang mungkin terjadi

do {
    try validateReceipt()
    // The receipt is valid 😌
    print("Receipt is valid")
} catch ReceiptValidationError.receiptNotFound {
    // There is no receipt on the device 😱
} catch ReceiptValidationError.jsonResponseIsNotValid(let description) {
    // unable to parse the json 🤯
    print(description)
} catch ReceiptValidationError.notBought {
    // the subscription hasn't being purchased 😒
} catch ReceiptValidationError.expired {
    // the subscription is expired 😵
} catch {
    print("Unexpected error: \(error).")
}

Anda bisa mendapatkan kata sandi dari App Store Connect. https://developer.apple.combuka tautan ini, klik

  • Account tab
  • Do Sign in
  • Open iTune Connect
  • Open My App
  • Open Feature Tab
  • Open In App Purchase
  • Click at the right side on 'View Shared Secret'
  • At the bottom you will get a secrete key

Salin kunci itu dan tempel ke bidang kata sandi.

Semoga ini bisa membantu setiap orang yang menginginkannya dalam versi cepat.

Pushpendra
sumber
19
Anda tidak boleh menggunakan URL validasi Apple dari perangkat Anda. Seharusnya hanya digunakan dari server Anda. Ini disebutkan dalam sesi WWDC.
pechar
Apa yang akan terjadi jika pengguna menghapus aplikasi atau tidak membuka dalam waktu lama? Apakah perhitungan tanggal kedaluwarsa Anda berfungsi dengan baik?
karthikeyan
Maka Anda perlu menjaga validasi di sisi server.
Pushpendra
1
Seperti @pechar katakan, Anda seharusnya tidak pernah melakukan ini. Harap tambahkan ke bagian atas jawaban Anda. Lihat sesi WWDC di 36:32 => developer.apple.com/videos/play/wwdc2016/702
cicerocamargo
Saya tidak mengerti mengapa tidak aman mengirim data tanda terima langsung dari perangkat. Adakah yang bisa menjelaskan?
Koh