I have read a lot of docs and code that in theory will validate an in-app and/or bundle receipt.
Given that my knowledge of SSL, certificates, encryption, etc., is nearly zero, all of the explanations I have read, like this promising one, I have found difficult to understand.
They say the explanations are incomplete because every person has to figure out how to do it, or the hackers will have an easy job creating a cracker app that can recognize and identify patterns and patch the application. OK, I agree with this up to a certain point. I think they could explain completely how to do it and put a warning saying "modify this method", "modify this other method", "obfuscate this variable", "change the name of this and that", etc.
Can some good soul out there be kind enough to explain how to LOCALLY validate, bundle receipts and in-app purchase receipts on iOS 7 as I am five years old (ok, make it 3), from top to bottom, clearly?
Thanks!!!
If you have a version working on your apps and your concerns are that hackers will see how you did it, simply change your sensitive methods before publishing here. Obfuscate strings, change the order of lines, change the way you do loops (from using for to block enumeration and vice-versa) and things like that. Obviously, every person that uses the code that may be posted here, has to do the same thing, not to risk being easily hacked.
This is a Swift 4 version for validation of in-app-purchase receipt...
Lets create an enum to represent the possible errors of the receipt validation
Then let's create the function that validates the receipt, it will throws an error if it's unable to validate it.
Let's use this helper function, to get the expiration date of a specific product. The function receives a JSON response and a product id. The JSON response can contain multiple receipts info for different products, so it get the last info for the specified parameter.
Now you can call this function and handle of the possible error cases
Here's a walkthrough of how I solved this in my in-app purchase library RMStore. I will explain how to verify a transaction, which includes verifying the whole receipt.
At a glance
Get the receipt and verify the transaction. If it fails, refresh the receipt and try again. This makes the verification process asynchronous as refreshing the receipt is asynchronous.
From RMStoreAppReceiptVerifier:
Getting the receipt data
The receipt is in
[[NSBundle mainBundle] appStoreReceiptURL]
and is actually a PCKS7 container. I suck at cryptography so I used OpenSSL to open this container. Others apparently have done it purely with system frameworks.Adding OpenSSL to your project is not trivial. The RMStore wiki should help.
If you opt to use OpenSSL to open the PKCS7 container, your code could look like this. From RMAppReceipt:
We'll get into the details of the verification later.
Getting the receipt fields
The receipt is expressed in ASN1 format. It contains general information, some fields for verification purposes (we'll come to that later) and specific information of each applicable in-app purchase.
Again, OpenSSL comes to the rescue when it comes to reading ASN1. From RMAppReceipt, using a few helper methods:
Getting the in-app purchases
Each in-app purchase is also in ASN1. Parsing it is very similar than parsing the general receipt information.
From RMAppReceipt, using the same helper methods:
It should be noted that certain in-app purchases, such as consumables and non-renewable subscriptions, will appear only once in the receipt. You should verify these right after the purchase (again, RMStore helps you with this).
Verification at a glance
Now we got all the fields from the receipt and all its in-app purchases. First we verify the receipt itself, and then we simply check if the receipt contains the product of the transaction.
Below is the method that we called back at the beginning. From RMStoreAppReceiptVerificator:
Verifying the receipt
Verifying the receipt itself boils down to:
The 5 steps in code at a high-level, from RMStoreAppReceiptVerificator:
Let's drill-down into steps 2 and 5.
Verifying the receipt signature
Back when we extracted the data we glanced over the receipt signature verification. The receipt is signed with the Apple Inc. Root Certificate, which can be downloaded from Apple Root Certificate Authority. The following code takes the PKCS7 container and the root certificate as data and checks if they match:
This was done back at the beginning, before the receipt was parsed.
Verifying the receipt hash
The hash included in the receipt is a SHA1 of the device id, some opaque value included in the receipt and the bundle id.
This is how you would verify the receipt hash on iOS. From RMAppReceipt:
And that's the gist of it. I might be missing something here or there, so I might come back to this post later. In any case, I recommend browsing the complete code for more details.
I'm surprised nobody mentioned Receigen here. It's a tool that automatically generates obfuscated receipt validation code, a different one each time; it supports both GUI and command-line operation. Highly recommended.
(Not affiliated with Receigen, just a happy user.)
I use a Rakefile like this to automatically rerun Receigen (because it needs to be done on every version change) when I type
rake receigen
: