关于
了解 DUSTO 的现在以及未来。
初衷
DUSTO 诞生的初衷是为了维护 iOS 开发者的利益。
为什么使用 DUSTO ?
DUSTO 可以从根源上阻止基于共享帐号的盗版行为。
作为 iOS 开发者,你不需要再到淘宝、微店、拼多多、闲鱼等平台申诉(不仅浪费时间,而且大部分情况下都没有效果)。
注册
DUSTO 通过「邀请制」完成注册。
如果你是一名饱受共享帐号盗版困扰的 iOS 开发者,欢迎通过 Telegram 与 @z3k3c1 联系。
隐私
DUSTO 不窥探、不分析用户数据。
收费 / 捐赠
DUSTO 不向开发者收取任何费用,也不接受任何形式的捐赠。
工作原理
了解 DUSTO 是如何工作的。
基本原理
- 关联「购买」与「用户」,统计某一「购买」被不同「用户」使用的次数。
- 通过统计到的使用次数来识别和封禁「购买」。
如何识别「购买」?
StoreKit 返回的购买回执中包含一个时间戳,它对应着此次购买的付款时间。因为多位用户的付款时间很难在秒级别出现碰撞,所以这个时间戳在一定程度上可以作为此次购买的唯一标识。
数据示例:
1584763266000
DUSTO 将此类数据称为「唯一购买标识」。
如何识别「用户」?
因为 Apple 的隐私政策,应用开发者无法获取用户的 Apple ID,但是通过 CloudKit 可以获取 Apple ID 的 Hash 值。这个 Hash 值可以直接作为用户的唯一标识。
数据示例:
_85dec2bc70552fa19c1ca0c60e88af85
DUSTO 将此类数据称为「唯一用户标识」。
接入指南
了解如何在 iOS 应用中接入 DUSTO。
基本流程
-
检测「购买」:
- 检测到购买已激活,则在应用内获取「唯一购买标识」与「唯一用户标识」,并通过 DUSTO 所提供的 HTTP API 将二者发送至 DUSTO。
- DUSTO 获取到以上数据后,会检测「购买」的合法性,并响应结果。
-
自定义「处理逻辑」:
- 应用内根据以上的返回结果来定义针对检测结果的处理逻辑。
- 为用户提供获取「唯一购买标识」的途径。
下面分别对这几个步骤进行介绍。
检测「购买」
DUSTO 提供了可以简化接入流程的 SDK。
应用开发者可以阅读其中的文档、示例代码、以及下面的文档,来理解 DUSTO 的接入流程。
注意:SDK 中不包含获取「唯一购买标识」和「唯一用户标识」的相关代码。开发者需要根据自己的需求并结合下面的文档来获取。
提示:如果想要自己实现接入相关的代码,可以跳转到「附录」中查看详细接入流程。
获取「唯一购买标识」
通过 StoreKit 获取购买回执,解析其中包含的时间戳 original_purchase_date_ms
。
回执示例(有意去除了不相关信息):
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
# ...
<key>receipt</key>
<dict>
# ...
<key>in_app</key>
<array>
<dict>
# ...
<key>original_purchase_date_ms</key>
<string>1599313352000</string> # 「免费 + 内购」销售模式使用该时间戳
# ...
</dict>
# ...
</array>
# ...
<key>original_purchase_date_ms</key>
<string>1596182470000</string> # 「付费下载」销售模式使用该时间戳
# ...
</dict>
</dict>
</plist>
更完整的示例,请查看上文提到的示例项目。
获取「唯一用户标识」
通过 CloudKit 获取 Apple ID 的 Hash 值。
示例代码:
CKContainer(identifier: "iCloud.top.dusto.xxx").fetchUserRecordID
更完整的示例,请查看上文提到的示例项目。
自定义「处理逻辑」的一个示例
下面以一个采用「免费 + 内购」销售模式的应用为例,来展示一个可能的处理逻辑。
以下为伪代码:
if (内购已激活) {
获取「唯一购买标识」
获取「唯一用户标识」
向 DUSTO HTTP API 发送请求,检测以上两个标识
if (请求失败) {
重试。多次失败后,则不作处理(避免 DUSTO 故障影响应用)
} else if (请求成功,检测结果为非法购买){
关闭内购,提示用户:“你可能是盗版受害者。如有疑问,可以联系我们,发起申诉。”
} else { // 请求成功,检测结果为合法购买
不作处理
}
}
提供「唯一购买标识」的获取途径
由于 DUSTO 可能误判,因此需要为用户提供获取「唯一购买标识」的途径。
一个可能的实践:连击应用版本号 5 次后,应用自动将「唯一购买标识」复制到剪贴板。
在发生误判时,用户向开发者提供「唯一购买标识」,开发者在 DUSTO 中查询后,根据结果进行处理。
附录
HTTP API 的基础格式
POST /api/validate_purchase
Content-Type: application/json
Accept: application/json
Authorization: DUSTO {ACCESS_KEY}:{HMAC_SHA256_SIGNATURE}
X-Auth-Timestamp: 1616082750
{
"purchase_id": "2584763269001",
"user_id": "_85dec2bc70552fa19c1ca0c60e88af86"
}
解释:
-
使用
POST
方法请求/api/validate_purchase
。 -
HTTP API 使用 JSON 格式通讯,
Content-Type
和Accept
两个 HTTP 头用来保证使用了 JSON 格式进行通讯。 -
Authorization
HTTP 头中包含签名相关信息。其中,{ACCESS_KEY}
可以从 Integration 面板中获取。{HMAC_SHA256_SIGNATURE}
是将 Request Method、Request PATH、序列化后的 Request Body 以及X-Auth-Timestamp
头用+
按顺序拼接后,使用 HMAC-SHA256 计算所得的签名(下一节会详细介绍)。 -
X-Auth-Timestamp
HTTP 头中包含发起请求时的 UTC 时间所对应的秒级 UNIX 时间戳。这个时间戳的可接受误差范围是 60 秒。 -
请求体格式为 JSON,其中
purchase_id
对应「唯一购买标识」,user_id
对应「唯一用户标识」。
HTTP API 的签名计算方法
以上述请求为例:
-
Request Method 为
POST
-
Request Path 为
/api/validate_purchase
-
序列化后的 Request Body 为
{"purchase_id":"2584763269001","user_id":"_85dec2bc70552fa19c1ca0c60e88af86"}
序列化的方式:将 JSON 中的数据按照 key 的字母顺序排序,并去除所有空白字符。
-
X-Auth-Timestamp
头中包含的时间戳为1616082750
以 +
将上述内容按顺序拼接后得到需要签名的字符串(下文中将此数据称为 MESSAGE):
POST+/api/validate_purchase+{"purchase_id":"2584763269001","user_id":"_85dec2bc70552fa19c1ca0c60e88af86"}+1616082750
从 Integration 面板中获取与 ACCESS KEY 对应的 ACCESS SECRET。这里假设:
-
ACCESS KEY 为
bFX4HgXiJHRERhdX
-
ACCESS SECRET 为
topQUN7wnP-9qPnX4sJ7RsOSEOyXB48h
使用 ACESSS SECRET,以 HMAC-SHA256 算法对 MESSAGE 进行签名,得到:
ba2d2015d2cb08948c7cd718121cd56c97863b67ebe4d2361f43412890c680b4
再用 BASE64 进行一次编码,就得到了最终的签名:
YmEyZDIwMTVkMmNiMDg5NDhjN2NkNzE4MTIxY2Q1NmM5Nzg2M2I2N2ViZTRkMjM2MWY0MzQxMjg5MGM2ODBiNA==
DUSTO 使用了 danharper/hmac-examples 提供的 HMAC-SHA256 计算方法。应用开发者自行实现签名时,需要保证与其兼容。
HTTP API 的请求示例
POST /api/validate_purchase
Content-Type: application/json
Accept: application/json
Authorization: DUSTO hXJLs_2UzSRQFgqT:ODlkY2E5OWY3ZmU1MGUyYzBiNjg1NmU5MjExM2Q5MTNkOGFiYzBlNTliZmU5YmRhN2QyZTY5ZWVjNDg2MmY2NQ==
X-Auth-Timestamp: 1616082750
{
"purchase_id": "2584763269001",
"user_id": "_85dec2bc70552fa19c1ca0c60e88af86"
}
HTTP API 的响应示例
判定为有效「购买」:
{
"data": {
"status": "valid"
}
}
判定为无效(恶意)「购买」:
{
"data": {
"status": "invalid"
}
}
使用指南
了解如何使用 DUSTO。
全局开关
DUSTO 提供了应用级别的全局开关。
在关闭检测后,DUSTO 会将所有的检测请求判定为合法。
开发者可以在任意时刻关闭 DUSTO 检测,不会给应用带来任何影响。
该功能在应用已经上架,但又不想继续使用 DUSTO 所提供的服务时非常有用。
用户申诉
在面对用户申诉时,应用开发者可以通过 DUSTO 的面板查询「用户」与「购买」的详情,根据情况解除封禁或者不予理睬。
合理的分享次数
默认情况下,DUSTO 允许某一「购买」被 5 个「用户」使用。这是我们通过以往的观察所得到一个合理值。
在我们的数据中:
- 99% 的「购买」只会被一个「用户」使用。
- 0.9% 的「购买」会被合理地分享,但都不超过 5 次。
- 0.1% 的购买会被恶意分享。
解除封禁
DUSTO 提供了解除封禁的功能,因为:
- 回执中的付款时间存在碰撞的可能性,如果用户运气很差,有可能发生误判。
- 合理范围内的分享应该是被允许的,如果用户使用了多个属于自己的 iCloud 账户,有可能发生误判。
发生误判时,可以通过提高「用户」对应「购买」的最大使用次数来解除封禁。
FAQ
常见问题汇总。
为什么接入 DUSTO 后,它没有按照预期工作?
DUSTO 的正常工作需要两个前提:
- 应用可以联网。否则,应用无法与 DUSTO 服务端通讯。
- 用户已经登录 iCloud。否则,无法获取「唯一用户标识」。
如果盗版用户有意不登录 iCloud、关闭联网权限,那么 DUSTO 就无法工作了。
不过,先不用沮丧。由于分散的个体难以达成共识,总会有人联网、总会有人登录 iCloud,只要这样做的用户数量足够多,总是可以检测到恶意「购买」的。
DUSTO 可以支持我的全球业务吗?
DUSTO 使用 Cloudflare 进行全球加速。
在中国以外的地区,不管是控制台,还是 HTTP API,都有着不错的响应速度。
在中国,虽然响应速度会受到特殊网络环境的影响,但是 HTTP API 的连通性是可以保证的,所以可以放心地集成到应用中。在访问控制台时,为了保证不错的响应速度,视不同地区,可能需要科学上网。
综上所述,DUSTO 是可以支持全球业务的。
DUSTO 可以保证用户数据的安全吗?
接入 DUSTO 后,只有上文提到的两个标识会被发送到 DUSTO。DUSTO 只使用这两个标识来判断购买和用户的唯一性,不作他用。
即使在最坏的情况(DUSTO 被爆库)中,这两个标识完全泄漏,也不会对用户造成任何影响:
- 唯一购买标识:本质上只是一个时间戳,不足以用来与 Apple ID 进行关联。
- 唯一用户标识:本质上是 Apple ID 的 Hash 值,仅在访问 Cloud Database 时有用。不过,由于 Cloud Database 的通信过程无法从外部干预,即使该 Hash 值泄漏,也难以被利用。
DUSTO 可以保证系统的稳定运行吗?
DUSTO 采用了滚动升级的部署方式,即使在应用更新时也可以保证系统的可用性。
另外,DUSTO 还配备了完善的硬件监控和网络监控,可以及时发现影响可用性的潜在问题。
DUSTO 为什么使用这么繁琐的 HMAC-SHA256 签名方式呢?
DUSTO 一定动了某些人的蛋糕。有些人不会觉得自己错,甚至可能会穷凶极恶地进行反击。DUSTO 对此做好了准备:
- HMAC-SHA256 签名在数据传输过程中不会传输 ACCESS SECRET,并且可以保证数据的一致性。这可以有效防御请求伪造。
-
签名时加入的
X-Auth-Timestamp
时间因子限制了请求的有效期。这可以有效防御重放攻击和 DDoS 攻击。
如果盗版用户都使用分享帐号来登录 iCloud 呢?
DUSTO 的工作机制确实无法防御这种使用方式。
但是,使用共享账号的用户,并不会使用共享账号来登录 iCloud —— 这是使用共享账号盗版的常识。
鸣谢
感谢向世界分享智慧的人们。
在构建 DUSTO 的过程中,我们参考了:
DUSTO 的诞生离不开以上开发者们的分享。