433

大多数应用程序开发人员会将一些第三方库集成到他们的应用程序中。如果是访问服务,例如 Dropbox 或 YouTube,或者记录崩溃。第三方图书馆和服务的数量是惊人的。大多数这些库和服务都是通过以某种方式与服务进行身份验证来集成的,大多数情况下,这是通过 API 密钥发生的。出于安全目的,服务通常会生成公共和私有密钥,通常也称为秘密密钥。不幸的是,为了连接到服务,必须使用此私钥进行身份验证,因此可能是应用程序的一部分。不用说,这面临着巨大的安全问题。可以在几分钟内从 APK 中提取公共和私有 API 密钥,并且可以轻松实现自动化。

假设我有类似的东西,我该如何保护密钥:

public class DropboxService  {

    private final static String APP_KEY = "jk433g34hg3";
    private final static String APP_SECRET = "987dwdqwdqw90";
    private final static AccessType ACCESS_TYPE = AccessType.DROPBOX;

    // SOME MORE CODE HERE

}

您认为存储私钥的最佳和最安全的方式是什么?混淆,加密,你怎么看?

4

13 回答 13

389
  1. 实际上,您编译的应用程序包含密钥字符串,还包含常量名称 APP_KEY 和 APP_SECRET。从这种自记录代码中提取密钥是微不足道的,例如使用标准的 Android 工具 dx。

  2. 您可以应用 ProGuard。它将保持键字符串不变,但会删除常量名称。它还将尽可能用简短、无意义的名称重命名类和方法。然后提取密钥需要更多时间,以确定哪个字符串用于哪个目的。

    请注意,设置 ProGuard 并不像您担心的那么困难。首先,您只需要启用 ProGuard,如 project.properties 中所述。如果第三方库有任何问题,您可能需要在 proguard-project.txt 中隐藏一些警告和/或防止它们被混淆。例如:

    -dontwarn com.dropbox.**
    -keep class com.dropbox.** { *; }
    

    这是一种蛮力方法;一旦处理的应用程序工作,您可以优化此类配置。

  3. 您可以在代码中手动混淆字符串,例如使用 Base64 编码,或者最好使用更复杂的编码;甚至可能是本机代码。然后,黑客将不得不对您的编码进行静态逆向工程或在适当的位置动态拦截解码。

  4. 您可以应用商业混淆器,例如 ProGuard 的专用兄弟DexGuard。它还可以为您加密/混淆字符串和类。提取密钥需要更多的时间和专业知识。

  5. 您也许可以在自己的服务器上运行部分应用程序。如果你能把钥匙放在那里,它们是安全的。

最后,这是你必须做出的经济权衡:密钥有多重要,你能负担多少时间或软件,对密钥感兴趣的黑客有多老练,他们想要多长时间花费,密钥被黑客入侵之前的延迟有多少价值,任何成功的黑客将分发密钥的规模等等。像密钥这样的小块信息比整个应用程序更难保护。从本质上讲,客户端没有什么是牢不可破的,但您当然可以提高标准。

(我是 ProGuard 和 DexGuard 的开发者)

于 2013-01-28T22:01:22.783 回答
89

很少有想法,在我看来,只有第一个提供了一些保证:

  1. 将您的秘密保存在互联网上的某些服务器上,并在需要时抓住它们并使用。如果用户即将使用保管箱,那么没有什么能阻止您向您的网站发出请求并获取您的密钥。

  2. 将你的秘密放在 jni 代码中,添加一些可变代码以使你的库更大,更难反编译。您还可以将密钥字符串分成几部分并将它们保存在不同的地方。

  3. 使用混淆器,还输入代码散列秘密,然后在需要使用时取消散列。

  4. 将您的密钥作为资产中一张图像的最后一个像素。然后在需要时在您的代码中阅读它。混淆你的代码应该有助于隐藏会读取它的代码。

如果您想快速了解阅读 apk 代码是多么容易,请使用 APKAnalyser:

http://developer.sonymobile.com/knowledge-base/tool-guides/analysis-your-apks-with-apkanalysisr/

于 2013-01-28T21:07:01.827 回答
55

另一种方法是一开始就没有设备上的秘密!请参阅移动 API 安全技术(尤其是第 3 部分)。

使用历史悠久的间接传统,在您的 API 端点和应用程序身份验证服务之间共享秘密。

当您的客户端想要进行API 调用时,它会要求 app auth 服务对其进行身份验证(使用强大的远程证明技术),并且它会收到一个由密钥签名的限时(通常是JWT)令牌。

令牌随每个API 调用一起发送,端点可以在处理请求之前验证其签名。

实际的秘密永远不会出现在设备上;实际上,该应用程序从不知道它是否有效,它只是请求身份验证并传递生成的令牌。作为间接的一个很好的好处,如果您想更改秘密,您可以这样做,而无需用户更新他们已安装的应用程序。

所以如果你想保护你的秘密,一开始就不在你的应用程序中包含它是一个很好的方法。

于 2018-02-02T21:12:47.213 回答
28

旧的不安全方式:

按照 3 个简单的步骤来保护 API/密钥(旧答案

我们可以使用 Gradle 来保护 API 密钥或 Secret 密钥。

1. gradle.properties(项目属性):用key创建变量。

GoolgeAPIKey = "Your API/Secret Key"

2. build.gradle (Module: app) :在 build.gradle 中设置变量,以便在活动或片段中访问它。将以下代码添加到 buildTypes {}。

buildTypes.each {
    it.buildConfigField 'String', 'GoogleSecAPIKEY', GoolgeAPIKey
}

3. 通过应用的 BuildConfig 在 Activity/Fragment 中访问它:

BuildConfig.GoogleSecAPIKEY

更新 :

上述解决方案有助于开源项目通过 Git 提交。(感谢 David Rawson 和 riyaz-ali 的评论)。

根据 Matthew 和 Pablo Cegarra 的评论,上述方式不安全,反编译器将允许有人使用我们的密钥查看 BuildConfig。

解决方案 :

我们可以使用 NDK 来保护 API 密钥。我们可以将密钥存储在本机 C/C++ 类中,并在我们的 Java 类中访问它们。

请关注博客以使用 NDK 保护 API 密钥。

关于如何在 Android 中安全存储令牌的后续行动

于 2017-11-29T12:55:26.473 回答
18

添加到@Manohar Reddy 解决方案,可以使用firebase 数据库或firebase RemoteConfig(具有Null 默认值):

  1. 加密你的密钥
  2. 将其存储在 firebase 数据库中
  3. 在应用程序启动期间或需要时获取它
  4. 解密密钥并使用它

这个解决方案有什么不同?

  • firebase 没有凭据
  • firebase 访问受到保护,因此只有具有签名证书的应用才有权限进行 API 调用
  • 加密/解密以防止中间人拦截。但是已经调用 https 到 firebase
于 2018-09-12T22:22:00.133 回答
16

App-Secret 密钥应该保密 - 但是在发布应用程序时,某些人可以将其撤消。

对于那些不会隐藏的人,锁定ProGuard代码。这是一个重构,一些付费混淆器正在插入一些按位运算符来取回jk433g34hg3 字符串。如果您工作 3 天,您可以将黑客攻击时间延长 5 -15 分钟 :)

最好的方法是保持原样,恕我直言。

即使您将密钥存储在服务器端(您的 PC),也可以破解并打印出密钥。也许这需要最长的时间?无论如何,最好的情况是几分钟或几个小时。

普通用户不会反编译您的代码。

于 2013-01-28T20:54:41.890 回答
15

一种可能的解决方案是在您的应用程序中编码数据并在运行时使用解码(当您想要使用该数据时)。我还建议使用 progaurd 使您的应用程序的反编译源代码难以阅读和理解。例如,我在应用程序中放置了一个编码密钥,然后在我的应用程序中使用解码方法在运行时解码我的密钥:

// "the real string is: "mypassword" "; 
//encoded 2 times with an algorithm or you can encode with other algorithms too
public String getClientSecret() {
    return Utils.decode(Utils
            .decode("Ylhsd1lYTnpkMjl5WkE9PQ=="));
}

proguarded 应用程序的反编译源代码是这样的:

 public String c()
 {
    return com.myrpoject.mypackage.g.h.a(com.myrpoject.mypackage.g.h.a("Ylhsd1lYTnpkMjl5WkE9PQ=="));
  }

至少对我来说已经够复杂了。当我别无选择只能在我的应用程序中存储一个值时,我就是这样做的。当然我们都知道这不是最好的方法,但它对我有用。

/**
 * @param input
 * @return decoded string
 */
public static String decode(String input) {
    // Receiving side
    String text = "";
    try {
        byte[] data = Decoder.decode(input);
        text = new String(data, "UTF-8");
        return text;
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
    return "Error";
}

反编译版本:

 public static String a(String paramString)
  {
    try
    {
      str = new String(a.a(paramString), "UTF-8");
      return str;
    }
    catch (UnsupportedEncodingException localUnsupportedEncodingException)
    {
      while (true)
      {
        localUnsupportedEncodingException.printStackTrace();
        String str = "Error";
      }
    }
  }

你可以在 google 中搜索一下,找到这么多加密器类。

于 2016-06-18T10:11:54.680 回答
12

这个例子有很多不同的方面。我将提到我认为其他地方没有明确涵盖的几点。

保护传输中的秘密

首先要注意的是,使用他们的应用身份验证机制访问 Dropbox API 需要您传输您的密钥和秘密。连接是 HTTPS,这意味着您无法在不知道 TLS 证书的情况下拦截流量。这是为了防止有人在从移动设备到服务器的过程中截获和读取数据包。对于普通用户来说,这是确保其流量隐私的一种非常好的方法。

它不擅长的是防止恶意人员下载应用程序并检查流量。对进出移动设备的所有流量使用中间人代理非常容易。由于 Dropbox API 的性质,在这种情况下,不需要反汇编或逆向代码工程来提取应用程序密钥和秘密。

您可以进行固定检查您从服务器收到的 TLS 证书是否是您期望的证书。这增加了对客户端的检查,使得拦截流量变得更加困难。这将使检查飞行中的流量变得更加困难,但固定检查发生在客户端中,因此仍有可能禁用固定测试。虽然它确实使它变得更难。

保护静止的秘密

作为第一步,使用诸如proguard之类的东西将有助于使任何秘密所在的位置变得不那么明显。您还可以使用 NDK 存储密钥和秘密并直接发送请求,这将大大减少具有适当技能的人来提取信息的数量。通过不将值直接存储在内存中任何时间长度,可以实现进一步的混淆,您可以按照另一个答案的建议在使用前对它们进行加密和解密。

更高级的选项

如果您现在偏执于将秘密放在应用程序的任何位置,并且您有时间和金钱来投资更全面的解决方案,那么您可能会考虑将凭据存储在您的服务器上(假设您有)。这将增加对 API 的任何调用的延迟,因为它必须通过您的服务器进行通信,并且由于数据吞吐量的增加可能会增加运行服务的成本。

然后,您必须决定如何最好地与您的服务器通信以确保它们受到保护。这对于防止您的内部 API 再次出现所有相同的问题非常重要。我能给出的最佳经验法则是不要因为中间人威胁而直接传输任何秘密。相反,您可以使用您的秘密对流量进行签名,并验证到达您的服务器的任何请求的完整性。执行此操作的一种标准方法是计算以密钥为密钥的消息的 HMAC。我在一家拥有安全产品的公司工作,该产品也在该领域运营,这就是我对这类东西感兴趣的原因。事实上,这是我的一位同事的一篇博客文章,其中涵盖了大部分内容。

我应该做多少?

有了这样的安全建议,您需要做出成本/收益决定,确定您想让某人闯入的难度。如果您是一家保护数百万客户的银行,您的预算与支持应用程序的人完全不同。空余时间。阻止某人破坏您的安全几乎是不可能的,但实际上很少有人需要所有的花里胡哨,并且通过一些基本的预防措施,您可以获得很长的路要走。

于 2017-01-17T13:32:11.777 回答
7

最安全的解决方案是将您的密钥保存在服务器上,并通过您的服务器路由所有需要该密钥的请求。这样,密钥永远不会离开您的服务器,因此只要您的服务器是安全的,那么您的密钥也是如此。当然,这种解决方案存在性能成本。

于 2016-09-24T15:41:13.317 回答
7

无论你做什么来保护你的密钥都不会是一个真正的解决方案。如果开发人员可以反编译应用程序,则无法保护密钥,隐藏密钥只是通过模糊来确保安全,代码混淆也是如此。保护密钥的问题在于,为了保护它,您必须使用另一个密钥,并且该密钥也需要得到保护。想想隐藏在一个用钥匙锁住的盒子里的钥匙。您将一个盒子放在房间内并锁定房间。您还剩下另一把钥匙来保护。并且该密钥仍将在您的应用程序中进行硬编码。

因此,除非用户输入 PIN 或短语,否则无法隐藏密钥。但要做到这一点,您必须有一个方案来管理带外发生的 PIN,这意味着通过不同的渠道。对于保护 Google API 等服务的密钥当然不切实际。

于 2016-12-15T16:27:44.127 回答
6

把秘密藏在firebase database里面,在应用启动时从中获取,这比调用Web服务要好得多。

于 2018-03-27T07:56:19.997 回答
5

年代久远的帖子,但仍然足够好。我认为将它隐藏在 .so 库中会很棒,当然使用 NDK 和 C++。.so 文件可以在十六进制编辑器中查看,但祝你反编译好运:P

于 2016-03-30T15:51:53.513 回答
5

保持这些隐私的唯一真正方法是将它们保存在您的服务器上,让应用程序将任何内容发送到服务器,然后服务器与 Dropbox 交互。这样您就永远不会以任何格式分发您的私钥。

于 2016-05-07T18:55:33.550 回答