在应用程序中存储和保护私有API密钥的最佳实践[关闭]

问题描述 投票:306回答:14

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

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

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

}

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

android reverse-engineering proguard api-key
14个回答
322
投票
  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的开发人员)


4
投票

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


4
投票

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

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


4
投票

保持firebase database的秘密,并在应用程序启动时从中获取,这比调用Web服务要好得多。


3
投票

保持这些私密性的唯一真正方法是将它们保留在您的服务器上,让应用程序将其发送到服务器,并且服务器与Dropbox交互。这样,您永远不会以任何格式分发您的私钥。


2
投票

基于痛苦的经验,在咨询IDA-Pro专家后,最好的解决方案是将代码的关键部分移动到DLL / SharedObject中,然后从服务器获取并在运行时加载。

敏感数据必须编码,因为它很容易做到这样的事情:

$ strings libsecretlib.so | grep My
  My_S3cr3t_P@$$W0rD

73
投票

很少有想法,在我看来只有第一个给出一些保证:

  1. 将您的秘密保存在互联网上的某些服务器上,并在需要时抓住它们并使用。如果用户即将使用dropbox,那么没有什么可以阻止您向您的网站发出请求并获取您的密钥。
  2. 把你的秘密放在jni代码中,添加一些变量代码,使你的库更大,更难以反编译。您也可以将键字串分成几个部分并将它们保存在不同的位置。
  3. 使用混淆器,也放入代码哈希秘密,稍后在需要时使用unhash。
  4. 将您的密钥作为资产中某个图像的最后一个像素。然后在需要时在您的代码中阅读它。混淆代码应该有助于隐藏将读取它的代码。

如果你想快速了解一下阅读apk代码是多么容易,然后抓住APKAnalyser:

http://developer.sonymobile.com/knowledge-base/tool-guides/analyse-your-apks-with-apkanalyser/


21
投票

另一种方法是首先在设备上没有秘密!见Mobile API Security Techniques(特别是第3部分)。

使用时间传承的间接传统,分享您的API端点和应用认证服务之间的秘密。

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

令牌随每次API调用一起发送,其中端点可以在对请求执行操作之前验证其签名。

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

因此,如果您想保护自己的秘密,首先不要在您的应用中使用它是一个非常好的方法。


19
投票

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

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

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

GoolgeAPIKey = "Your API/Secret Key"

2. build.gradle(Module:app):在build.gradle中设置变量以在activity或fragment中访问它。将以下代码添加到buildTypes {}。

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

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

BuildConfig.GoogleSecAPIKEY

更新:

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

根据Matthew和Pablo Cegarra的评论,上述方式并不安全,Decompiler将允许某人使用我们的密钥查看BuildConfig。

方案:

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

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

A follow-up on how to store tokens securely in Android


13
投票

一种可能的解决方案是在应用程序中对数据进行编码,并在运行时使用解码(当您想要使用该数据时)。我还建议使用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=="));
}

已编译应用程序的反编译源代码如下:

 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";
      }
    }
  }

你可以在谷歌搜索一下这么多的加密类。


11
投票

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

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

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

  • 没有firebase的凭据
  • firebase访问受到保护,因此只有具有签名证书的应用程序才有权进行API调用
  • 加密/解密,以防止中间人拦截。但是,呼叫已经https到firebase

10
投票

App-Secret密钥应保密 - 但在发布应用程序时,有些人可以撤销它们。

对于那些不会隐藏的家伙,锁定ProGuard代码。它是一个重构,一些付费的混淆器正在插入几个按位运算符来取回jk433g34hg3字符串。如果你工作3天,你可以让黑客长5到15分钟:)

imho,最好的方法是保持原样。

即使您存储在服务器端(您的PC),密钥也可能被黑客入侵并打印出来。也许这花费的时间最长?无论如何,最好的情况是几分钟或几小时。

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


10
投票

这个例子有许多不同的方面。我将提到一些我认为在其他地方没有明确涵盖过的观点。

保护运输中的秘密

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

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

您可以执行pinning,它会检查您从服务器收到的TLS证书是否是您期望的证书。这会向客户端添加一个检查,并使拦截流量变得更加困难。这将使检查飞行中的流量变得更加困难,但是在客户端中发生钉扎检查,因此仍可能禁用钉扎测试。它确实让它变得更难。

在休息时保护秘密

作为第一步,使用像proguard这样的东西将有助于使任何秘密保持不太明显。您还可以使用NDK存储密钥和密钥并直接发送请求,这将大大减少具有提取信息的适当技能的人数。通过不将值直接存储在内存中任何时间长度,可以实现进一步的混淆,您可以加密它们并在使用之前解密它们,如另一个答案所示。

更高级的选择

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

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

我该怎么办?

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


8
投票

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

© www.soinside.com 2019 - 2024. All rights reserved.