为什么说在移动App中使用OAuth API密钥是不安全的?

对于移动应用程序来说,使用密钥来访问后端API服务来获取数据是非常常见的。那么,如何将密钥安全地包含在移动应用程序中呢?往短了说,你做不到。往长了说,请看这篇文章的剩余部分。

\"\"

保存在本地和移动应用程序中的密钥被提取

从JavaScript应用程序中提取API密钥非常简单。只要你加载包含JavaScript应用程序的网页,浏览器就会下载整个源代码。你所要做的就是单击“查看源代码”,就可以看到整个源代码(包括API密钥)。JavaScript代码通常会被压缩或缩小,看懂源代码可能并没有那么容易,但应用程序中定义的所有字符串都是可见的。

对于原生移动应用程序来说也是一样。这些应用程序在运行之前也会被下载到设备上,只是你下载的是二进制文件,而不是未编译的源代码。

让我们来做一个有趣的快速演示。如果你使用的是Mac,并且安装了1Password,请运行下面这个命令。你可以针对Mac上的任意一个二进制文件运行这个命令,但1Password应用程序中恰好有一些非常容易读懂的数据。

strings /Applications/1Password\\ 7.app/Contents/MacOS/1Password\\ 7 | grep 1Password

strings命令将会显示二进制文件的所有嵌入字符串,然后我们使用grep查找与1Password匹配的字符串,结果得到了在应用程序中使用的一堆文本!

...Restarting 1Password...1Password failed to connect to 1PasswordPlease quit 1Password and start it again.Add Account to 1PasswordAn update to Safari is required in order to use the 1Password extension.Welcome to 1Password!...

如果应用程序中嵌入了API密钥,strings命令也会将它们显示出来。

让我们从头开始写一个简单的程序来证明这一点。我们将编写一个输出“hello world”的C语言程序。首先,创建一个叫作hello.c的文件,并写入以下内容:

#include \u0026lt;stdio.h\u0026gt;char hello[] = \u0026quot;hello\u0026quot;;char world[] = \u0026quot;world\u0026quot;;int main(){  printf(\u0026quot;%s %s\u0026quot;, hello, world);  return 0;}

在这段代码中,我们声明一个叫作example的字符串,值为“hello world”,然后在程序运行时打印它。你可以使用任何一个C语言编译器(如gcc)编译它:

$ gcc hello.c

结果会得到一个叫作a.out的二进制文件,然后你可以运行它:

$ ./a.outhello world

让我们针对这个二进制文件运行strings命令:

$ strings a.out%s %shelloworld

它很容易就能发现二进制文件中的文本。

现在你可能会想:“如果我将API密钥分成几个部分并将它们分散在代码中会怎样?”这可能会为你赢得一些时间,但仍然会被一个真正有决心的人找出来。

即使是Twitter也无法阻止这种情况发生!2013年,Twitter官方应用程序的API密钥就是通过这种方式泄露的,让攻击者可以冒充合法的Twitter应用程序。

关键问题在于,一旦你将包含应用程序需要使用的字符串的内容发送给用户,总有人会提取它们。解决这个问题唯一的方法是使用“硬件安全模块”,秘钥被存储在微处理器中,微处理器无法通过编程的方式提取任密钥,它可以对数据进行加密签名,而不是发送密钥本身。

总的来说,如果你以未编译或二进制形式向用户发送代码,他们就有可能看到其中的内容。

HTTPS请求在移动应用程序端被拦截

即使你认为你使用了最厉害的混淆技术,并且确信没有人能够从应用程序二进制文件中提取密钥,但总有人可以通过另一种方式找到密钥。

与运行在数据中心服务器上的应用程序不同,移动应用程序运行在用户手中的设备上,经过各种网络。用户可能是通过自家的网络安装应用程序,然后连接到咖啡店的网络并打开它,又或者在通过身份认证之前连接到酒店的网络。这些网络都是不可信任的,并且很有可能会出问题,或者攻击者会试图拦截数据!

你可能会想:“HTTPS会保护传输中的数据!”在正常情况下确实如此。只要应用程序在发出请求时正确验证HTTPS证书,处于手机和服务器之间的攻击者几乎不可能看到流量。

但是,我们担心的不是这个问题。如果有人愿意,他们可以为你的API URL提供自己的HTTPS证书,在请求离开手机之前拦截自己的HTTPS连接。网上有很多教程教你如何做到这一点!实际上,这一项很好的技术,在开发应用程序时,可以用它来测试自己的应用程序,也是人们对Instagram等私有API进行反向工程的常见方式。

如果你有兴趣尝试一下,可以看看Charles Proxy(https://www.charlesproxy.com/)或者免费的mitmproxy(https://mitmproxy.org/)。

在手机上安装了自己的证书授权程序后,它就可以为任何一个域颁发HTTPS证书,对于你的手机来说,一切看起来都很正常。只是你的手机实际上是在向运行在笔记本电脑上的软件发出HTTPS请求,然后你的笔记本电脑再向真实的API发起新的HTTPS请求。这样你的笔记本电脑就可以看到手机发送给API的所有内容。

当然,攻击者不会通过这种方式随机拦截用户的传输数据,但如果有人想要知道应用程序使用的密钥,他们就可以通过这种方式轻松查看应用程序通过网络发送的所有数据。这意味着尽管你尽最大努力在源代码中隐藏应用程序密钥,仍然会在通过网络传输时被拦截。

这与OAuth有什么关系?

我们已经看到了从移动应用中提取API密钥的两种方法,但这与OAuth有什么关系呢?

传统上,OAuth 2.0应用程序在开发人员注册应用程序时会发出client_id和client_secret。当应用程序在Web服务器上运行时,这没问题,因为应用程序用户永远无法访问源代码,因此无法查看密钥。但是,当我们在JavaScript或原生应用程序中使用OAuth 2.0时,显然会有问题,因为正如我们所看到的那样,它们没有保密的能力。

在OAuth 1中,每个API请求都需要使用一个密钥,这是它的主要缺点之一,也是它被OAuth 2.0取代的主要原因。OAuth 1是在移动应用程序开始流行之前出现的,所以它并没有考虑到这些限制。

随着OAuth 2.0的出现,这种情况发生了变化,特别是在引入PKCE(证明密钥交换)扩展之后。我喜欢把PKCE看作是一个“动态”的客户端密钥。PKCE没有采用向移动应用程序传递client_secret的方式,而是在每次启动OAuth请求流时,应用程序都会创建一个新的随机密钥。这样就不存在需要提前传递的密钥,攻击者也没有什么东西可窃取。

OAuth仍然会通过网络发送访问令牌,如果使用了mitmproxy之类的东西,那么它们对你仍然是可见的,但不同的是,这个令牌是动态发出的,并且特定于使用它的用户!这样一来,源代码中就没有密钥了,如果有人想从他们自己的设备上拦截流量,他们看到的只是一个访问令牌!他们无法访问应用程序已经无法访问的东西。

如何保护移动应用中的密钥

希望你现在已经了解为什么在移动应用程序中发布API密钥或其他密钥是不安全的。那么你会怎么做呢?

OAuth解决了这个问题,它没有向移动应用程序传递任何密钥,而是让用户参与获取应用程序访问令牌的过程。这些访问令牌在用户每次登录时都是唯一的。PKCE扩展提供了在移动应用程序上安全执行OAuth请求流的解决方案,即使没有使用预先准备的密钥。

如果你需要从移动应用程序中访问API,但愿可以支持OAuth和PKCE!幸运的是,有关PKCE的大部分繁琐的任务都是由像AppAuth这样的SDK来处理的,所以你不需要自己编写所有的代码。如果你使用的是像Okta这样的API,那么Okta的SDK会自动执行PKCE,你完全不需要操心。

英文原文:https://developer.okta.com/blog/2019/01/22/oauth-api-keys-arent-safe-in-mobile-apps

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值