java 在进行 InApp Billing 时如何保护 Google Play 公钥

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11671865/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-31 06:00:07  来源:igfitidea点击:

How to protect Google Play public key when doing InApp Billing

javaandroidgoogle-play

提问by Ryan

Actually this is a little bit silly about protecting public key (what is the definition of public key then?) but as per the documentation by Google:

实际上,这对于保护公钥有点愚蠢(那么公钥的定义是什么?)但根据Google文档

To keep your public key safe from malicious users and hackers, do not embed it in any code as a literal string. Instead, construct the string at runtime from pieces or use bit manipulation (for example, XOR with some other string) to hide the actual key. The key itself is not secret information, but you do not want to make it easy for a hacker or malicious user to replace the public key with another key.

为了保护您的公钥免受恶意用户和黑客的攻击,请勿将其作为文字字符串嵌入任何代码中。相反,在运行时从片段构造字符串或使用位操作(例如,与其他字符串进行 XOR)来隐藏实际密钥。密钥本身不是秘密信息,但您不想让黑客或恶意用户轻易地将公钥替换为另一个密钥。

Are there any recommended way to do it?

有什么推荐的方法吗?

I know there are many ways to do it, I just don't want to follow the same way how people handle password hashingin the past (e.g. md5, sha1 etc), I want to know the best practice in the above use case.

我知道有很多方法可以做到这一点,我只是不想遵循人们过去处理密码散列的方式(例如 md5、sha1 等),我想知道上述用例中的最佳实践。

采纳答案by Nikolay Elenkov

This comes up a lot around here :) The idea behind the paragraph you are quoting is that for in-app billing to be secure, you need to verify transaction signatures. Those are signed with a private key, associated with your developer account. The key resides on Google's servers, so it's fairly safe to assume that no one else can sign data with it. To verify it you need your public key, which you can copy from the developer console. If someone replaced it in your app, they could fool it to accept in-app billing transactions from unauthorized sources, because if they plant the public key, they probably also control the corresponding private key. In practice however, it is far easier to simply modify your code in the right places to always return true for isLicensed(), hasItem()or similar methods you might have and no one does this.

这在这里经常出现:) 您引用的段落背后的想法是,为了确保应用内计费的安全,您需要验证交易签名。它们使用与您的开发者帐户相关联的私钥进行签名。密钥位于 Google 的服务器上,因此可以相当安全地假设没有其他人可以使用它对数据进行签名。要验证它,您需要您的公钥,您可以从开发者控制台复制它。如果有人在您的应用程序中替换了它,他们可能会欺骗它接受来自未经授权的来源的应用程序内计费交易,因为如果他们植入公钥,他们可能也控制了相应的私钥。然而,在实践中,更容易简单地修改代码在正确的地方总是返回真正的isLicensed()hasItem()或者你可能有类似的方法也没有人做到这一点。

The best way to protect the key is, of course, not to have the key in your app at all. Move all of the transaction validation logic to your server, and use HTTPS to connect to it. Properly validate the certificate chain to ensure you are talking to your own server(s). Otherwise, someone might mess around with DNS and fool your app to connect to their own servers. A similar attack against iOS purchasing was announced a couple of weeks ago.

当然,保护密钥的最佳方法是根本不在您的应用程序中放置密钥。将所有事务验证逻辑移动到您的服务器,并使用 HTTPS 连接到它。正确验证证书链以确保您正在与您自己的服务器通信。否则,有人可能会弄乱 DNS 并欺骗您的应用程序连接到他们自己的服务器。几周前宣布了针对 iOS 购买的类似攻击。

The next best thing is to somehow obfuscate the key, and have it included in your app. This has the advantage that you don't need a server, but the disadvantage is that if someone is determined enough they will figure it out, since they can always reverse the byte code of your app. So your best bet is to come up with your own original way to do it that doesn't show up on public forums :) To make it a bit harder, you can implement the validation part in native code, which is harder (but not impossible) to analyze. Still, as mentioned above, patching byte code in the right places is far easier than trying to replace the public key, so that is what most crackers will do.

下一个最好的事情是以某种方式混淆密钥,并将其包含在您的应用程序中。这样做的优点是您不需要服务器,但缺点是如果有人足够坚定,他们会弄清楚的,因为他们总是可以反转您应用程序的字节码。所以你最好的办法是想出你自己的原始方法来做到这一点,但不会出现在公共论坛上:) 为了让它更难一点,你可以在本机代码中实现验证部分,这更难(但不是不可能)来分析。尽管如此,如上所述,在正确的位置修补字节码比尝试替换公钥要容易得多,因此大多数破解者都会这样做。

回答by Pointer Null

Do at least simple text transform. The idea is that plain dex disassembling won't reveal your public key.

至少做简单的文本转换。这个想法是简单的 dex 反汇编不会泄露你的公钥。

Here's example of function that makes simple string encoding/decoding:

这是进行简单字符串编码/解码的函数示例:

/**
 * Simple String transformation by XOR-ing all characters by value.
 */
static String stringTransform(String s, int i) {
   char[] chars = s.toCharArray();
   for(int j = 0; j<chars.length; j++)
      chars[j] = (char)(chars[j] ^ i);
   return String.valueOf(chars);
}

Then your private key is stored in source as encoded string (encode it with this function), and decoded at runtime with same function. This is kind of "XOR" method suggested by Google.

然后您的私钥作为编码字符串存储在源中(使用此函数对其进行编码),并在运行时使用相同的函数进行解码。这是谷歌建议的一种“XOR”方法。

You make the 'i' parameter yourself, anything random such as 0x27 or other will work. If you hide more strings this way, use different 'i' for each transform.

您自己设置 'i' 参数,任何随机值(例如 0x27 或其他)都可以使用。如果以这种方式隐藏更多字符串,请为每个转换使用不同的“i”。

回答by Eric Lafortune

As an alternative to obfuscating the key manually, you can also let an obfuscator do it automatically. ProGuardis part of the Android SDK, but it mostly obfuscates class/field/method names, not strings. Its specialized sibling for Android, DexGuard, can add more layers of obfuscation, applying string encryption, class encryption, and reflection. It's not free, but it saves time and it's probably more effective than doing it manually.

作为手动混淆密钥的替代方法,您还可以让混淆器自动完成。ProGuard是 Android SDK 的一部分,但它主要混淆类/字段/方法名称,而不是字符串。它的 Android 专用兄弟DexGuard可以添加更多的混淆层,应用字符串加密、类加密和反射。它不是免费的,但可以节省时间,而且可能比手动操作更有效。

(I am the developer of ProGuard and DexGuard)

(我是 ProGuard 和 DexGuard 的开发者)

回答by skygeek

Store your public key in the server side and once you get response from google play to verify the key send that response to server and perform your operation there at server.

将您的公钥存储在服务器端,一旦您收到来自 google play 的响应以验证密钥,将该响应发送到服务器并在服务器上执行您的操作。

回答by darrenp

The public key is base64-encoded ([a-zA-Z0-9+/]) so you can easily avoid the need to escape the obfuscated string at all as is an annoying problem in @PointerNull's solution.

公钥是 base64 编码的 ( [a-zA-Z0-9+/]),因此您可以轻松避免完全转义混淆字符串的需要,因为这是@PointerNull 解决方案中的一个烦人的问题。

Instead, you can perform the obfuscation by first converting the character in question to a 6-bit int. Then do the bit manipulation (e.g. XOR-ing) and then convert back to a base64-encoded char. Guaranteed no character escaping needed.

相反,您可以通过首先将有问题的字符转换为 6 位 int 来执行混淆。然后进行位操作(例如 XOR-ing),然后转换回 base64 编码的字符。保证不需要字符转义。