gpt4 book ai didi

ios - 在 iOS 7 上本地验证应用内收据和捆绑收据的完整解决方案

转载 作者:IT王子 更新时间:2023-10-29 07:26:44 28 4
gpt4 key购买 nike

我已经阅读了很多理论上可以验证应用内和/或捆绑收据的文档和代码。

鉴于我对 SSL、证书、加密等方面的知识几乎为零,我阅读的所有解释,like this promising one ,我发现很难理解。

他们说解释是不完整的,因为每个人都必须弄清楚如何去做,否则黑客将很容易创建一个可以识别和识别模式并修补应用程序的破解应用程序。好的,我在一定程度上同意这一点。我认为他们可以完全解释如何做到这一点,并发出警告说“修改这个方法”、“修改这个其他方法”、“混淆这个变量”、“更改这个和那个的名称”等。

有好心人能解释一下吗如何在 iOS 7 上本地验证、捆绑收据和应用内购买收据 因为我五岁(好吧,让它3岁),从上到下,清楚吗?

谢谢!!!

如果您的应用程序有一个版本,并且您担心黑客会看到您是如何做到的,只需在此处发布之前更改您的敏感方法。混淆字符串,更改行的顺序,更改循环的方式(从使用 for 到阻止枚举,反之亦然)等等。显然,每个使用可能在此处发布的代码的人都必须做同样的事情,而不是冒被轻易入侵的风险。

最佳答案

这是我如何在我的应用内购买库中解决此问题的演练 RMStore .我将解释如何验证交易,包括验证整个收据。

乍看上去

获取收据并验证交易。如果失败,请刷新收据并重试。这使得验证过程是异步的,因为刷新收据是异步的。

来自 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];
}];

获取收据数据

收据在 [[NSBundle mainBundle] appStoreReceiptURL]并且实际上是一个 PCKS7 容器。我不擅长密码学,所以我使用 OpenSSL 打开这个容器。其他人显然是用 system frameworks 完成的。 .

将 OpenSSL 添加到您的项目中并非易事。 RMStore wiki应该有帮助。

如果您选择使用 OpenSSL 打开 PKCS7 容器,您的代码可能如下所示。来自 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;
}

稍后我们将进入验证的详细信息。

获取收据字段

收据以 ASN1 格式表示。它包含一般信息、一些用于验证目的的字段(我们稍后会谈到)以及每个适用的应用内购买的具体信息。

在读取 ASN1 时,OpenSSL 再次提供了帮助。来自 RMAppReceipt ,使用一些辅助方法:
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;

获取应用内购买

每个应用内购买也在 ASN1 中。解析它与解析一般收据信息非常相似。

来自 RMAppReceipt ,使用相同的辅助方法:
[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;
}
}
}];

需要注意的是,某些应用内购买,例如消耗品和不可更新订阅,只会在收据中出现一次。您应该在购买后立即验证这些(同样,RMStore 可以帮助您)。

验证一目了然

现在我们从收据及其所有应用内购买中获得了所有字段。首先我们验证收据本身,然后我们简单地检查收据是否包含交易的产品。

下面是我们一开始回调的方法。来自 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;
}

验证收据

验证收据本身归结为:
  • 检查收据是否有效 PKCS7 和 ASN1。我们已经隐含地做到了这一点。
  • 验证收据是否由 Apple 签名。这是在解析收据之前完成的,将在下面详细说明。
  • 检查收据中包含的捆绑包标识符是否与您的捆绑包标识符相对应。您应该硬编码您的捆绑包标识符,因为修改您的应用捆绑包并使用其他收据似乎并不难。
  • 检查收据中包含的应用版本是否与您的应用版本标识符相对应。出于与上述相同的原因,您应该对应用程序版本进行硬编码。
  • 检查收据哈希以确保收据与当前设备相对应。

  • 高级代码中的 5 个步骤,来自 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;
    }

    让我们深入到第 2 步和第 5 步。

    验证收据签名

    当我们提取数据时,我们浏览了收据签名验证。收据使用 Apple Inc. 根证书签名,可从 Apple Root Certificate Authority 下载。 .以下代码将 PKCS7 容器和根证书作为数据并检查它们是否匹配:
    + (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;
    }

    在解析收据之前,这是在开始时完成的。

    验证收据哈希

    收据中包含的哈希是设备 ID 的 SHA1、收据中包含的一些不透明值和捆绑包 ID。

    这就是您在 iOS 上验证收据哈希的方式。来自 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];
    }

    这就是它的要点。我可能在这里或那里遗漏了一些东西,所以我稍后可能会回到这篇文章。无论如何,我建议浏览完整的代码以获取更多详细信息。

    关于ios - 在 iOS 7 上本地验证应用内收据和捆绑收据的完整解决方案,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19943183/

    28 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com