简而言之,如果您知道如何反编译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:
- 解压 myapp.apk 文件,或者在 APK 上右键点击选择
Unzip here
。会出现 classes.dex
文件。
- 使用 dex2jar 工具将
classes.dex
转换为 JAR 文件。执行命令 dex2jar.sh classes.dex
后,会得到 classes_dex2jar.jar
文件。
使用一些 Java 反编译器,比如 JD-GUI,打开 classes_dex2jar.jar
文件中的 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文档中,在安全性和设计部分,他们建议使用以下方法来保护你的Google Play公钥:
不要将您的公钥作为纯字符串嵌入到任何代码中,以使其免受恶意用户和黑客的攻击。相反,从多个片段构造字符串,或使用位运算(例如,与其他字符串进行异或运算)来隐藏实际密钥。密钥本身并不是机密信息,但您不希望使黑客或恶意用户轻松替换公钥为另一个密钥。
好的,让我们试试这个方法:
@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()
,我们仍然可以运行反编译代码并查看它会产生什么内容。或者逐步进行调试。