安全保护Android应用程序敏感数据的最佳方法是什么?

79

是的,这是一个非常笼统的问题,但我想了解处理与向应用程序提供敏感数据的Web服务器接触的最佳方式。任何链接、一般信息、建议等都将不胜感激。

由于应用程序将存储从数据库检索的持久数据一段时间... 一切都变得有些棘手。


数据库不是存储敏感信息的好选择吗? - user849998
安全防护针对谁?您的应用程序的合法用户,手机盗窃者,病毒... - CodesInChaos
6个回答

113

在设备上存储敏感数据

这要取决于你的受众。通常,Android操作系统通过经过验证的Linux文件权限禁止应用程序访问彼此的文件(即数据库、首选项文件、存储在应用程序私有目录中的常规文件)。然而,在已获取root权限的设备上,应用程序可以获得root访问权限并读取所有内容。以下是需要考虑的几个问题:

  1. 如果你知道你的用户不会获取root权限(例如,如果你不是通过Android市场分发该应用程序,而只是在公司内部使用),那么你可以简单地依赖Android基于文件系统的安全性。
  2. 如果用户确实获得了root权限,他会非常谨慎地选择授予哪个应用程序这种特权。
  3. 如果一个应用程序获得了root权限,它可能会造成很多麻烦。你应用程序中的信息可能是用户最不必担心的问题之一。
  4. 获取root权限将导致零保修。包括在应用程序中。你不能对泄露根据手机信息承担责任。

总之,如果你的信息不是非常敏感(例如信用卡信息),我建议仅使用Android提供的默认安全性(即保存所有内容以纯文本形式,知道其他应用程序无法访问它)。

否则,加密是最好的方法。它不是100%安全的(黑客可能会反编译你的应用程序并找出如何解密数据),但很难被破解,并且会阻止大多数黑客。特别是如果你使用像ProGuard这样的代码混淆工具。


从服务器传输敏感数据到设备

你有几个选择。首先,始终使用HTTPS。启用HTTPS后,以下是我建议采取的两种额外的安全措施:

  1. 使用API密钥系统。将此API密钥包含在所有请求中,并在发送任何响应之前在服务器端检查它。请记住,由于您正在使用HTTPS,攻击者将无法仅使用网络嗅探器找到您的API密钥。但是,如果有人反编译您的应用程序,这很容易找出,这就是为什么您可以进一步混淆它(除了使用ProGuard)。例如,您可以将API密钥分解成各个代码部分(例如作为两个或三个类中的静态成员)。然后,当您发送请求时,只需连接所有这些部分即可。您甚至可以应用某种其他变换(例如位移),以使其更难从反编译的代码中找出。
  2. 您可以在每次发送请求时生成一个密钥。该密钥将由您知道的一些逻辑生成,以便您可以在客户端和服务器端实现它。例如,请求可能包括以下参数:
    time = 1321802432&key = [generated-key]
    其中generated-key time 参数生成。例如: md5(time + salt)。当服务器收到此请求时,它可以执行以下两个操作:
    1. 检查 key 是否确实等于 md5(time + salt)(请注意,只有客户端和服务器知道salt,它可以类似地混淆API密钥以上),以及
    2. 检查 time 是否不太过去(例如,如果它比过去的1-2分钟更长,则将请求视为无效)。

如果您还要进行普通HTTP请求,则第二种方法更有用,因为每个人都可以看到被发送的参数。此外,从反编译的代码中找出它要困难得多。特别是如果您在多个类中分散密钥计算逻辑。

然而,请注意没有什么事情是完全不可能破解你的应用程序的。 无论您多么混淆,如果黑客真的决心获取您的数据,他将通过反编译应用程序并花费许多不眠之夜浏览您的代码并弄清楚请求如何形成来实现此目的。保护数据的唯一真正方法是要求用户提供密码,在执行我上面写的所有工作之外。您无法从反编译的代码中获得仅存在于某人(用户)头脑中的密码:)。


关于Proguard的信息绝对是有益的...一定要仔细阅读文档。 - DJPlayer
最初我只谈论如何在设备上安全存储数据。我现在编辑了我的回答,也讨论了如何将数据从服务器安全传输到设备。看一下 :) - Felix
总的来说,这看起来与我考虑的差不多。肯定会使用MD5单向哈希算法。最终,我们想要访问众多内部基于Web的应用程序,并使用相同的凭据进行身份验证。令牌使用会很好...这将允许用户在登录后访问多个区域,并允许一个良好的默认超时时间。不幸的是,为了开始,我们可能需要从一些更简单的东西开始。这可能是一个很好的开始,我们不希望用户每次平板电脑暂停时都必须输入密码。谢谢。 - DJPlayer
@Felix 为什么不使用已经发明的 Oauth 呢? - GoRoS
在这种情况下,您将如何避免中间人攻击?如果我们仅依赖HTTPS,那么应用程序接收到的HTTPS证书不是可能由中间人发行的吗? - Mathew Paret
显示剩余4条评论

15

(通过谷歌搜索来到这里)

我最近一直在研究这个问题,而且通过谷歌和必应搜索很多次都跳转到了这个页面。目前存储设备上数据的广泛接受方式是使用强加密算法如AES进行安全保护。但是更困难的问题是,“AES需要一个安全密钥,你该如何处理这个密钥?”

谷歌最近宣布了一种面向应用程序的云存储解决方案,因此如果情况允许,您可以考虑将密钥存储在那里。否则,好像将密钥从设备外部获取(例如在服务器上)会更好。如果可以让用户输入PIN码,那么这实际上是最好的方法。可以使用密码推导技术来存储密码,并可以重新进行推导以验证密码。

如果没有“用户输入PIN码”这部分,我没有找到太多好的答案。但是,如果您必须在应用程序中存储密钥,请不要硬编码密钥。至少,要使用安全的密码生成器和/或类似PBKDF2(基于密码的推导函数2)的推导函数来生成密钥。

如果我正确阅读了帖子,谷歌确实说过一种方法是在应用程序第一次启动时生成一个密钥,通过MODE_PRIVATE标志将密钥存储到许多文件I/O操作中,并将其用作密钥。您还可以基于该主密钥派生其他密钥,NIST实际上建议采用这种方法之类的东西。

是否信任主密钥方法,我留给您自己判断。这个密钥将在root设备上暴露出来。我也承认我仍在研究这个问题。


现在有进展了吗? - Mike Yang

1
每个Android应用程序都在安全的沙盒环境中运行,因此系统上的其他进程在没有适当握手的情况下无法访问您的代码或私有数据。但是,如果应用程序设计不良,则仍然可能存在许多漏洞。来自Android开发者网站的此链接为您提供一些安全提示-https://developer.android.com/training/articles/security-tips.html

1
public String androidId = "";
private String INIT_VECTOR = "encryptionIntVec";

public String encrypt(String value) {
    try {
        IvParameterSpec iv = new IvParameterSpec(INIT_VECTOR.getBytes("UTF-8"));
        SecretKeySpec skeySpec = new SecretKeySpec(androidId.getBytes("UTF-8"), "AES");

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
        cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);

        byte[] encrypted = cipher.doFinal(value.getBytes());
        return Base64.encodeToString(encrypted, Base64.DEFAULT);
    } catch (Exception ex) {
        ex.printStackTrace();
    }
    return null;
}

public String decrypt(String value) {
    try {
        IvParameterSpec iv = new IvParameterSpec(INIT_VECTOR.getBytes("UTF-8"));
        SecretKeySpec skeySpec = new SecretKeySpec(androidId.getBytes("UTF-8"), "AES");

        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
        cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
        byte[] original = cipher.doFinal(Base64.decode(value, Base64.DEFAULT));

        return new String(original);
    } catch (Exception ex) {
        ex.printStackTrace();
    }

    return null;
}

public void saveSecureData(String sdt) {
    SharedPreferences settings = getApplicationContext().getSharedPreferences("TableName", 0);
    SharedPreferences.Editor editor = settings.edit();
    String encryptedString = encrypt(sdt);
    editor.putString("FieldName", encryptedString.toString());
    editor.apply();
}
public String readSecureData() {
    SharedPreferences settings = getApplicationContext().getSharedPreferences("TableName", 0);
    String homeScore = settings.getString("FieldName", "DEFAULT");
    return homeScore;
}

public void testUnit() {

    androidId = Settings.Secure.getString(getContentResolver(), Settings.Secure.ANDROID_ID);
    String lastRecord = readSecureData();

    if (lastRecord.equals("DEFAULT")) {

        saveSecureData("Data to save...");


    } else {


        String decryptedString = decrypt(lastRecord);
        Toast.makeText(MainActivity.this, lastRecord + " > " + decryptedString, Toast.LENGTH_LONG).show();


    }

}
  • testUnit():用于测试代码的示例函数。
  • androidId:设备唯一标识,将在testUnit函数中设置。

androidId是一个随机生成的唯一代码,在设备首次启动时仅生成一次...

  • encrypt():将输入字符串使用androidId的密钥进行加密,并将其作为新字符串返回。
  • saveSecureData():将输入字符串进行encrypt加密,并保存在Android共享数据库中。
  • readSecureData():从Android共享数据库中以字符串形式返回加密数据。
  • decrypt():根据androidId的密钥,将加密的输入字符串转换为原始格式,并返回新字符串。

1
你只是把问题转移了。如何保护androidId的安全? - hldev
@hldev,你说得没错!但这只是一个基本的解决方案,你必须根据自己的情况进行调整和开发...例如,你可以将androidid与trackid或cookies或firebaseid结合起来使用...一些只有你知道的解决方案。 - Eyni Kave
请问INIT_VECTOR是什么意思? - Ahmed Elsayed
你的意思是这个算法有一个依赖关系吗?因为它给我返回了“无法解析符号'INIT_VECTOR'”。 - Ahmed Elsayed
好的,谢谢。我会去做的。 - Ahmed Elsayed
显示剩余7条评论

0

如果您想确保用户除了通过查看您的应用程序外无法看到数据,那么加密确实是唯一的方法。即使是“受保护”的存储也可以在设备被root的情况下被用户访问。即使加密也不是完全安全的,因为您需要在某个时候解密数据以便显示它。您将会阻止普通浏览者,但无法阻止决心强烈的黑客。


这些设备是公司所有的,因此我们可以通过移动设备管理器在相当大的程度上控制这些设备。对于其他设备,我们计划使用令牌,在通过类似于siteminer的东西后在活动服务器上进行身份验证。不幸的是,钥匙串API只是4.0的一部分。可能会使用单向哈希算法来验证用户。 - DJPlayer

0

使用HTTPS上的SSL来传输数据,而不是HTTP,您需要在Web服务器上设置证书,但不太确定它是如何工作的。

如果您真的关心数据安全,则可以在发送之前使用独特的算法进一步加密它,并在到达应用程序时进行解密。我想这就是全部了...除非您需要更强大的保护,那么可以基于TCP开发自己的协议和/或使用另一个端口...也许这会有所帮助。

http://en.wikipedia.org/wiki/Secure_Sockets_Layer http://developer.android.com/reference/javax/net/ssl/package-summary.html http://blog.synyx.de/2010/06/android-and-self-signed-ssl-certificates/

关于在应用程序中存储数据,您可以在存储之前加密数据,或者使用其他格式而不是SQLite以获得更好的安全性,因为您可以很容易地使用浏览器查看SQLite数据库。
除非手机已经被root,否则不应该有从中提取数据的方法。

2
我不同意“编写自己的安全协议”的理念。你可能会意外地引入旧的、众所周知的安全漏洞,而这些漏洞没有得到任何社区的支持(因为它必须是秘密的)。此外,在Android的官方文档中明确写道,这不是一个好方法:“如果您需要一个安全通道,请考虑使用HttpsURLConnection或SSLSocket而不是编写自己的协议。”来源:https://developer.android.com/training/articles/security-tips.html - Thecave3
我指的是仍然使用TCP来开发自己的协议,并使用已知的加密算法来加密有效载荷。许多具有持久连接的游戏都这样做,以减少在http / https中引入的有效载荷,并使用户更难猜测所使用的加密算法。我并不要求用户在原始套接字上开发全新的协议。 - Rejinderi
这不就是SSLSocket的目的吗?将原始TCP套接字包装成TLS。 - hldev

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