在Android中存储私人API密钥的最佳做法

我正在开发一个应用程序,并使用Dropbox和Google Drive等多个第三方API和SDK。 这些库需要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 } 

应用程序密钥应该保密 – 但是当发布应用程序时,他们可以被一些人扭转。

我想知道什么是最好的加密,混淆或什么使这个安全。

我想过使用ProGuard,但是为整个项目设置ProGuard需要花费几个星期的时间。 这就是为什么我只想使用ProGuard来存储私钥和其他敏感数据的重要类。

这是好的还是有其他的方法吗? 你怎么看?

  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的开发者)

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

  1. 保持你的秘密在互联网上的一些服务器,并在需要时抓住他们,并使用。 如果用户即将使用Dropbox,则无法阻止您向您的网站发送请求并获取您的密钥。

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

  3. 使用混淆器,也可以放在代码散列的秘密中,稍后在需要使用的时候就可以解散它。

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

如果您想快速浏览一下您读取APK代码的容易程度,那么请抓取APKAnalyser:

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

应用程序密钥应该保密 – 但是当发布应用程序时,他们可以被一些人扭转。

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

最好的办法是保持它,恕我直言。

即使您在服务器端(您的电脑)存储密钥可以被黑客攻击和打印出来。 也许这需要最长的时间? 无论如何,最好的情况是几分钟或几个小时。

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

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

这个例子有很多不同的方面。 我会提到几个我不认为在其他地方明确提到的观点。

保护运输中的秘密

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

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

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

保护休息的秘密

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

更高级的选项

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

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

我应该做多少?

有了这样的安全建议,你需要做出一个成本/收益的决定,你想如何努力让别人破门而入。如果你是一家保护数百万用户的银行,那么你的预算与支持应用程序的用户完全不同空余时间。 实际上不可能阻止某人破坏你的安全,但实际上很少有人需要所有的哨声和一些基本的预防措施,你可以走很长的路。

年纪大了,但还是够好的。 我认为隐藏在.so库中会很好,当然使用NDK和C ++。 .so文件可以在十六进制编辑器中查看,但祝你好运反编译:P

一种可能的解决方案是将数据编码到您的应用程序中,并在运行时使用解码(当您要使用该数据时)。 我也建议使用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.gha(com.myrpoject.mypackage.gha("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(aa(paramString), "UTF-8"); return str; } catch (UnsupportedEncodingException localUnsupportedEncodingException) { while (true) { localUnsupportedEncodingException.printStackTrace(); String str = "Error"; } } } 

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

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

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

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