1

I'm trying to validate that the receipt is for this particular device using the code from a popular library for Receipt Validation called RMStore:

NSUUID * uuid = [[UIDevice currentDevice] identifierForVendor];
uuid_t uuidBytes;
[uuid getUUIDBytes:uuidBytes];

NSMutableData * data = [[NSMutableData alloc] init];
[data appendBytes:uuidBytes length:sizeof(uuidBytes)];
[data appendData:_parsedReceipt.opaqueValue];
[data appendData:_parsedReceipt.bundleIdentifierData];

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

return [computedHash isEqualToData:_parsedReceipt.hash];

But the two hashes are not equal. Is there something wrong with the code?

Edit

    SKReceiptRefreshRequest * request = [[SKReceiptRefreshRequest alloc] initWithReceiptProperties:@{SKReceiptPropertyIsRevoked: @YES}];
    [request setDelegate:self];
    [request start];

After I re-fetch the receipt once, the hashes start to match. This is the most bizarre behavior I have seen. Does anyone have an idea why this may happen?

platypus
  • 1,165
  • 5
  • 27
  • 47
  • 1
    Looks good in general, but I don't use RMStore for this. Could there be an issue with _parsedReceipt? – Chris Prince Mar 15 '14 at 17:00
  • The above code is very similar to the code snippet in the first answer in: http://stackoverflow.com/questions/19943183/a-complete-solution-to-locally-validate-an-in-app-receipts-and-bundle-receipts-o?rq=1 – platypus Mar 15 '14 at 18:16
  • I take you're using RMStore as a basis. Does RMStore say that the hashes are different as well? – hpique Mar 19 '14 at 09:25
  • So I figured this weird behavior out: the first time, the hash does not validate. However, when I do fetch the receipt again using SKReceiptRefreshRequest (see edit in original post), the second time and so-on it starts validating... – platypus Mar 22 '14 at 07:13

2 Answers2

1

As indicated in the answer from where you took that code, Apple recommends to refresh the receipt if validation fails. This is what RMStore does to validate a receipt/transaction:

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];
}];
hpique
  • 119,096
  • 131
  • 338
  • 476
0

I'll just add one thing here - took me a while to figure out why my hash is mismatching...

receipt bundleId example: ASN1 OCTET STRING (27 byte) 0C19636F6D2E706177656C6B6C61707563682E536B696E4578616D

Which is actually made of Identifier (0C), length (19) and value (63..6D).

For comparing app.bundleId == receipt.bundleId -> use value only
For generating hash -> use the whole ASN1 buffer
(otherwise SHA1 will result in different value)

Pawel Klapuch
  • 380
  • 4
  • 15