【问题标题】:Android In App Billing: securing application public keyAndroid In App Billing:保护应用程序公钥
【发布时间】:2012-12-30 10:31:14
【问题描述】:

来自 Android In App Billing 版本 3 (TrivialDrive) 示例应用程序附带 sdk

MainActivity.java

/* base64EncodedPublicKey should be YOUR APPLICATION'S PUBLIC KEY
 * (that you got from the Google Play developer console). This is not your
 * developer public key, it's the *app-specific* public key.
 *
 * Instead of just storing the entire literal string here embedded in the
 * program,  construct the key 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 we don't
 * want to make it easy for an attacker to replace the public key with one
 * of their own and then fake messages from the server.
 */
String base64EncodedPublicKey = "CONSTRUCT_YOUR_KEY_AND_PLACE_IT_HERE";

好吧,我不确定我是否了解此安全措施。我知道如何从 Google Play Developer Console 获取应用程序公钥(已经是 base 64 编码)。

我不明白的是这部分

 /* Instead of just storing the entire literal string here embedded in the
 * program,  construct the key at runtime from pieces or
 * use bit manipulation (for example, XOR with some other string) to hide
 * the actual key
 */

据我所知,这个公钥是一个常量字符串,它是在应用程序上传过程中由谷歌提供的。

我们如何使用任何位操作过程以编程方式创建相同的密钥?以前有人做过吗?有没有关于如何做到这一点的示例代码?

【问题讨论】:

  • 为什么要隐藏 public 密钥,因为它是公开的?
  • @GameScripting 好吧,这是我问自己的第一个问题。但我认为我们不是在谈论密钥对(私钥/公钥对)中的公钥。应用程序公钥可能是不同.. 太糟糕了,谷歌没有太多关于它的文档,特别是在他们自己认为这是一个安全威胁之后..
  • 'A string' XOR 'secret' 变成 'Another string'。 'Another string' 再次对相同的 'secret' 进行异或运算,它变成了 'A string'。这是一个 XOR 用例。
  • @Krishnabhadra 它有助于防止攻击者轻松地用他自己的公钥替换您的公钥并使用他自己的服务器(当然使用他的私钥)验证购买,因此执行此类技巧会使攻击者正常工作有点难。当然,为了真正缓解这个问题,鼓励在您自己的服务器上进行二次验证......

标签: android security bit-manipulation in-app-billing public-key


【解决方案1】:

类似这样的:

String Base64EncodedPublicKey key = "Ak3jfkd" + GetMiddleBit() + "D349824";

String Base64EncodedPublicKey key = 
         DecrementEachletter("Bl4kgle") + GetMiddleBit() + ReverseString("D349824");

或任何不将密钥放在 base64 纯文本中的单个字符串中。可能也不将密钥存储在 base64 中的东西也是一个好主意,因为原始的 base64 文本片段很容易发现。

这不是保护密钥的特别好的方法。但它可以防止一些微不足道的攻击,即有人只是在您的 APK 中搜索文字字符串,寻找看起来像 base64 编码的公钥的东西。至少你让#$#$ers 工作了一点。

如果坏人识别了你的公钥,他们可能会做坏事。显然,谷歌似乎是这么认为的。我可以猜到这一步的作用,但我不确定我是否真的想在一个开放的论坛上推测这一点,并给任何人任何想法。不过你想这样做。

基本情节摘要是,您让某人更难编写以编程方式对应用程序进行 LVL 处理的应用程序。

假设任何这样做的人都可以通过破解 20 或 30,000 个 Android 应用程序并重新发布它们来谋生。很有可能,我想他们不会再花十分钟的时间将您的应用程序添加到已被程序破坏的 20,000 个 Android 应用程序的列表中,如果他们实际上需要做一些手动工作的话。除非您有顶级应用程序。然后战斗可能是无休止的,而且可能最终是徒劳的。

将密钥分成连续的块(如另一个答案中所建议的)可能还不够好。因为密钥将在 APK 的字符串常量表中以连续的字符串结尾。用程序太容易找到了。

【讨论】:

  • 如果应用是开源的怎么办?这真的值得吗?还有其他方法可以“保护”公钥吗?
  • 确实没有任何方法可以“保护”公钥。如果您的软件可以从存储在 APK 中的数据重建密钥,那么仅读取 APK 的软件也可以。很有可能,除了对密钥进行微不足道的模糊之外,做任何事情都不值得。 Kiddie 黑客破坏了比您想出的更强大的软件保护方案。如果您阻止使用从逐字复制 SDK 代码的任何 APK 中提取密钥的自动化软件,那么您可能已经完成了您的职责。
  • 关于安全堆栈交换的另一个有用的答案! security.stackexchange.com/questions/33686/…
  • @ibgib:如果您的应用程序是开源的,那么您将无法在 Google Play 上进行购买,因为这个以及其他一系列原因,例如(例如)APK未被 Google Play 安装程序标记为已安装。如果您想从开源应用程序中捐款,那么您最好使用 PayPal 托管的信用卡付款。他们也可以以低得多的费用进行微交易。 (服务条款禁止 Google Play 应用使用替代付款方式,以防您认为那里有一个好主意的萌芽。不要这样做!)
  • @RobinDavies 我们已经摆脱了任何涉及应用内购买的事情。不过感谢您的提醒。
【解决方案2】:

另一种方法是对键进行一些基本的转换。

// Replace this with your encoded key.
String base64EncodedPublicKey = "";

// Get byte sequence to play with.
byte[] bytes = base64EncodedPublicKey.getBytes();

// Swap upper and lower case letters.
for (int i = 0; i < bytes.length; i++) {
    if(bytes[i] >= 'A' && bytes[i] <= 'Z')
        bytes[i] = (byte)( 'a' + (bytes[i] - 'A'));
    else if(bytes[i] >= 'a' && bytes[i] <= 'z')
        bytes[i] = (byte)( 'A' + (bytes[i] - 'a'));
}

// Assign back to string.
base64EncodedPublicKey = new String( bytes );

所以想法是将原始密钥作为base64EncodedPublicKey 并运行上面的代码,它将交换大小写字母并将结果放回base64EncodedPublicKey。然后,您可以从调试器复制结果并将其作为原始 base64EncodedPublicKey 值粘贴到代码中。此时,您的密钥将被转换(大小写切换),并在运行时将其修复为正确的大小写,并继续工作。

上面显然是一个相当基本的转码,但你可以更有创意,颠倒 A-Z 的顺序,交换奇数和偶数,交换元音为偶数。这里的问题是,如果我将代码放在上面的 sn-p 中,进行一堆更有趣的转码,然后每个人都将其复制并粘贴到他们的项目中,破解者将很容易能够自己查看和使用转码(来自看这个帖子)!所以你只需要自己想出一些变换。

我特意在两个方向上进行了上述工作(因此,如果您运行两次,您将获得原始值),因为它可以轻松地在您的原始密钥上运行算法。我认为它有点整洁,看起来真正的钥匙以纯文本的形式放在那里,一个随便的破解者可能会尝试切换它,然后当它不起作用时会感到困惑。

【讨论】:

  • ~"从调试器复制结果并将其粘贴到代码中"。为什么是手动步骤?你不能在 Java 中做到这一点吗?
  • 重点是你不希望你的真正的公钥是纯文本的;所以在上面它以纯文本开始;调试器告诉你编码的版本是什么;然后将编码版本粘贴到纯文本版本上。
  • 但是当您将您的公钥传递给 IabHelper 的构造函数时,该密钥无论如何都是您的原始公钥。因此,可以对特定的调用堆栈进行逆向工程。
  • 正确:如果您的目标是使您无法访问密钥,那么您将永远不会成功。它被称为公钥是有原因的。谷歌给出的建议是避免将您的密钥保留为纯文本,因此如上所述进行简单的转换即可实现这一点。它确实不会阻止任何下定决心的人,但它避免了作为字符串文字和一些更基本的攻击向量出现的密钥。
  • 您的解决方案不将密钥作为源代码中的字符串文字涉及吗?
【解决方案3】:

你可以像这样把它分成几块

String piece1 = "SDFGJKGB4UIH234WE/FRT23RSDF/3DFUISDFVWE";
String piece2 = "SDFGJKGB4UIHUISDFVWE";
String piece3 = "BDYASGBDNAWGRET24IYE23das4saGBENWKD";
String piece4 = "432423SDF23R/+SDDS";

mHelper = new IabHelper(this, piece1 + piece2 + piece3 + piece4);

任何类型的操作都可以。

你不能对攻击者完美地隐藏公钥,你只需要操纵字符串来迷惑攻击者一点

您可以添加一些字符串并在需要时将其删除或将其拆分为块。

【讨论】:

  • 如果您使用ProGuard 来混淆您的应用程序,那么这个技巧将毫无用处。 ProGuard 会将它们重新分解为一个字符串。
  • 确实如此。此外,我会警告不要按原样使用 IabHelper。通过构造函数传递密钥(不管它通常是如何动态的)意味着如果攻击者可以识别 IabHelper 类(即使它可能被混淆),那么他知道插入他自己的恶意密钥的确切位置(似乎有点太容易了)
  • @KevinLee 那么你将如何修改 IabHelper?
  • @IgorGanapolsky 将 IabHelper 中的键硬编码为类变量,或者将其准确放在将要使用的位置。或者将密钥拆分为字节数组。至于方法和回调,您确实必须花一些时间来理解它们并重新实现所需的东西。注意硬编码的字符串值,因为它们往往会泄露大量信息;减少它们的描述性,或使用错误代码。可能反转简单的布尔值或将它们替换为与某个编码值的比较等。这样做的目的是使您的代码有所不同。
  • @KevinLee 但这仍然没有意义,因为您仍然会使用布尔值来启用或禁用(如果用户购买或未购买)您的一些应用高级功能,所以它甚至与购买无关/键,但您的功能可以解锁某些东西,您将使用购买 api 的结果(布尔值)
【解决方案4】:

我所做的是将键转换为 char 数组,将其一分为二,然后在需要时重构它,如下所示:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_shop);

    char[] base64KeyByteArray = ArrayUtils.addAll(getPublicKeyChunk1(), getPublicKeyChunk2());

    Log.d(TAG, String.valueOf(base64KeyByteArray));
}

private char[] getPublicKeyChunk1() {
    return new char[]{82, 73, 67, 66, 73, 106, 65, 78, 66, 103, 107, 113, 104, 107,
            105, 71, 57, 119, 79, 66, 65, 81, 69, 70, 65, 65, 79, 67, 65, 81, 56, 65, 77, 73,
            73, 66, 67, 103, 75, 67, 65, 81, 69, 65, 121, 55, 81, 76, 122, 67, 105, 80, 65,
            110, 105, 101, 72, 66, 53, 57};
}

private char[] getPublicKeyChunk2() {
    return new char[]{82, 43, 68, 47, 79, 121, 122, 110, 85, 67, 118, 89, 108, 120, 43, 49,
            80, 100, 67, 108, 55, 90, 57, 103, 119, 57, 87, 78, 79, 111, 53, 101, 80, 71,
            117, 74, 104, 82, 87, 97, 100};
}

【讨论】:

    【解决方案5】:

    Steven Craft's Answer 的帮助下,我从gidds 获得帮助

    这是一个更简洁的代码。 此外,它将数字 (0-9) 替换为 ascii 字符 (37-46),反之亦然。用 Kotlin 编写。

     val string = "Hello World 012345679 %&()"
     fun String.swapCase() = map {
            when {
                it.isUpperCase() -> it.toLowerCase()
                it.isLowerCase() -> it.toUpperCase()
                it.isDigit() -> (37 + (it.toInt() - 48)).toChar()
                it.isDefined() -> if (it.toInt() >= 37 && it.toInt() <= 46) (48 + (it.toInt() - 37)).toChar() else it
                else -> it
            }
        }.joinToString("")
        println(string.swapCase()) // hELLO wORLD %&'()*+,. 0134
    

    使用这个 -> https://edge-developer.github.io/BillingGenerator/ 快速生成所有这些

    【讨论】:

      【解决方案6】:

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

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

      【讨论】:

        【解决方案7】:

        真的有人需要你的私钥吗?我认为整个想法是替换它。恕我直言,任何操作都是无用的。邪恶的人唯一要做的就是用正确的(他自己的密钥)值初始化变量一行begore google API call。

        【讨论】:

          猜你喜欢
          • 2013-11-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-08-25
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-07-14
          相关资源
          最近更新 更多