tl;dr 如果您知道如何反编译 APK,则无论代码多么混乱,您都可以轻松获取密码。不要在 APK 中存储密码,这不安全。
我知道 ProGuard 有一些混淆功能,但我很好奇
关于上述“混淆”技术在编译时的作用,
以及通过查看来弄清楚它会有多难
APK 和/或使用其他更复杂的技术?
我会告诉你它是多么容易。这是一个 Android SSCCE,我们将对其进行反编译:
MyActivity.java:
public class MyActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
TextView text = (TextView) findViewById(R.id.text);
text.setText(getPassword());
}
private String getPassword() {
String pool = "%&/@$()7?=!656sd8KJ%&HDI!!!G98y/&%=?=*^%&ft4%(";
return pool.substring(4, 7) + pool.substring(20, 24) + pool.substring(8, 11);
}
}
main.xml:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
编译并运行后,我们可以在TextView 上看到$()&HDI?=!。
让我们反编译一个 APK:
-
unzip myapp.apk 或右键单击 APK 和 Unzip here。
classes.dex 文件出现。
- 将
classes.dex 转换为带有dex2jar 的JAR 文件。执行dex2jar.sh classes.dex、classes_dex2jar.jar文件后
出现。
-
在classes_dex2jar.jar上使用一些Java反编译器,例如JD-GUI,我们从MyActivity.class检索这样的Java代码:
public class MyActivity extends Activity
{
private String getPassword()
{
return "%&/@$()7?=!656sd8KJ%&HDI!!!G98y/&%=?=*^%&ft4%(".substring(4, 7)
+ "%&/@$()7?=!656sd8KJ%&HDI!!!G98y/&%=?=*^%&ft4%(".substring(20, 24)
+ "%&/@$()7?=!656sd8KJ%&HDI!!!G98y/&%=?=*^%&ft4%(".substring(8, 11);
}
public void onCreate(Bundle paramBundle)
{
super.onCreate(paramBundle);
setContentView(2130903040);
((TextView)findViewById(2131034112)).setText(getPassword());
}
}
ProGuard 帮不上什么忙,代码仍然很容易阅读。
基于以上,我已经可以给你这个问题的答案了:
像下面这样的密码生成功能是否合理
安全吗?
没有。如您所见,它稍微增加了阅读反混淆代码的难度。我们不应该以这种方式混淆代码,因为:
- 它给人一种安全的错觉。
- 这是在浪费开发人员的时间。
- 它会降低代码的可读性。
在官方 Android 文档中,Security and Design 部分中,他们建议这样做以保护您的 Google Play 公钥:
为了保护您的公钥免受恶意用户和黑客的攻击,请不要
将其作为文字字符串嵌入任何代码中。 相反,构建
在运行时从片段中提取字符串或使用位操作(例如,
XOR 与其他字符串)来隐藏实际的密钥。 密钥本身是
不是秘密信息,但你不想让它变得容易
黑客或恶意用户用另一个密钥替换公钥。
好的,让我们试试吧:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
TextView text = (TextView) findViewById(R.id.text);
text.setText(xor("A@NCyw&IHY", "ehge13ovux"));
}
private String xor(String a, String b) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < a.length() && i < b.length(); i++) {
sb.append((char) (a.charAt(i) ^ b.charAt(i)));
}
return sb.toString();
}
在TextView 上提供$()&HDI?=!,很好。
反编译版本:
public void onCreate(Bundle paramBundle)
{
super.onCreate(paramBundle);
setContentView(2130903040);
((TextView)findViewById(2131034112)).setText(xor("A@NCyw&IHY", "ehge13ovux"));
}
private String xor(String paramString1, String paramString2)
{
StringBuilder localStringBuilder = new StringBuilder();
for (int i = 0; (i < paramString1.length()) && (i < paramString2.length()); i++) {
localStringBuilder.append((char)(paramString1.charAt(i) ^ paramString2.charAt(i)));
}
return localStringBuilder.toString();
}
和以前的情况非常相似。
即使我们有极其复杂的函数soStrongObfuscationOneGetsBlind(),我们也总是可以运行反编译的代码,看看它产生了什么。或者一步一步调试。