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

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

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

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


}

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

209221 次浏览
app - secret密钥应该是私有的,但是当释放app

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

恕我直言,最好的办法就是保持现状。

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

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

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

  1. 把你的秘密保存在互联网上的某个服务器上,需要的时候就把它们拿出来使用。如果用户即将使用dropbox,那么没有什么可以阻止你向你的网站发出请求并获得你的秘密密钥。

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

  3. 使用混淆器,也把代码哈希的秘密,然后在需要使用的时候拆散它。

  4. 把你的秘密密钥作为最后一个像素你的图像在资产。然后在需要时在代码中读取它。混淆代码可以帮助隐藏读取它的代码。

如果你想快速看看阅读你的apk代码是多么容易,然后获取APKAnalyser:

http://developer.sonymobile.com/knowledge-base/tool-guides/analyse-your-apks-with-apkanalyser/ < a href = " http://developer.sonymobile.com/knowledge-base/tool-guides/analyse-your-apks-with-apkanalyser/ " > < / >

  1. 实际上,编译后的应用程序不仅包含键字符串,还包含常量名称APP_KEY和APP_SECRET。从这种自我记录的代码中提取键是很简单的,例如使用标准的Android工具dx。

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

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

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

    这是一种蛮力方法;一旦处理过的应用程序工作,您就可以细化这样的配置。 < / p >

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

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

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

最后,这是你必须做出的经济权衡:密钥有多重要,你能提供多少时间或软件,对密钥感兴趣的黑客有多老练,他们愿意花多少时间,密钥被黑客攻击之前的延迟有多大价值,成功的黑客会在多大范围内分发密钥,等等。像密钥这样的小块信息比整个应用程序更难保护。从本质上讲,客户端没有什么是牢不可破的,但你当然可以提高标准。

(我是ProGuard和DexGuard的开发者)

老帖子,但仍然足够好。我认为把它藏在一个。so库中会很棒,当然使用NDK和c++。.so文件可以在十六进制编辑器中查看,但祝你能反编译它:P

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

一个可能的解决方案是在应用程序中编码数据,并在运行时(当你想使用该数据时)使用解码。我还建议使用progaard来增加应用程序反编译源代码的阅读和理解难度。例如,我把一个编码的密钥在应用程序,然后使用解码方法在我的应用程序解码我的秘密密钥在运行时:

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

你可以在谷歌中找到这么多的加密类。

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

无论您做什么来保护您的秘密密钥都不会是一个真正的解决方案。如果开发人员可以反编译应用程序,就没有办法保护密钥,隐藏密钥只是通过模糊来实现安全,代码混淆也是如此。保护密钥的问题是,为了保护它,你必须使用另一个密钥,而这个密钥也需要被保护。想象一把钥匙藏在一个锁着钥匙的盒子里。你把一个盒子放在房间里,然后把房间锁上。你还剩下另一把钥匙需要保护。这个键仍然是硬编码在应用程序中。

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

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

保护运输中的秘密

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

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

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

保守秘密

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

更高级的选项

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

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

我应该做多少?

有了这样的安全建议,你需要对你想让别人入侵的难度做出成本/效益决定。如果你是一家保护数百万客户的银行,你的预算与那些在业余时间支持应用程序的人完全不同。要防止别人破坏你的安全系统几乎是不可能的,但在实践中,很少有人需要所有的铃铛和哨子,只要有一些基本的预防措施,你就可以走得很远。

旧的不安全的方式:

遵循3个简单的步骤来保护API/秘密密钥(旧的答案)

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

创建带有key的变量。

GoogleAPIKey = "Your API/Secret Key"

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

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

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

BuildConfig.GoogleSecAPIKEY

更新:

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

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

解决方案:

我们可以使用NDK来保护API密钥。我们可以在原生C/ c++类中存储键,并在Java类中访问它们。

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

后续关于如何在Android中安全地存储令牌

另一种方法是一开始就不要在设备上保存秘密!参见移动API安全技术(特别是第3部分)。

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

当你的客户端想要创建一个API调用,它会要求应用认证服务对它进行认证(使用强大的远程认证技术),并且它会收到一个由secret签名的限时令牌(通常是JWT)。

令牌与每个API调用一起发送,端点可以在对请求采取行动之前验证其签名。

真正的秘密永远不会出现在设备上;事实上,应用程序从来不知道它是否有效,它只是请求身份验证并传递结果令牌。间接的好处是,如果你想改变秘密,你可以不需要用户更新他们安装的应用程序。

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

将秘密保存在firebase database中,并在应用程序启动时从它获取, 它比调用web服务好得多

添加到@Manohar Reddy解决方案,可以使用firebase Database或firebase RemoteConfig(默认值为Null):

  1. 密码你的钥匙
  2. 存储在firebase数据库中
  3. 在应用程序启动期间或任何需要的时候获得它
  4. 破译钥匙并使用它

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

  • 没有firebase凭据
  • firebase访问受到保护,因此只有具有签名证书的应用程序才有 权限使API调用
  • 加密/解密以防止中间人拦截。然而 调用已经HTTPS到firebase