通过 Play Integrity API 的 nonce 字段提高应用安全性

通过 Play Integrity API 的 nonce 字段提高应用安全性
www.zeeklog.com  - 通过 Play Integrity API 的 nonce 字段提高应用安全性

作者 / Oscar Rodriguez, Developer Relations Engineer

我们近期发布了 ,希望帮助开发者们保护自己的应用和游戏,使其免受可能存在风险的欺诈性互动 (例如欺骗和未经授权的访问) 的危害,让您能够采取适当措施来防范攻击并减少滥用行为。

Play Integrity API

https://developer.android.google.cn/google/play/integrity

除了与应用完整性、设备完整性和许可信息相关的有用信号外,Play Integrity API 还提供了一个简单却非常实用的功能,即 "nonce"。如果使用得当,开发者可以进一步加强 Play Integrity API 的现有保护措施,并降低特定类型攻击的风险,例如中间人 (PITM) 篡改攻击和重放攻击。

在这篇文章中,我们将深入介绍什么是 nonce、它的工作原理,以及如何使用 nonce 字段来进一步保护您的应用和游戏。

什么是 nonce?

在密码学和安全工程学中,nonce (number once) 是一个在安全通信中仅能被使用一次的数字。nonce 用途广泛,如身份验证、数据加密和哈希处理等。

在 Play Integrity API 中,nonce 是您在调用 API 完整性检查前设置的不透明 Base64 编码二进制 blob,并通过被签名的响应中原样返回。根据创建和验证 nonce 的方式,您可以使用它来进一步加强 Play Integrity API 的现有保护措施,并缓解特定类型的攻击,例如中间人 (PITM) 篡改攻击和重放攻击。

除了在被签名的响应中按原样返回 nonce,Play Integrity API 不会对 nonce 实际数据进行任何处理,因此您可以设置任意值,只要它是一个有效的 Base64 值即可。也就是说,为了对响应进行数字签名,nonce 值将被发送到 Google 服务器,因此请勿将 nonce 设置为任何类型的个人身份信息 (PII),例如用户姓名、电话或电子邮件地址。

设置 nonce

将您的应用设置为使用 Play Integrity API 之后,您可以使用 setNonce() 方法,或其适当的变体设置 nonce,这些变体适用于 API 的 、Java、Unity 和 Native 版本。

将您的应用设置为使用 Play Integrity API

https://developer.android.google.cn/google/play/integrity/setup

设置 nonce

https://developer.android.google.cn/google/play/integrity/verdict#request

Kotlin:

val nonce: String = ...


// 创建 manager 的实例
val integrityManager =
    IntegrityManagerFactory.create(applicationContext)


// 通过 nonce 获取完整性令牌
val integrityTokenResponse: Task<IntegrityTokenResponse> =
    integrityManager.requestIntegrityToken(
        IntegrityTokenRequest.builder()
             .setNonce(nonce) // 设置 nonce
             .build())

Java:

String nonce = ...


// 创建 manager 的实例
IntegrityManager integrityManager =
    IntegrityManagerFactory.create(getApplicationContext());


// 通过 nonce 获取完整性令牌
Task<IntegrityTokenResponse> integrityTokenResponse =
    integrityManager
        .requestIntegrityToken(
            IntegrityTokenRequest.builder()
            .setNonce(nonce) // 设置 nonce
            .build());

Unity:

string nonce = ...


// 创建 manager 的实例
var integrityManager = new IntegrityManager();


// 通过 nonce 获取完整性令牌
var tokenRequest = new IntegrityTokenRequest(nonce);
var requestIntegrityTokenOperation =
    integrityManager.RequestIntegrityToken(tokenRequest);

Native:

// 创建 IntegrityTokenRequest 对象
const char* nonce = ...
IntegrityTokenRequest* request;
IntegrityTokenRequest_create(&request);
IntegrityTokenRequest_setNonce(request, nonce); // 设置 nonce
IntegrityTokenResponse* response;
IntegrityErrorCode error_code =
        IntegrityManager_requestIntegrityToken(request, &response);

验证 nonce

Play Integrity API 的响应以 JSON 网络令牌 (JWT) 的形式返回,其负载为纯文本 JSON,格式如下:

{
  requestDetails: { ... }
  appIntegrity: { ... }
  deviceIntegrity: { ... }
  accountDetails: { ... }
}

JSON 网络令牌 (JWT)

https://jwt.io/

纯文本 JSON

https://developer.android.google.cn/google/play/integrity/verdict#returned-payload-format

您可以在 requestDetails 结构中查看 nonce,其格式如下:

requestDetails: {
  requestPackageName: "...",
  nonce: "...",
  timestampMillis: ...
}

nonce 字段的值应与您之前调用 API 传过去的值完全匹配。此外,由于 nonce 值位于 Play Integrity API 的加密签名响应中,收到响应之后是无法改变它的。通过这些属性,您就可以使用 nonce 进一步保护您的应用。

保护重要操作

试想这个场景,一名攻击者正在试图恶意将玩家得分虚报给游戏服务端。这种情况下,设备和应用都是完整的,但攻击者仍可以通过代理服务器或者虚拟专用网络查看并修改与游戏服务器之间的通信数据流,从而达到虚报分数的目的。

在这种情况下,仅调用 Play Integrity API 不足以保护应用: 设备没有被破解、应用也是合法的,因此该操作可以通过 Play Integrity API 的所有检查。

但您可以使用 Play Integrity API 的 nonce 来保护这种报告游戏分数的特定高价值操作,即在 nonce 中编码操作的值。实现方法如下:

  1. 用户发起重要操作;
  2. 应用准备好要保护的消息,例如 JSON 格式的消息;
  3. 应用计算要保护的消息的加密哈希值。例如,使用 SHA-256 或 SHA-3-256 哈希算法;
  4. 应用调用 Play Integrity API,并调用 setNonce() 以将 nonce 字段设置为在上一步计算的加密哈希值;
  5. 应用将要保护的消息以及 Play Integrity API 的签名结果发送给服务器;
  6. 应用服务器验证其收到的消息的加密哈希值是否与签名结果中的 nonce 字段值匹配,并拒绝任何不匹配的结果。

下面的序列图说明了相关步骤:

www.zeeklog.com  - 通过 Play Integrity API 的 nonce 字段提高应用安全性

只要受保护的原始消息与签名结果一起发送,且服务器和客户端都使用完全相同的机制来计算 nonce,通过这样的方式来保证消息不会被篡改。

请注意,在上述场景下,安全模型的有效性仅限攻击行为发生在网络中 (而不是发生在设备或应用),因此验证 Play Integrity API 提供的设备和应用完整性信号也尤为重要。

防范重放攻击

我们再试想另外一种场景,一个应用或游戏使用了 Play Integrity API 来保护自己的 C/S 架构,但攻击者试图通过用已破解的设备与服务端交互,并且不让服务器端监测到。

若要 "达成" 这种攻击目标,攻击者会首先在合法的设备上让应用与 Play Integrity API 进行交互,并获得已经签名的响应内容,然后再在破解设备上运行应用并拦截 Play Integrity API 的调用,使用此前记录的、已获得签名的响应内容进行响应,这样一来就不会执行完整性检查了。

由于已签名的响应并未以任何方式被更改,所以数字签名看似正常,应用服务器就会误以为它正在与合法设备进行通信。我们将此称为重放攻击

抵御此类攻击的第一道防线是验证签名响应中的 timestampMillis 字段。这个字段包含创建响应时的时间戳,即使在数字签名通过验证的情况下,也能用于服务器端检测是否为可疑的旧响应。

也就是说,应用服务器也可以利用 Play Integrity API 中的 nonce,为每个响应分配一个唯一值,并验证该响应是否与之前设置的唯一值匹配。实现方法如下:

  1. 服务器以攻击者无法预测的方式创建全局唯一值。例如,128 位或位数更多的加密安全随机数;
  2. 应用调用 Play Integrity API,并将 nonce 字段设置为应用服务器接收的唯一值;
  3. 应用将 Play Integrity API 的签名结果发送到服务器;
  4. 服务器验证签名结果中的 nonce 字段是否与之前生成的唯一值匹配,并拒绝所有不匹配的结果。

下面的序列图说明了相关步骤:

www.zeeklog.com  - 通过 Play Integrity API 的 nonce 字段提高应用安全性

实现上述流程后,每次服务器要求应用调用 Play Integrity API 时,它都会使用不同的全局唯一值,因此只要攻击者无法预测该值,nonce 与预期值不匹配,就无法重用之前的响应。

结合两种保护措施

虽然上述两种机制的工作方式不同,但如果应用同时需要两种保护,则可以将这两种机制组合在一个 Play Integrity API 调用中,例如,将两种保护措施的结果附加到一个更大的 Base64 nonce 中。结合两种保护措施的实现方法如下:

  1. 用户发起重要操作;
  2. 应用要求服务器提供一个标识请求的唯一值;
  3. 应用服务器生成全局唯一值,防止攻击者做出预测。例如,您可以使用加密安全的随机数生成器创建此类值。我们建议创建不小于 128 位的值;
  4. 应用服务器向应用发送全局唯一值;
  5. 应用准备好要保护的消息,例如 JSON 格式的消息;
  6. 应用计算要保护的消息的加密哈希值。例如,使用 SHA-256 或 SHA-3-256 哈希算法;
  7. 应用通过附加从应用服务器收到的唯一值以及要保护的消息的哈希值来创建一个字符串;
  8. 应用调用 Play Integrity API,并调用 setNonce() 以将 nonce 字段设置为在上一步中创建的字符串;
  9. 应用将要保护的消息以及 Play Integrity API 的签名结果发送给服务器;
  10. 应用服务器拆分 nonce 字段的值,然后验证消息的加密哈希值以及之前生成的唯一值是否与预期值相匹配,并拒绝任何不匹配的结果。

下面的序列图说明了相关步骤:

www.zeeklog.com  - 通过 Play Integrity API 的 nonce 字段提高应用安全性

以上是您可以使用 nonce 进一步保护应用免受恶意用户攻击的一些示例。如果您的应用会处理敏感数据,或容易被滥用,我们建议您考虑借助 Play Integrity API,采取相关措施缓解威胁。

如需了解关于使用 Play Integrity API 的更多信息并开始体验,请前往 Play Integrity API 页面:

https://developer.android.google.cn/google/play/integrity

www.zeeklog.com  - 通过 Play Integrity API 的 nonce 字段提高应用安全性

点击屏末 | 阅读原文 | 即刻了解 Play Integrity API 更多信息


www.zeeklog.com  - 通过 Play Integrity API 的 nonce 字段提高应用安全性

对方块的大小和颜色同时进行

Read more

redis分布式锁原理与实例

redis分布式锁原理与实例

目录 1 分布式锁的实现方式 分布式锁常用的有三种实现方式: 1)基于zookeeper的分布式锁; 2)基于数据库的分布式锁; 3)基于redis的分布式锁。 每一种分布式锁都有自己的优缺点,今天我们来介绍一下基于redis的分布式锁。 2 redis分布式锁的原理 2.1 分布式锁的基本实现 redis的存储结构是key-value形式的,并且redis的操作都是单线程执行的。基于这两点我们可以设置一个key来做多线程的竞争资源,线程拿到key就继续业务流程,拿不到就等待。使用redis加锁,实际上就是给key设置一个值: set lock_key random_value NX NX键不存在时,才对键进行操作。这样一个线程操作lock_key时,其他线程就需要等待,达到了加锁的目的。 但是如果一个线程一直持有锁,就会造成死锁的情况。那么如何规避死锁呢? 2.2 如何避免死锁 可以设置过期时间,比如设置过期时间为500毫秒: set lock_key random_value NX

By Ne0inhk
Java NIO原理剖析与实战

Java NIO原理剖析与实战

目录 1 原理剖析 jdk1.4之前是没有NIO的,传统IO是阻塞式的,即客户端有一个请求时,服务端就需要创建一个线程来处理请求,如果并发特别高,服务端会面临创建太多线程导致内存泄露等问题,并且有的连接不做任何事情却一直占用一个线程造成资源浪费。当然这些问题可以在服务端使用线程池来解决,但是性能上存在瓶颈。这时候NIO就应运而生。 NIO中一个线程可以管理多个连接,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。 1.1 BIO原理 同步阻塞式IO,服务端使用accept等待接收客户端的请求,接收到请求之后建立通讯套接字进行读写操作,此时不再接收其他客户端的请求,等待当前客户端操作执行完成。如果客户端请求并发非常高,需要服务端开启多线程来处理,处理逻辑是这样的:每次accept阻塞等待客户端的请求,当接收到请求立即建立通讯套接字并启动一个线程来处理通讯套接字的读写操作,accept就可以继续阻塞等待下一个客户端的请求。如下图所示: 1.2 NIO原理 同步非阻塞式IO,有三部分组成,缓冲区,通道,多路复用器。在NI

By Ne0inhk
SpringCloud实战【一】:基础组件介绍

SpringCloud实战【一】:基础组件介绍

目录 1 为什么选择Spring Cloud 很多大公司都有自己的一套实现微服务的架构,比如:阿里的Dubbo/HSF、京东的JSF、新浪微博的Motan、当当网的DubboX等,每一种架构都有自己的优缺点、都经历了严格的考验存活到现在。但是当我们提到微服务首先会想到是Spring Cloud,就像我们提到电商首先想到淘宝,提到外卖首先想到美团一样。那么Spring Cloud到底有哪些优势能够在众多微服务架构中脱颖而出,成为佼佼者呢。 要回答这个问题我们先看一下Spring Cloud的组成,如下图所示: 可以看到SpringCloud项目是有多个独立的子项目集合而成的,每个子项目都实现了不同的功能,解决了不同的问题,实现一个实际的微服务架构,需要哪个组件直接引用过来使用就可以了,而像dubbo这些需要好几个中间件组合起来才能实现微服务架构,这一点就像品牌机和组合机的区别。以dubbo为例和SpringCloud对比: SpringCloudDubbo注册中心SpringCloud Eureka借助Zookeeper调用方式REST APIRPC降级/熔断SpringCl

By Ne0inhk
【团队管理】如何做好技术团队年终复盘

【团队管理】如何做好技术团队年终复盘

1 正确理解年终复盘 很多做技术的小伙伴都非常讨厌复盘,感觉复盘的时候没什么可说的,把自己说的太好好像是在自吹,说的不好又感觉憋屈,说别人的不足会不好意思,别人说自己的没做好的地方会感觉被攻击,一次糟糕的复盘会让一个团队下一阶段一直处于低迷的状态,但是一次成功的复盘会让一个低迷的团队荣光换发、斗志昂扬、充满激情。不只是技术团队需要复盘,各行各业都需要复盘,历史上有非常多比较有名的复盘。那么如何做好一次复盘,使整个团队朝着一个好的方向发展呢,其实并不难,尤其是技术团队的复盘是有迹可循的,我们需要把握好以下几个关键步骤: * 明确复盘目的:做任何事情都需要有一个目标,尤其是团队协作目标一定要统一,所谓众口难调,一千个读者有一千个哈姆雷特,我们无法统一人的思想,但是可以统一人的目标。做一次复盘,想要达到的效果一定要明确,复盘是为了团队更加的健壮,小伙伴能够更快的成长,有了这个大前提每个小伙伴才能全身心的投入进去。 * 制定复盘约定:所谓无规矩不成方圆,一个团队要共同完成一件事情,需要有共同的约定,复盘的时候如果没有合理的约定,很可能开成批斗会,开成夸夸会。 * 准备复盘材料:复盘期

By Ne0inhk