DEV Community

Ting
Ting

Posted on

保护 API Key 和其他应用密钥

尝试英译中机翻我们总结的 API Key 相关经验文档… 尽量说人话 😊

情景

假设我们要发布一个基于 ArcGIS Maps SDK 的 app,并使用 API Key 来授权各种地图服务。API Key 的费用取决于接口调用的使用量。正常用户越多,产生的用量就越大,成本也就越高。没毛病——我们当然希望有更多用户。然而,假设某个黑客(bad actor)决定窃取我们的密钥,如果他能够利用某个漏洞获取这个 API Key,那么他的使用量最终也会算在我们的账单上。

不应该直接把密钥硬编码(hard-code)进 app 里,主要有两个原因:

  1. API Key 可能会被黑客从二进制文件中被提取出来
  2. 当需要更换 API Key 时,不用非得重新发一个新版本

API Key 会过期,但一般有效期比较长。假如我们创建密钥的时候设置了一年到期,那么黑客就可以白嫖将近一年。

API Key 过期又带来了第二个问题。如果 API Key 被硬编码在应用中,那么过期后 app 也会停止工作。这会造成两个后果:

  1. 我们必须在 API Key 失效之前重新发布应用
  2. 所有用户都必须马上升级到新版,否则 app 不能用

简而言之,硬编码密钥的弊端:

  • 如果被盗白嫖,会造成额外开销
  • 一旦过期,app 就不能用了

弱点分析

  1. 硬编码在二进制文件中的密钥

如果在代码中直接硬编码一个字符串,那么这个字符串最终会被编译进应用的二进制文件中。一旦黑客获得了没混淆的二进制文件,其中所有的字符串字面量(string literals)都可以轻松读取。

App Store 上的 app/ipa 可以下载,然后 dump 里面的字符串。我试过用 Apple Configurator 之类的工具下载已经发布的应用,并通过这种方式提取出了其中的 API Key。尽管二进制文件包含大量的字符串,但如果攻击者知道自己要找的字符串的特征,那么找到 API Key 往往并不困难。

  1. 通过网络传输的密钥

即使我们设法保护了二进制文件中的密钥,或者应用通过其他方式获得了密钥——例如从一个 Web 服务安全地下载密钥——仍然存在另一个疏漏。那就是:一旦我们真正开始使用这个 API Key,它就必须通过网络传输。

通常情况下,数据会通过 HTTPS/TLS 进行传输。这通常意味着我们拥有网络安全中的 C.I.A. 三要素:

  • Confidentiality(机密性):只有预期的接收者才能看到发送的数据
  • Integrity(完整性):数据在传输过程中没有被修改
  • Authentication(身份认证):消息被预期的接收者接收

对于用户密钥(user secrets)而言,这种机制很有效。

但在本文讨论的场景中,情况恰恰相反:开发者希望保护密钥,不让黑客获取;问题关键在于,“用户”自己就是那个黑客。用户可以安装类似 Proxyman 和 Charles 的代理工具,并安装代理工具提供的证书,从而使所有进出设备的流量都可以被代理工具解密。这种攻击被称为 “中间人攻击”(Man-in-the-Middle Attack,MitM)。

这意味着 API Key 可能会在传输过程中被泄露。

降低风险

由于上面介绍的两种问题,长期有效的密钥很难做到绝对安全。可以通过一些方法来降低风险。但是打的补丁越多,开发者的工作量也会增加。

按照弱点的类型,分别介绍对策。

二进制文件中的密钥

混淆(Obfuscation)

如果选择将密钥直接嵌入应用或代码中,那么应用发布之后,我们无法彻底阻止黑客找到这个字符串。不过,我们可以对它进行混淆。通过混淆,可以让密钥难以直接被搜到。黑客需要一定的技术水平,才能反编译应用逻辑,弄清楚字符串是如何被混淆的,然后再将其还原。

这种方法无法彻底保护密钥,但可以显著增加获取密钥的难度。

目前有一些可用于混淆的开源库。例如,对于 Swift 应用,可以使用 swift-confidential

从服务器下载密钥

更好的方案是:别把密钥硬编码在代码里。开发者工作量会增加一些,但其安全优势对比硬编码来说相当明显。

问题来了:如何保证只有自己的应用才能访问存密钥的服务器?

一种方式是让用户登录某个服务器,然后通过用户身份认证来控制访问权限。这意味着开发者需要在应用中建立用户体系;同时搭建一个能够处理身份认证的服务器。很多应用不需要用户体系,或者搭建鉴权框架过于复杂,所以这个方案并不适合所有应用。

另一种方法是使用 Apple 的 App Attest Framework 或 Google 的 Integrity API。这是一种可以通过密码学手段,确保只有合法且未经修改的应用版本才能访问服务的技术。

从易用性和安全地向应用获取 API Key 的角度来看,这是一个非常不错的方案:用户不需要进行任何操作,所有过程都在后台自动完成。

当然,这要求我们作为开发者:

  • 在应用端编写代码来完成 attestation(应用真实性证明)
  • 在服务器端编写代码来验证这个 attestation

此外,还有一些第三方库和框架可以替开发者完成 Apple App Attest 或 Google Integrity API 的大量工作。其中一个例子就是 Google 的 Firebase App Check

这是一个不错的选择,因为搭建一个项目以及一个受到 App Check 保护的 Firebase 云函数步骤相对简单。

这种方案安全性很高,但也存在一些缺点:

  • API Key 过期后,需要在服务端更新
  • 需要支付服务费用
  • 如果不使用 Firebase 这样的第三方服务,而是自己编写 attestation 逻辑,那么整个实现过程会比较复杂

当我们已经安全地获得了密钥,应用也可以使用之后,我们仍然需要解决另一个问题:密钥使用时,依然需要通过网络传输。

通过网络传输的密钥

TLS Pinning

防止中间人攻击的一种方法,是使用 TLS Pinning(也称 SSL pinning 或者 Certificate Pinning)。

这种技术要求我们对应用进行配置,使其只与指定的目标域名通信。

具体来说,我们需要在应用代码中“固定”(pin)目标服务器的证书;或者更准确地讲,固定该证书对应的公钥(public key)。因为证书已经被固定,代理服务器或中间人就没办法解密网络流量。如果中间人试图篡改流量,那么发送到被固定域名的请求也会直接失败。

从密码学角度来看,这是一个安全性很高的方法。

不过,这种方案也存在一些缺点,主要集中在应用的开发维护方面:

  • 如果服务器证书轮换(certificate rotation)或过期后导致公钥发生变化,那么应用必须发一个新版,更新里面的证书后才能继续正常工作
  • 如果我们希望保护多个子域名,就还需要知道这些子域名所使用的全部公钥
  • 如果黑客水平足够高,把应用给逆向工程了,他也还是可能绕过 TLS Pinning。当然,这种可能性很小啦

这和处理过期 API Key 时面临的问题非常类似:产生变化之后要发新版,而用户则需要升级到最新版本才能用。另外,对于不属于我们的域名和服务器,好比 arcgis.com,维护的麻烦程度进一步增加。

由于我们的应用通常需要将 API Key 传输给一个不属于我们的服务器,例如 arcgis.com,因此 TLS Pinning 带来的维护成本往往超过了它所提供的安全收益。有没有一种更好的办法,可以同时避免上面提到的两种漏洞?如果不把密钥硬编码进应用,同时我们也不需要通过网络传输一个长期有效的密钥,该怎么办?

更好的方案

ArcGIS OAuth 2.0 Application Credential

OAuth 2.0 Application Credential 是一种短期有效的 Token(令牌)。生成这个 Token 需要两项信息:

  • Client ID(客户端 ID)
  • Client Secret(客户端密钥)

有了这两项信息,我们就可以调用一个服务,动态生成 Token。我们仍然必须保护 Client ID 和 Client Secret,不能让黑客获取。因此,不能硬编码进代码里。

虽然我们仍然不希望真正的 Token 落入黑客手中,但 Token 是短期有效的,因此即使 Token 被盗,攻击者能够利用它的时间也是有限的。

Firebase App Check

借助 Firebase 和 Firebase App Check,我们可以创建一个 Firebase Cloud Function,让它负责为我们生成短期有效的 Token,也就是 ArcGIS OAuth 2.0 Application Credential。这个 Token 生成服务可以受到 App Check 的保护,从而确保只有我们的应用才能访问。

Client ID 和 Client Secret 都存储在服务器上,从来不会在应用与服务器之间通过网络传输。这意味着我们的密钥可以免受 MitM 攻击。

整个方案中:

  • 密钥没有嵌入应用;
  • 密钥不会通过网络被嗅探劫持
  • 应用只获取短期有效的 Token

有什么缺点?

  • 和前面介绍 App Check 时一样,需要花钱买这些 Firebase 之类的服务
  • 此外,虽然长期密钥得到了保护,但短期 Token 本身仍然有可能通过 MitM 被嗅探。不过正如前面所说,这个 Token 的有效期很短,因此攻击者能够利用它的时间也是有限的

未来方向

理想情况下,我们希望 ArcGIS Online / Enterprise 能够提供一种 App Verification Credential。它的使用方式可以类似于目前创建 OAuth 2.0 Application Credential 的方式。这种新的 Credential 类型可以配置各种权限。在创建 Credential 时,需要提供 App Attest 或 Google Integrity 所需的信息。例如,对于 iOS App Attest,需要提供:

  • Team ID
  • Bundle ID

然后,系统可以提供一个 Endpoint ,用于生成一个短期有效的 Token,并且这个 Endpoint 只能由提供了 ID 的应用调用。

这样一来,在 Native Maps SDK 中,我们就可以提供一个 AppVerificationCredential,并将 App Attest 或 Google Integrity 的客户端实现逻辑全部内置进去。这将是一个非常理想的方案。

它可以提供:

  • 短期有效的、基于使用量的 API Token
  • 短期有效的、基于会话的 Session Token

开发者不需要自己托管服务器,同时整个方案由 Apple 和 Google 的密码学技术提供安全保障,也不需要开发者自己编写复杂的客户端代码。

从实现所需要的工作量来看,服务端逻辑确实比较复杂,但绝非无法完成。与其让每一个用户都自己编写这些代码,不如由我们统一实现。

相关链接

Top comments (0)