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

Bas*_*der 342 android reverse-engineering proguard api-key

大多数应用开发者会将一些第三方库集成到他们的应用中.如果要访问服务,例如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

}
Run Code Online (Sandbox Code Playgroud)

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

Eri*_*une 327

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

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

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

    -dontwarn com.dropbox.**
    -keep class com.dropbox.** { *; }
    
    Run Code Online (Sandbox Code Playgroud)

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

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

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

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

最后,你必须做出经济权衡:钥匙的重要性,你能承受多少时间或软件,对钥匙感兴趣的黑客有多复杂,他们想要多长时间花费,在密钥被黑客攻击之前的延迟,在任何成功的黑客以何种规模分发密钥等的价值是多少.像密钥这样的小信息比整个应用程序更难保护.从本质上讲,客户端没有任何东西是牢不可破的,但你肯定可以提高标准.

(我是ProGuard和DexGuard的开发人员)

  • @EricLafortune 将私钥字符串存储在 Java 类中与存储在 String 资源 XML 中是否没有区别? (4认同)
  • @EricLafortune 现在是否可以使用 Android Keystore 系统来安全地存储密钥?( http://developer.android.com/training/articles/keystore.html ) (2认同)
  • @DavidThomas:您是否尝试过使用密钥库。我想混淆用 Java 类编写的 API 密钥。请回复 (2认同)
  • 我不明白#5。难道它没有与原始问题完全相同的问题吗? (2认同)
  • @BartvanIngenSchenau 我将如何向服务器发送请求以验证我确实已通过身份验证?我可以想到一个解决方案......我会发送一个私钥凭证......但这不是我们试图解决的原始问题吗? (2认同)

mar*_*inj 74

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

  1. 将您的秘密保存在互联网上的某些服务器上,并在需要时抓住它们并使用.如果用户即将使用dropbox,则不会阻止您向您的站点发出请求并获取您的密钥.

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

  3. 使用混淆器,也放入代码哈希秘密,稍后在需要时使用unhash.

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

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

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

  • 如果用户可以反编译应用程序,虽然他们可能会确定对您自己的服务器发出的请求,并只是执行它来获取秘密.这里没有银弹,但是走几步,我打赌你会好的!如果您的应用程序超级受欢迎,但可能不是..好主意! (39认同)
  • 我真的很喜欢将键隐藏在图像中.+1 (36认同)
  • 是的,1号不保证. (2认同)
  • @ Mr.Hyde这叫做隐写术,它的方式过于复杂,无法在这里给出示例代码,你可以在google上找到例子.我在这里找到了一个:http://www.dreamincode.net/forums/topic/27950-steganography/.这个想法很棒但是因为apk代码可以反编译它破坏了它的美丽. (2认同)

小智 26

另一种方法是首先在设备上没有秘密!请参阅移动API安全技术(尤其是第3部分).

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

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

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

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

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

  • 当您想要访问身份验证服务时,问题仍然存在。它会给你一个客户端 ID 和客户端密码。我们应该把他们救到哪里呢? (12认同)
  • 这应该是公认的答案. (4认同)
  • 不能解决私有 api 问题,您需要先对 api 进行身份验证才能使用它。您从哪里获取所有应用程序用户的凭据? (2认同)

SAN*_*NAT 19

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

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

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

GoolgeAPIKey = "Your API/Secret Key"
Run Code Online (Sandbox Code Playgroud)

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

buildTypes.each {
    it.buildConfigField 'String', 'GoogleSecAPIKEY', GoolgeAPIKey
}
Run Code Online (Sandbox Code Playgroud)

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

BuildConfig.GoogleSecAPIKEY
Run Code Online (Sandbox Code Playgroud)

更新:

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

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

方案:

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

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

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

  • 真的11票赞成?只是反编译,打开字符串和voilá (28认同)
  • 使用Java Decompiler将允许某人查看BuildConfig文件和"GoogleSecAPIKEY" (4认同)
  • 将密钥存储在gradle文件中是安全的吗? (3认同)
  • 不应该将@Google`gradle.properties`签入Git,这样就可以将秘密保留在已提交的源代码之外,至少 (3认同)
  • 这并不妨碍将API密钥捆绑到生成的`apk`中(它将添加到生成的`BuildConfig`文件中),尽管这绝对是管理不同API密钥的好主意(例如在开源中)项目) (3认同)
  • 正如所指出的,如果解决方案明显不安全,那么答案顶部非常大的粗体文本可能不应该说“安全”。我知道那里有一个“更新”部分,但很多人显然没有读到。 (3认同)
  • 您的`BuildConfig.java`文件将具有纯文本格式的密钥。这并不比OP已经在做的更好。 (2认同)

Mil*_*nia 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=="));
}
Run Code Online (Sandbox Code Playgroud)

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

 public String c()
 {
    return com.myrpoject.mypackage.g.h.a(com.myrpoject.mypackage.g.h.a("Ylhsd1lYTnpkMjl5WkE9PQ=="));
  }
Run Code Online (Sandbox Code Playgroud)

至少它对我来说足够复杂.当我别无选择但在我的应用程序中存储值时,这就是我的方式.当然我们都知道这不是最好的方式,但它对我有用.

/**
 * @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";
}
Run Code Online (Sandbox Code Playgroud)

反编译版本:

 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";
      }
    }
  }
Run Code Online (Sandbox Code Playgroud)

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

  • 我认为这已经接近最佳解决方案,但将其与静态链接的 NDK 代码结合起来,该代码对“正在运行的应用程序名称”进行哈希处理,并使用生成的哈希值来解密秘密。 (4认同)

Aym*_*bsi 12

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

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

此解决方案有什么不同?

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

  • 尽管所有人都尊重这个解决方案,但我们仍然是第一方。您建议使用证书,而不是使用凭据。任何能够窃取您的凭据的人都能够窃取您的签名证书。 (3认同)

小智 11

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

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

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

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

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

  • 它是"可见"的解密程序,很容易反过来,你有原始的字符串 (2认同)

The*_*ist 10

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

保护运输中的秘密

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

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

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

在休息时保护秘密

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

更高级的选择

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

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

我该怎么办?

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

  • 此外,Skip 的文章和他们所基于的博客文章在我回答一周后发布。 (5认同)
  • 您只需从此处复制并粘贴此内容:https://hackernoon.com/mobile-api-security-techniques-682a5da4fe10,无需确认来源。 (2认同)

Ber*_*iri 8

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

  • 问题是 - 到达包含所有秘密的服务器我应该使用另一个密钥 - 我想知道我会保留它的位置?;)我想说的是 - 这也不是最好的解决方案(不要认为这里有理想的解决方案) (29认同)
  • 你能解释一下客户端如何加密他想发送给服务器的数据,而密钥是在服务器端吗?如果你的答案是 - 服务器将密钥发送给客户端 - 所以必须保证!所以再没有神奇的解决方案 你不能看到吗?! (5认同)
  • @ken随机数根据电话号码和对其文本消息的物理访问进行验证.如果有人欺骗你,你就会得到他们的信息.如果这还不够强迫他们创建完整的用户帐户和密码.如果这还不够好,也可以获得信用卡.如果这还不够好让他们打电话.如果那不够好,他们会面对面.你想要多么安全/不方便? (3认同)
  • @BernardIgiri然后我们又回到了正方形1.让我们假设,手机创建一个随机登录,服务器接受并发送一个pin(这就是所谓的_private server_我们正在谈论).然后,对您的应用程序进行反汇编的人会发现访问您的_private_服务器所需的一切只是他可以自己创建的随机登录.告诉我是什么阻止他创建一个并访问你的服务器?实际上,您的解决方案与实际存储登录或api密钥到主服务器(我们想要存储在我们的私有服务器中的凭据)之间的区别是什么? (2认同)

Ahm*_*wad 5

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

  • 用户可以轻松地对共享库进行函数调用,并获取隐藏在其中的任何内容.不需要将它贬低. (10认同)
  • 根据http://www.androidauthority.com/where-is-the-best-place-to-store-a-password-in-your-android-app-597197/,在android中没有安全的方法来做到这一点在这一刻. (5认同)
  • @AhmedAwad 不明白为什么这有 3 票赞成。任何人都可以轻松反编译该应用程序并查看如何调用 ndk 入口点:/ (2认同)
  • 这个答案几乎是最好的选择之一,但作者应该提到,您应该包含一个调用(在您的 NDK 库内)以查看校验和是否与您的 APK 匹配,这一点非常重要,否则有人可以在外部调用您的 NDK 库你的应用程序 (2认同)

Nic*_*ick 5

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

  • 但是你如何防止世界其他地方调用服务器呢? (14认同)
  • 嗯,这可能意味着,但它是一个完全独立的授权。但你没有必要。我正在谈论的用例是您的应用程序用户可以登录到您的应用程序,例如使用 facebook 或 twitter。您不会将他们的凭据存储在您的应用程序中,您甚至不知道它们。该授权过程允许他们访问您的 api 服务器,该服务器具有 dropbox 的凭据,但没有应用程序或用户可以直接访问它们。 (4认同)
  • 是的,但您说应用程序将首先与服务器进行身份验证。这是否意味着在应用程序中存储另一组凭据?我知道服务器会处理实际的保管箱调用。 (3认同)

use*_*728 5

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

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


Man*_*ddy 5

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

  • 但是firebase的凭证呢? (10认同)
  • 没道理,攻击者可以从反编译代码中看到您的Firebase详细信息,并从您的数据中获取任何数据 (9认同)
  • 不幸的是,Firebase 数据库在中国不起作用。 (3认同)
  • 我认为这是最好的解决方案,因为Firebase Apps使用SHA1允许访问服务器。反编译代码将无助于调​​用Firebase,因为黑客的新应用应使用确切的应用戳来访问Firebase。另外,存储的密钥在存储到Firebase DB中之前应先加密,并在收到后解密,以免中间人被截获。 (3认同)