保护可从Android访问的REST API

39
我们正在为Android构建一款游戏,需要访问Web服务 - 我们在自己的服务器上编写了一个用PHP编写的RESTful API。 API提供的功能包括:创建用户、登录、下载游戏、检索游戏列表、提交分数...等。现在我在思考,如果有经验的用户获取了API的URL格式,他们将能够以多种方式破坏系统:
  • 创建脚本并运行它来创建自动用户 - 我认为可以通过验证码或类似的方式来防止它。但是,验证码会令游戏玩家感到烦恼。
  • 恶意用户使用浏览器登录,下载游戏,然后按照他的意愿提交分数 - 通过在浏览器中直接调用API来实现。我假设恶意用户通过嗅探应用程序进行HTTP请求时知道了API的URL。
  • 我需要确保请求仅从安装了游戏的Android设备发出。(该游戏将是免费的)
现在,我该如何防止这种滥用?
5个回答

45

我认为你永远无法隐藏应用程序调用的URL(如果我运行的是root-ed Android手机,我应该能够监视所有网络流量)。

但是,你真正的问题是需要以某种方式验证你的API。

一种方法是实现OAUTH,但也许这会过度。

如果你想要一个简单的机制,怎么样:

  1. 创建一个秘钥
  2. 构建API请求(例如https://my.example.com/users/23?fields=name,email
  3. 对这个请求路径进行哈希运算+加上你的秘钥(例如md5(url+secret_key) == "a3c2fe167")
  4. 将此哈希添加到请求中(现在是https://.....?fields=name,email&hash=a3c2fe167
  5. 在API端,执行相同的转换(删除哈希参数)
  6. 检查URL和秘钥的MD5

只要秘密保持秘密,就没有人可以伪造你的请求。

示例(伪代码):

Android端:

SECRET_KEY = "abc123"

def call_api_with_secret(url, params)
  # create the hash to sign the request
  hash = MD5.hash(SECRET_KEY, url, params)

  # call the api with the added hash
  call_api(url+"&hash=#{hash}", params)
end

服务器端:

SECRET_KEY = "abc123"

def receive_from_api(url, params)
  # retrieve the hash
  url_without_hash, received_hash = retrieve_and_remove_hash(url)

  # check the hash
  expected_hash = MD5.hash(SECRET_KEY, url_without_hash, params)

  if (expected_hash != received_hash)
    raise our exception!
  end

  # now do the usual stuff
end

1
非常感谢,@matthew-rudy :) 我是否需要按每个用户创建一个“秘密密钥”?如果是这样,我该如何分发它?如果我将秘密密钥作为响应发送给应用程序进行注册,那么任何人都可以通过其桌面浏览器进行API调用,并获取秘密密钥作为响应。 - Shafiul
11
在马修的方法中,密钥从未通过网络发送。它是一个“盐”,附加到URL上创建哈希值。由于哈希是单向函数,用户无法从中恢复密钥。这将需要用户反编译APK并查找源代码以找到密钥。您可以使用代码混淆技术使此过程更加困难,但是只要有足够的时间和动力,某人总能找到密钥。 - Jason LeBrun
3
确实@JasonLeBrun,我已经添加了一个伪代码示例。但是giga,我建议你寻找现有的PHP框架,而不是创建自己的框架。我相信一定有很多现有的解决方案。 - Matthew Rudy
1
很好的回答。我是新来的,有一个问题。因为你说“如果我正在运行root-ed Android手机,我应该能够监视所有网络流量”。所以我在网络上发送的任何哈希都可以作为URL的一部分找到。因此,任何人一旦找到它就可以请求这个URL。因此,任何人都可以使用https://.....?fields=name,email&hash=a3c2fe167请求我的服务器。请建议。 - Suyash Dixit
@SudhirN 是的,在 API 调用中包含时间戳作为一些不显眼的标头。如果 POST 操作有一些 JSON 主体,我们也可以将其包含在要进行哈希处理的字符串中。但是,如果您使它过于复杂,可能会影响游戏性能。 - Daniel Wu
显示剩余8条评论

11
在这里提出的解决方案被称为安全通过混淆。基本上,他们试图掩盖协议并隐藏实现。这可能有效,直到有足够能力的人拆开应用程序并反向工程协议。黑客非常擅长这样做。
问题是你的应用程序是否值得破解?像iTunes、DVD或Sony PS3网络这样的计划显然值得一试。如果没有人有能力破解,则混淆方法可能有效。只是不要自欺欺人认为它是不可行的。
由于您不能信任设备或应用程序,因此必须信任用户。为了信任用户,您需要用户身份验证和授权系统。基本上是登录到您的应用程序。而不是自己滚动识别系统(使用确认电子邮件等登录),请使用第三方系统:OpenID(Google帐户)或OAuth(Facebook、Twitter)。在Facebook的情况下,请使用服务器端身份验证方案。
我会做什么:
  1. 允许用户自由玩游戏,直到他们想要在服务器上“保存”结果。
  2. 在保存结果之前,让他们通过上述方法登录。
  3. 使用HTTPS将数据发送到服务器。从可信CA购买ssl证书,这样您就不必处理自签名证书。

3
您提到过用户伪造高分。即使您的用户已经通过身份验证,这种情况仍然可能发生。当游戏上传高分时,您可以让它同时上传得分证明。例如:得分20100,消灭了103只虫子,飞行了1200英里,达到了3级,并且吃了2个樱桃。这并不完美,但可以解决一些低级问题。
首先,您需要有经过身份验证的用户。用户id/密码/会话令牌等,请查找一些现有的框架。一旦您进行了用户身份验证,请确保您可以使用TLS或类似的方法进行安全验证。
据我所知,服务器无法确定请求是否来自您的应用程序(所有请求都只是数据包中的位),但至少可以让恶意行为变得更加困难。
以下是一些建议:
1.在应用程序中构建一个密钥(如其他回答中所建议的密钥、哈希盐等)。 2.在首次安装应用程序后生成唯一ID,并跟踪与登录用户相关的详细信息。关于此和设备的唯一ID(为什么不使用它)的详细信息可以在Android博客上找到。 3.本文How to ensure/determine that a post is coming from an specific application running on an iPhone/iTouch?中讨论了一些想法。 4.检查用户代理。

感谢您的评论。将总分拆分成部分听起来不那么有害。 - Shafiul
@giga,这不仅仅是分割的问题。您希望他们提交得分以及证明所提交的得分是由知道得分工作方式的应用程序生成的(您的应用程序将知道此信息)。如果您只是分割它,那么恶意用户仍然可以提交任意值。您需要强制他们必须反向工程化您的应用程序,而不仅仅是窥视通信。 - goldsz

2
如果您真的想要保护连接,那么您必须使用公钥加密技术,例如RSA。设备将使用公钥加密登录信息,在服务器端您需要使用私钥进行解密。登录后,服务器将发送一个令牌/加密密钥(响应将是加密的JSON或其他内容),设备将存储该令牌。从那时起,只要会话未过期,设备将使用该令牌加密所有信息。对于这些请求,您不应使用RSA,因为这将需要更长的时间。您可以使用AES256(一种常用的私钥加密)与从服务器接收到的加密密钥来加密您的请求。
为了简单起见,如果您没有发送付款信息,可以完全放弃RSA,使用私钥完成所有操作。步骤应如下:
- 使用私钥加密每个传出请求。 - 将加密字符串转换为base 64字符串。 - 对base 64编码的字符串进行URL编码。 - 发送它。
在服务器端
- 进行base 64解码 - 使用私钥进行解密。
您的请求应携带签名(例如附加为盐的加密密钥),以便在解密后识别它。如果未提供签名,请简单丢弃该请求。
对于发送响应,请执行相同操作。
Android SDK应具有使用AES256和Base 64编码加密的方法。

那样会比HTTPS更好吗? - Peter Knego
我从未说过这是必须的。如果您没有HTTPS证书,这只是一个解决方法。 - rushafi
如果您不想从CA购买证书,可以使用自签名证书来实现HTTPS。您还提出了一个私钥解决方案,因此在技术上这是相同的,只是更难实施。 - Peter Knego
我在iOS设备中实现了它,对我来说并不太难。你不必编写AES加解密代码,因为有免费的可用代码。据我所知,Android SDK具有本地功能。无论如何,保护连接的多种解决方案都是可接受的。 - rushafi
在我的世界里,最小代价(=最小努力)是可以接受的。 - Peter Knego

1

按照Android团队的这些指南,通过使用Google API提供的Oauth令牌来保护您的后端。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接