天天看点

[译]再谈如何安全地在 Android 中存储令牌

<b>本文讲的是[译]再谈如何安全地在 Android 中存储令牌,</b>

作为本文的序言,我想对读者做一个简短的声明。下面的引言对本文的后续内容而言十分重要。

没有绝对的安全。所谓的安全是指利用一系列措施的堆积和组合,来试图延缓必然发生的事情。

客户端应用与服务端的交互是最常见的场景之一。数据交换时的敏感度差别很大,并且登录请求、用户数据更改请求等之间交换的数据类型也变化多样。

当你使用 SSL 连接时(也就是当你看到浏览器上有一个小锁时),这意味着你与服务器之间的连接被加密了。理论上讲,没有什么能够访问到你请求里的信息(*)

(*)我说过绝对的安全不存在吧?SSL 连接仍然可以被攻破。本文不打算提供所有可能的攻击手段列表,只想让你了解几种攻击的可能性。比如,可以伪造 SSL 证书,或者进行中间人攻击。

我们继续。假设客户端正在通过加密的 SSL 通道与后台链接,它们在愉快的交换有用的数据,执行业务逻辑。但是我们还想提供一个额外的安全层。

接下来要采取的措施是在通信中使用授权令牌或 API 密钥。当后台收到一个请求时,我们如何判断该请求是来自认证的客户端而不是任意一个想要获取我们 API 数据的家伙?后台会检查该客户端是否提供了一个有效的 API 密钥。如果密钥有效,则执行请求操作,否则拒绝该请求并根据业务需求采取一些措施(当出现此情况时,我一般会纪录他们的 IP 地址和客户端 ID,看一下他们的访问频率。如果频率高于我的忍受范围,我会考虑禁止并观察一下这个无礼的家伙想要得到什么)。

让我们从头开始构建我们的城堡吧。在我们的应用中,添加一个叫做 API_KEY 的变量,该变量会自动注入到每次的请求(如果是 Android 应用,可能会是你的 Retrofit 客户端)中。

很好,这样可以帮助我们鉴定客户端。但问题在于它本身并没有提供一个十分有效的安全保证。

是的,我知道。这并不能保证是一个有效的令牌,所以我们仍然需要通过一个精确的验证来决定如何找到那个字符串,和它是否可以用来通过验证。但是你知道我要表达什么:这通常只是时间和资源的问题。

我将会更新我提出的初始模型,不断迭代它,以提供更安全的替代方案。我们假设有两个函数分别负责加密和解密数据:

代码没啥好说的。这两个函数会使用一个密钥值和一个被用来编/解码的字符串作为入参。它们会返回相应的加密或解密过的字符串。我们会用如下方式调用它们:

猜到为什么要这么做了吗?是的,我们可以根据需求来加/解密令牌。这就为我们提供了一个额外的安全层:当代码混淆后,寻找令牌不再像执行字符串搜索和检查字符串周围的环境那样简单了。但是,你能指出还有一个需要解决的问题吗?

找到了吗?

如果还没找到就多花点时间。

是的。我们仍然有一个加密密钥以字符串的形式存储。虽然这种隐晦的做法增加了更多的安全层,但不管这个令牌是用于加密或它本身就是一个令牌,我们仍然有一个以明文形式存在的令牌。

现在,我们将使用 NDK 来继续迭代我们的安全机制。

NDK 允许我们在 Android 代码中访问 C++ 代码库。首先我们来想一下要做什么。我们可以在一个 C++ 函数中存放 API 密钥或者敏感数据。该函数可以在之后的代码中调用,避免了在 Java 文件中存储字符串。这就提供了一个自动的保护机制来防止反编译技术。

C++ 函数如下:

在 Java 代码中调用它也很简单:

在加/解密函数中会这样调用:

此时我们生成 APK,混淆它,然后反编译并尝试在原生函数 getSecretKey() 中查找该字符串,无法找到!胜利了吗?

并没有!NDK 代码其实也可以被反汇编和检查。只是难度较高,需要更高级的工具和技术。虽然这样可以摆脱掉 95% 的脚本小子,但一个有充足资源和动机的团队让然可以拿到令牌。还记得这句话吗?

那么,我们要使用哪种方案来避免后台与客户端的通信被标记呢?

在设备上实时生成密钥。

你的设备不需要存储任何形式的密钥并处理各种保护字符串字面值的麻烦!这是在服务中用到的非常古老的技术,比如远程密钥验证。

客户端知道有个函数会返回一个密钥。

后台知道在客户端中实现的那个函数。

客户端通过该函数生成一个密钥,并发送到服务器上。

服务器验证密钥,并根据请求执行相应的操作。

抓到重点了吗?为什么不使用返回三个随机素数( 1~100 之间)之和的函数来代替返回一个字符串(很容易被识别)的原生函数呢?或者拿到当天的 UNIX 时间,然后给每一位数字加 1?通过设备的一些上下文相关信息(如正在使用的内存量)来提供一个更高程度的熵值?

上面这段包含了一些想法,希望读者们已经得到重点了。

绝对的安全是不存在的。

多种保护手段的结合是达到高安全度的关键。

不要在代码中存储字符串明文。

使用 NDK 来创建自生成的密钥。

还记得开头的那段话吧?

我想再强调一次,你的目标是尽可能的保护你的代码,同时不要忘记 100% 的安全是不可能的。但是,如果你能保证解密你代码中任意的敏感信息都需要耗费大量的资源,你就能安心睡觉啦。

愉快的编码吧!

<b></b>

<b>原文发布时间为:2017年6月08日</b>

<b>本文来自云栖社区合作伙伴掘金,了解相关信息可以关注掘金网站。</b>

继续阅读