应用内购买收据验证矛盾

4

苹果提供了两份关于收据验证的文档,其中似乎存在矛盾的陈述。

在"验证商店收据"中:

注意:在iOS上,商店收据的内容和格式是私有的且可能会更改。您的应用程序不应尝试直接解析收据数据

然而,在"iOS应用内购买收据验证"的示例代码中,商店收据被解析并验证,作为一种安全漏洞的“缓解策略”:

// Check the validity of the receipt.  If it checks out then also ensure the transaction is something
// we haven't seen before and then decode and save the purchaseInfo from the receipt for later receipt validation.
- (BOOL)isTransactionAndItsReceiptValid:(SKPaymentTransaction *)transaction
{
    if (!(transaction && transaction.transactionReceipt && [transaction.transactionReceipt length] > 0))
    {
        // Transaction is not valid.
        return NO;
    }

    // Pull the purchase-info out of the transaction receipt, decode it, and save it for later so
    // it can be cross checked with the verifyReceipt.
    NSDictionary *receiptDict       = [self dictionaryFromPlistData:transaction.transactionReceipt];
    NSString *transactionPurchaseInfo = [receiptDict objectForKey:@"purchase-info"];
    NSString *decodedPurchaseInfo   = [self decodeBase64:transactionPurchaseInfo length:nil];
    NSDictionary *purchaseInfoDict  = [self dictionaryFromPlistData:[decodedPurchaseInfo dataUsingEncoding:NSUTF8StringEncoding]];

    NSString *transactionId         = [purchaseInfoDict objectForKey:@"transaction-id"];
    NSString *purchaseDateString    = [purchaseInfoDict objectForKey:@"purchase-date"];
    NSString *signature             = [receiptDict objectForKey:@"signature"];

    // Convert the string into a date
    NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
    [dateFormat setDateFormat:@"yyyy-MM-dd HH:mm:ss z"];

    NSDate *purchaseDate = [dateFormat dateFromString:[purchaseDateString stringByReplacingOccurrencesOfString:@"Etc/" withString:@""]];


    if (![self isTransactionIdUnique:transactionId])
    {
        // We've seen this transaction before.
        // Had [transactionsReceiptStorageDictionary objectForKey:transactionId]
        // Got purchaseInfoDict
        return NO;
    }

    // Check the authenticity of the receipt response/signature etc.

    BOOL result = checkReceiptSecurity(transactionPurchaseInfo, signature,
                                       (__bridge CFDateRef)(purchaseDate));

    if (!result)
    {
        return NO;
    }

    // Ensure the transaction itself is legit
    if (![self doTransactionDetailsMatchPurchaseInfo:transaction withPurchaseInfo:purchaseInfoDict])
    {
        return NO;
    }

    // Make a note of the fact that we've seen the transaction id already
    [self saveTransactionId:transactionId];

    // Save the transaction receipt's purchaseInfo in the transactionsReceiptStorageDictionary.
    [transactionsReceiptStorageDictionary setObject:purchaseInfoDict forKey:transactionId];

    return YES;
}

如果我理解正确,如果我验证收据,当苹果决定更改收据格式时,我的应用程序可能会停止工作。

而如果我不验证收据,我就没有遵循苹果的“缓解策略”,我的应用程序就容易遭受攻击。

无论我怎么做都会受到惩罚。我是否漏掉了什么?

2个回答

3
他们强烈建议您使用自己的服务器作为验证的中介,因为这将为所有iOS版本提供一个明确且安全的通道,以便在App Store上进行验证。这确实是避免任何问题的最佳途径。
如果必须直接从设备到App Store执行验证,则只有在应用程序运行在5.1.x及以下版本时才能使用他们的缓解策略。对于iOS6及以上版本,请使用推荐的方法。
虽然一直都不应该直接解析收据,但发现的漏洞使得苹果在如何解决此问题方面陷入了困境,并决定要求应用程序开发人员实施检查。这意味着当用户更新应用程序时,收据现在被重新保护(无论iOS版本如何),从而提供更好的修复覆盖范围。作为副作用,这意味着您必须打破通常的做法(但苹果已经允许您这样做)。
我同意文档并不完全清楚,可能需要更加明确(您应该在文档页面底部向他们提供反馈)。苹果已经发布了一项缓解策略,并表示应在iOS 5.1.x及以下版本中使用该策略来解决漏洞问题。如果他们更改IAP收据的格式/内容,责任在他们身上。

谢谢WDUK。在提问之前,我已经向他们发送了反馈。;) 对于服务器不可用的情况,我认为最好的方法是仅对运行iOS 5.1或更低版本的设备执行他们提供的代码。这样,如果他们更改收据,损害就会受到限制。 - hpique
是的,那基本上就是我的答案。苹果之前允许了这种不推荐的做法,如果你没有自己的服务器,这似乎是唯一的选择(苹果似乎也理解这种情况的发生)。如何保持与 iOS 5.1.x 及以下版本的兼容性取决于苹果而不是开发者,因为使用缓解方法可能会导致兼容性问题。我不知道他们会如何做到这一点,也许他们只会在 iOS 5 已经过时的时候更改收据格式... - WDUK

0

5
抱歉,但是理解如何使用由苹果公司提供的这些链接验证收据就像通过观看两只猴子打板球来理解脑外科手术一样困难。苹果公司是模糊之王,因此这些链接并不容易理解。 - Duck
是的,在应用内购买确实有点麻烦。我已经在这方面工作了一个月左右,仍在继续。需要哪些信息帮助呢? - Chris Prince
1
我的SSL、证书等方面的知识为零。我需要看到一个从头到尾的示例,演示如何完成它。显然,我知道我必须更改一些方法以防止自动破解,但如果我能看到整个过程运作,我会很高兴。 - Duck
1
我已经在这个问题上开了一个赏金:https://dev59.com/MGIj5IYBdhLWcg3wuXeN - Duck
在过去的几天里,我似乎已经让我的iOS7设备本地收据验证工作起来了,尽管只是在初始测试阶段。大约500行代码。总的来说,我愿意分享它,但我确实担心这些与安全相关的细节会被其他人用于他们的应用程序中,并使得我的应用程序的安全性更容易被破解。你有什么想法? - Chris Prince
创建一个不同的版本,更改变量/函数名称,以不同的方式混淆字符串,更改循环的方式(使用块枚举而不是“for”),以及类的顺序或方法名称等等。显然,一旦我使用这样的代码,我也会这样做。出于您所说的相同原因,我永远不会直接使用该代码。或者,如果您想将其发送给我而不是公开发布,您完全可以(utugau....at....gmail)。 - Duck

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接