有时您需要在应用程序中存储密码,例如用于与您自己的服务器通信的用户名/密码。在这些情况下,不可能遵循存储密码的正常过程 - 即散列密码,存储散列,与散列用户输入进行比较 - 因为您没有任何用户输入来比较散列到。密码需要由应用程序本身提供。那么如何保护APK中存储的密码呢?如下所示的密码生成功能是否合理安全?
纯文字:
String password = "$()&HDI?=!";
简单的混淆:
private String getPassword(){
String pool = "%&/@$()7?=!656sd8KJ%&HDI!!!G98y/&%=?=*^%&ft4%(";
return pool.substring(4, 7) + pool.substring(20, 24) + pool.substring(8, 11);
}
我知道ProGuard有一些混淆功能,但我很好奇上面的"混淆"技术在编译时会做什么,以及通过查看APK和/或使用其他更复杂的技术来解决问题有多难?
答案 0 :(得分:15)
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"/>
编译并运行后,我们可以在$()&HDI?=!
上看到TextView
。
让我们反编译APK:
unzip myapp.apk
或右键点击APK和Unzip here
。
出现classes.dex
文件。classes.dex
转换为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();
}
$()&HDI?=!
上的TextView
,好。
反编译版本:
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()
,我们也可以运行反编译代码并查看它产生的内容。或者一步一步地调试它。