我的服务器如何安全地验证 iPhone 应用内购买?

发布于 2024-08-07 20:33:15 字数 474 浏览 6 评论 0 原文

查看 Apple 的图表 服务器购买模型

在步骤 #9 中,服务器如何知道它确实正在与有权购买的 iPhone 进行通话,并且 Eve 没有使用不诚实获得的收据执行重播?

收据可能有效,但这并不能证明发件人是有权的一方。

iPhone 上是否有可用于签署收据的设备证书概念?

有什么方法可以将收据绑定到设备,或者将收据同时绑定到 iTunes 帐户和设备,以便服务器可以验证吗?

Look at Apple's diagram for the server purchase model.

In step #9, how can the server know that it is really talking with an iPhone that is entitled to the purchase, and that Eve is not performing a replay with a dishonestly obtained receipt?

The receipt may be valid, but that doesn't prove that the sender is the entitled party.

Is there any notion of a device certificate on the iPhone that can be used to sign the receipt?

Is there any way to bind the receipt to the device, or bind the receipt to both the iTunes account and to the device, so the server can validate?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

独享拥抱 2024-08-14 20:33:15

Apple 提供的易受攻击的方法

服务器可以通过 执行以下操作

  1. iPhone 应用程序收到 transactionReceipt 购买后。让 iPhone base64 对其进行编码(您可以使用这个开源添加到 NSData) 并将其发送到您的服务器。 (您甚至可以按原样发送,并在验证之前让服务器对其进行 base64 编码。)

  2. 让您的服务器使用单个密钥发送 JSON 请求 receipt-data 使用base64编码transactionReceipthttps://buy.itunes.apple.com/verifyReceipt。 (有关如何使用各种服务器端语言执行此操作的说明请参阅此网站

  3. 服务器将使用带有两个键的 JSON 对象进行响应:status(这是一个整数)和 receipt(这是重复的收据)。

如果状态为零,则应接受收据有效,非零值表示收据无效。

Apple 方法的安全补充

但是,存在一些安全隐患。用户可以使用另一个用户的收据,因为设备未绑定到收据,或者用户可以使用另一个产品的收据,因为服务器不验证收据的产品 ID。为了确保这种情况不会发生,您还应该执行以下操作:

  1. 当您第一次在应用程序中收到收据时,立即将其与 设备的 UUID 通过安全通道(例如 HTTPS)或 SSL 套接字。 不要将其存储在任何地方,而是将其保留在内存中。

  2. 在您的服务器上,将 UUID 和收据对存储在数据库中。

  3. 当设备发送 UUID 和收据对时,请通过您的数据库验证该收据尚未被使用,通过检查收据产品 ID收据只是一个 JSON 对象,因此您的服务器可以通过从 Base64 解码收据来读取内容。

  4. 通过安全通道向设备返回响应,告知其购买是否为:< /p>

    • 已验证为新的(不在数据库中且有效)
    • 过去已进行过身份验证(数据库中已存在相同的 UUID 和收据对)
    • 因产品 ID 错误而被拒绝
    • 因收据已与另一个 UUID 一起使用而被拒绝。

由于收据仅存在于设备内存中,并且您的应用程序使用设备的 UUID(可以是 被越狱设备欺骗,请参阅评论),并且您的所有产品购买都会以安全的方式使用设备的 UUID 记录在您的服务器上;用户不能使用其他用户的收据来验证购买,也不能使用其他产品的收据,因为您会进行检查。

如果您想验证交易的其他详细信息,您还可以验证收据中的其他字段。例如,如果您的产品是订阅产品,您可能还想查看交易日期。

此外,用户无法通过将设备置于与您的主机同名的专用网络上来冒充您的服务器,因为他们没有您的 SSL 证书。

失败注意事项

由于在用户设备收到收据和与您的服务器验证收据之间可能会发生失败(例如,如果用户失去连接,或者您的服务器因维护而停机),您还应该让用户“重新授权”。重新授权应从商店获取收据(使用已恢复交易) 并将其重新发送到服务器,就像这是一次新购买一样。这应该很少需要使用,但应该可以避免用户在网络故障的情况下重新购买产品。

多设备考虑

这意味着,如果用户想要在多台设备上使用某个应用程序,他们将不得不多次购买该产品。这可能是预期的效果,但您应该在用户购买之前通知他们,因为他们可能希望能够在与其帐户关联的设备上使用内容。

如果收据还包含 iTunes 帐户信息,则身份验证可以使用该信息来允许用户在所有设备(但不是朋友的设备)之间共享内容。

Apple-Provided Vulnerable Approach

The server can authenticate a purchase by doing the following:

  1. The iPhone application receives a transactionReceipt after the purchase. Have the iPhone base64 encode it (You can use this open-source addition to NSData) and send it to your server. (You could even send it as-is and have the server base64 encode it before validation.)

  2. Have your server send a JSON request with the single key receipt-data with the base64 encoded transactionReceipt to https://buy.itunes.apple.com/verifyReceipt using an HTTP POST. (For directions on how to do this in various server-side languages see this site)

  3. The server will respond with a JSON object with two keys: status which is an integer and receipt which is the receipt repeated.

If status is zero, the receipt is valid should be accepted, a non-zero value means the receipt isn't valid.

Secure Additions to Apple's Approach

However, there are a few security implications. A user could use another user's receipt since devices aren't tied to receipts, or a user could use another product's receipt since the server doesn't verify the product id of the receipt. To ensure this doesn't happen you should also do the following:

  1. When you first get the receipt in the application, immediately send it to your server along with the device's UUID over a secure channel such as HTTPS or an SSL socket. Do not store it anywhere, leave it in memory.

  2. On your server, store the UUID and receipt pair in a database.

  3. When a device sends a UUID and receipt pair, verify with your database that the receipt has not already been used, and make sure the receipt is actually for your product by checking the receipt's product id. The receipt is just a JSON object, so your server can read the contents by decoding the receipt from base64.

  4. Return a response to the device over the secure channel telling it whether the purchase is:

    • Authenticated as new (wasn't in DB and was valid)
    • Authenticated in the past (Same UUID and receipt pair was already in DB)
    • Denied due to wrong product id
    • Denied due to having already used the receipt with another UUID.

Since the receipt is only ever in memory on the device, and your application uses the device's UUID (can be spoofed by jailbroken devices, see comments), and all purchases of your product are logged with the device's UUID on your server in a secure manner; a user could not use another user's receipt to verify the purchase, nor could they use a receipt from another product, since you check for that.

You can also validate other fields from the receipt if you want to verify other details of the transaction. For example, if your product is a subscription, you'll like want to look at the transaction date as well.

Also, users cannot pretend to be your server by having the device on a private network with a host of the same name as yours, since they won't have your SSL certificate.

Failure Considerations

Since failure might occur between when the user's device gets the receipt and verifying it with your server (for example if the user looses connectivity, or your server is down for maintenance), you should also let the user "re-authorize". Re-authorizing should get the receipt from the store (using a Restored Transaction) and re-send it to the server just as though this was a new purchase. This should rarely need to be used, but should be available to save the user having to re-buy the product in the case of network failure.

Multiple Devices Consideration

This means that if a user wants to use an application on more than one device, they will have to purchase the product multiple times. This might be the desired effect, but you should likely inform your users before they purchase since they might expect to be able to use the content across devices they have associated with their account.

If the receipt also contains the iTunes account information, authentication could use that to allow users to share content between all their devices (but not their friends').

浅蓝的眸勾画不出的柔情 2024-08-14 20:33:15

我不认为可以将收据绑定到设备上。

我的理解是,您可以在多个设备上安装应用程序,而无需支付额外费用。将其绑定到设备意味着如果您升级/更换手机,则需要再次购买所有应用程序。

I do not think that can bind the receipt to the device.

My understanding is that you are allowed to install an application on multiple devices without extra cost. Binding it to the device would mean that if you for example upgrade/change your phone you would need to purchase all the apps again.

淡紫姑娘! 2024-08-14 20:33:15

我相信,如果您无法读取用户的 Apple ID,那么针对盗版的唯一保护就是跟踪(当然是服务器端)每个 transaction_id 的下载请求数量,并在它们超过特定值时对其进行限制。

因此,如果您将其限制为 50 个,则可以为用户在多个设备上部署应用程序及其内容并多次恢复提供合理的余量,但对于想要无限量分发具有有效收据的盗版版本的人来说会变得困难恢复。当然,他们可以只分发包含您所有内容的版本,但您对此无能为力,至少他们不会对您的服务器造成负担。

I believe if you can't read the user's apple ID, your only protection against piracy would be keeping track(server-side of course) of the number of download requests per transaction_id and limit those if they go over a certain value.

So if you limit it to say 50, that gives a reasonable margin for the user to deploy the app and it's contents on multiple devices and restore several times, but makes it hard for whoever wants to distribute a pirated version with a valid receipt for unlimited restores. Of course they can just distribute a version with all your content, but that's nothing you can do about that and at least they're not taxing your servers.

盛装女皇 2024-08-14 20:33:15

UDID 不再工作

Beniot 的回答很好,但是,现在,正如 Joe D'Andrea 提到的,UDID 已被弃用,我上次尝试时,使用调用获取 UDID 的应用程序在上传到iTunes。

限时收据作为收据计数器的替代方案

要添加到 hloupyhonza 的答案中,除了针对特定收据具有“下载请求”计数器之外,您还可以按时间限制收据有效性。我发现 12 到 24 小时之间的任何时间都是合理的。

这种方法还允许购买者在他拥有的任何其他设备上使用购买的产品,只要他使用同一 Apple ID 登录 App Store。注意:每次完成“恢复购买”后,Apple 都会返回一张全新的收据(包含原始收据的详细信息) - 这允许在我们为特定收据设置的时间限制之后恢复购买。

防止“现成”黑客

为了防止典型的“Googled”黑客解决方案(我的数据显示这几乎构成了所有 IAP 黑客尝试),我使用了校验和(选择您最喜欢的算法,这并不重要,除非您想要它是防水的)以下串联:

  • 收据数据 json 字符串
  • 共享密钥
  • 验证成功状态代码。

该应用程序将验证我们的验证服务器返回的校验和。但这并不是无懈可击的,因为黑客可能会从您的应用程序的二进制文件中检索共享密钥。但到目前为止,它已经阻止了所有“现成的”黑客攻击,这对于我的使用来说已经足够了。

UDID Does Not Work Anymore

Beniot answer is great, however, these days, as mentioned by Joe D'Andrea, UDID is deprecated and the last time I tried, an App that used the call to get the UDID failed to pass validation during upload to iTunes.

Time-limited Receipts as Alternative to Receipt Counters

To add on to hloupyhonza's answer, besides having a "download request" counter for a particular receipt, you can just limit the receipt validity by time. I found anything between 12 to 24 hours reasonable.

This method also allows the purchaser to use the purchase on any other device he owns as long as he logs into the App Store with the same Apple ID. Note: Each time Restore Purchases is done, Apple returns a completely new receipt (with details of the original receipt contained) - this allows purchases to be restored past the time limit we set for a particular receipt.

Preventing "Off-The-Shelf" Hacks

To prevent typical "Googled" hacking solutions (my data shows this constitutes almost all of IAP hacking attempts), I use a checksum (pick your favorite algorithm, doesn't matter unless you want to make it watertight) of the following concatenation:

  • receipt-data json string
  • A shared secret key
  • Validation success status code.

The App will verify the checksum returned by our validation server. This is not watertight though, as the hacker may retrieve the shared key from your App's binary. But it has prevented all "off-the-shelf" hacks thus far and that's good enough for my use.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文