ChatGPT解决这个技术问题 Extra ChatGPT

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

关闭。这个问题是基于意见的。它目前不接受答案。想改进这个问题?更新问题,以便可以通过编辑这篇文章用事实和引用来回答它。 2年前关闭。改进这个问题

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

}

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

我已经存储在 image/png 并通过 png 作为 BufferReader 获取密钥
我认为这是一个有效的问题,并在 Firebase Android SDK github 页面上发布了一个类似的问题:github.com/firebase/firebase-android-sdk/issues/1583。让我们看看这是否得到处理。

E
Eric Lafortune

实际上,您编译的应用程序包含密钥字符串,还包含常量名称 APP_KEY 和 APP_SECRET。从这种自记录代码中提取密钥是微不足道的,例如使用标准的 Android 工具 dx。您可以应用 ProGuard。它将保持键字符串不变,但会删除常量名称。它还将尽可能用简短、无意义的名称重命名类和方法。然后提取密钥需要更多时间,以确定哪个字符串用于哪个目的。请注意,设置 ProGuard 并不像您担心的那么困难。首先,您只需要启用 ProGuard,如 project.properties 中所述。如果第三方库有任何问题,您可能需要在 proguard-project.txt 中隐藏一些警告和/或防止它们被混淆。例如:-dontwarn com.dropbox.** -keep class com.dropbox.** { *;这是一种蛮力的方法;一旦处理的应用程序工作,您可以优化此类配置。您可以在代码中手动混淆字符串,例如使用 Base64 编码,或者最好使用更复杂的编码;甚至可能是本机代码。然后,黑客将不得不对您的编码进行静态逆向工程或在适当的位置动态拦截解码。您可以应用商业混淆器,例如 ProGuard 的专用兄弟 DexGuard。它还可以为您加密/混淆字符串和类。提取密钥需要更多的时间和专业知识。您也许可以在自己的服务器上运行部分应用程序。如果你能把钥匙放在那里,它们是安全的。

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

(我是 ProGuard 和 DexGuard 的开发者)


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

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

将您的秘密保存在互联网上的某些服务器上,并在需要时抓住它们并使用。如果用户即将使用保管箱,那么没有什么能阻止您向您的网站发出请求并获取您的密钥。将你的秘密放在 jni 代码中,添加一些可变代码以使你的库更大,更难反编译。您还可以将密钥字符串分成几部分并将它们保存在不同的地方。使用混淆器,还输入代码散列秘密,然后在需要使用时取消散列。将您的密钥作为资产中一张图像的最后一个像素。然后在需要时在您的代码中阅读它。混淆你的代码应该有助于隐藏会读取它的代码。

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

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


如果用户可以反编译应用程序,尽管他们可能会确定向您自己的服务器发出的请求并简单地执行该请求以获取机密。这里没有灵丹妙药,但走几步,我打赌你会没事的!如果您的应用程序非常受欢迎,但可能不是……好主意!
是的,1号不能保证。
我真的很喜欢将键隐藏在图像中的想法。 +1
@MarcinJędrzejewski 您想解释更多(最好使用示例或剪切代码)关于第四个解决方案吗?谢谢你。
@Mr.Hyde 这被称为隐写术,它太复杂了,无法在此处提供示例代码,您可以在 google 上找到示例。我在这里找到了一个:dreamincode.net/forums/topic/27950-steganography。这个想法很棒,但由于 apk 代码可以被反编译,它破坏了它的美感。
s
shareef

另一种方法是一开始就没有设备上的秘密!请参阅 Mobile API Security Techniques(尤其是第 3 部分)。

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

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

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

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

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


当您要访问身份验证服务时,问题仍然存在。它将为您提供客户端 ID 和客户端密码。我们应该把它们保存在哪里?
不解决您需要首先对您的 api 进行身份验证才能使用它的私有 api。您从哪里获得所有应用程序用户的凭据?
@Ashi 客户端 ID 在某种程度上被混淆了,只有 api 端点知道如何从混淆数据中提取数据,例如只有客户端 ID 的某些字符(其中客户端 ID 是实际客户端 ID + 更多数据以创建混淆字符串)仅表示实际数据,但如果黑客试图更改或更改客户端 ID,他不知道哪些数据实际代表客户端 ID,因为实际上只有 api 端点知道客户端 ID 如何被混淆以及如何提取来自客户端 ID 的有用数据,它实际上代表了客户端 ID....希望你明白我的意思
R
Ramesh R

旧的不安全方式:

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

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

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

GoogleAPIKey = "Your API/Secret Key"

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

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

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

BuildConfig.GoogleSecAPIKEY

更新:

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

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

解决方案:

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

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

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


将密钥存储在 gradle 文件中是否安全?
@Google gradle.properties 不应该签入到 Git,所以这是一种将秘密隐藏在提交的源代码之外的方法,至少
这不会阻止 API 密钥绑定到生成的 apk(它将添加到生成的 BuildConfig 文件中),尽管这绝对是管理不同 API 密钥的好主意(例如在开源项目)
使用 Java 反编译器将允许某人查看 BuildConfig 文件和“GoogleSecAPIKEY”
您的 BuildConfig.java 文件将具有纯文本形式的密钥。这并不比 OP 已经在做的更好。
M
Manohar

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

加密您的密钥 将其存储在 firebase 数据库中 在应用程序启动期间或在需要时解密密钥并使用它

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

firebase 没有凭据

firebase 访问受到保护,因此只有具有签名证书的应用才有权限进行 API 调用

加密/解密以防止中间人拦截。但是已经调用 https 到 firebase


所有人都尊重这个解决方案,我们仍然是第一个广场。您建议使用证书,而不是使用凭据。任何能够窃取您的凭据的人都能够窃取您的签名证书。
但是,使用建议的解决方案的一个优点是,我们在黑客面前又增加了一种复杂性。
我们从不将我们的私人证书保存在源代码中,所以没有机会窃取,对吧?
佚名

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

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

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

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

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


好吧 - 不是我希望得到的答案 =) ...我认为你可以获得很大的安全性:(
抱歉,这不是您想要的出色、超安全的解决方案,但是对于那些可以使用编译器、反编译器的人来说,没有安全的 java 代码:甚至可以使用 hexa viewer 和 decrtyped 查看本机代码。至少值得一试...
虽然 Proguard 不会混淆实际的密钥..?最好的办法是一些简单的加密/解密例程,混淆会隐藏。
它是“可见”的解密例程,很容易进行反向操作,并且您拥有原始字符串
即使你可以在设备上保护它,你也绝对不能保护用户不查看他自己的网络日志,其中可能会传递秘密。
M
Milad Faridnia

一种可能的解决方案是在您的应用程序中编码数据并在运行时使用解码(当您想要使用该数据时)。我还建议使用 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 中搜索一下,找到这么多加密器类。


我认为这已接近最佳解决方案,但将其与静态链接的 NDK 代码相结合,该代码散列“正在运行的应用程序名称”并使用此生成的散列来解密秘密。
T
ThePragmatist

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

保护传输中的秘密

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

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

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

保护静止的秘密

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

更高级的选项

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

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

我应该做多少?

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


您只需从此处复制并粘贴此内容:hackernoon.com/mobile-api-security-techniques-682a5da4fe10,而无需确认来源。
@ortonomy 我同意他应该引用您链接的文章,但他可能已经忘记了,因为两者都在同一个地方工作......
在我回答一周后,Skip 的文章和他们所依据的博客文章也发表了。
u
user1611728

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

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


B
Bernard Igiri

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


问题是——要到达包含所有秘密的服务器,我应该使用另一个密钥——我想知道我会把它保存在哪里? ;) 我想说的是 - 这也不是最好的解决方案(不要认为这里有理想的解决方案)
你能在这里解释一下客户端如何加密他想要发送到服务器的数据,而密钥在服务器端吗?如果你的答案是——服务器向客户端发送密钥——那么这也必须得到保护!所以再次没有神奇的解决方案!你看不见吗?!
@Ken,所以您要解决的问题是阻止其他人使用您的服务器?我只知道一种解决方案,身份验证。用户必须创建一个帐户并登录才能访问服务器。如果您不希望人们输入他们的信息,您可以让应用程序自动化它。该应用程序可以在手机上生成一个随机登录令牌,使用手机号向服务器发送请求,服务器可以用随机密码响应手机。在验证了 pin 之后,就会创建帐户,并且从那里开始需要令牌。
@BernardIgiri,然后我们又回到了第 1 格。假设手机创建了一个随机登录,并且服务器接受了该登录并发送了一个密码(这就是我们所说的私人服务器)。然后反汇编您的应用程序的人会看到访问您的私人服务器所需要的只是一些他可以自己创建的随机登录。告诉我是什么阻止他创建一个并访问您的服务器?实际上,您的解决方案与实际将登录名或 api 密钥存储到主服务器(我们希望将其凭据存储在我们的私有服务器中)之间有什么区别
@ken 随机数根据电话号码和对其文本消息的物理访问进行身份验证。如果有人欺骗你,你就有他们的信息。如果这还不够好,请强制他们创建完整的用户帐户和密码。如果这还不够好,也可以买一张信用卡。如果这还不够好,请他们打电话。如果这还不够好,请与他们面对面交流。你想有多安全/不方便?
M
Manohar

把秘密保存在firebase database中,在应用启动时从中获取,这比调用网络服务要好得多。


但是firebase的凭据呢?
不幸的是,Firebase 数据库在中国不起作用。
没有意义,攻击者可以从反编译的代码中看到你的 firebase 详细信息,并从你的数据库中获取任何数据
我认为这是最好的解决方案,因为 firebase Apps 使用 SHA1 来允许访问服务器。反编译代码将无助于调用 firebase,因为黑客新应用程序应使用确切的应用程序标记来访问 firebase。此外,存储的密钥应在存储到 firebase 数据库之前进行加密,并在收到后解密,以避免中间人拦截。
当您通过网络从 firebase 数据库获取秘密时,这比通过安全 (https) 通道从另一个 Web 服务获取相同的秘密更安全吗?你可以解释吗?
A
Ahmed Awad

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


用户可以轻松地对共享库进行函数调用并获取其中隐藏的任何内容。无需对其进行反编译。
根据 androidauthority.com/…,目前在 android 中没有安全的方法。
@AhmedAwad 不明白为什么会有 3 票赞成。任何人都可以轻松地反编译应用程序并查看如何调用 ndk 入口点:/
这个答案几乎是最好的选择之一,但作者应该提到,您应该包括一个调用(在您的 NDK 库中)以查看校验和是否与您的 APK 匹配,否则有人可以在外部调用您的 NDK 库,这一点非常重要你的应用
@Sniper 会很棒,但它有一个大问题。你怎么知道什么文件在“调用”本机方法?如果你硬编码要检查的 apk 的名称,很好,但是如果我将我的“hack”apk 与“good”apk 放在同一个文件夹中怎么办?它将检查“好”的 apk 是否具有良好的校验和,并允许我执行该本地方法。除非有办法从 JNI/C++ 端知道调用者文件,否则它与其他选项一样毫无意义。
N
Nick

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


但是你如何防止世界其他地方调用服务器呢?
如果“服务器”是指凭据所在的网络服务器 - 您可以使用任何您想要的方法。使用用户名/密码、oauth、活动目录等进行简单身份验证。真正取决于您的应用程序。
也许我遗漏了一些东西,但这是否仍然需要在应用程序中存储凭据?
对,但你说应用程序将首先通过服务器进行身份验证。这是否意味着在应用程序中存储另一组凭据?我了解服务器将处理实际的 Dropbox 调用。
好吧,这可能意味着,但它是一个完全独立的授权。但你不必这样做。我正在谈论的用例是您的应用程序用户将登录到您的应用程序,例如使用 facebook 或 twitter。您不会将他们的凭据存储在您的应用程序中,您甚至都不知道它们。该授权过程允许他们访问您的 api 服务器,该服务器具有 Dropbox 的凭据,但没有应用程序或用户可以直接访问它们。