現狀:很多apk會被黑客反編譯成smali文件,然後修改或植入惡意代碼後重新編譯成apk發佈到市場。
解決要點:
1,代碼混淆。可使用高級商用混淆工具DexGuard。(此法容易被攻破)
2,apk運行時進行簽名驗證和crc校驗碼驗證。(此法的驗證代碼容易被黑客註銷掉,使之不起作用)
個人總結的比較安全的解決方式:(這裏不考慮網絡通信被中間人攔截情況,後面會給出防止網絡攔截的ssl驗證方法)
1)服務器保存發佈時apk的加密的簽名和加密的crc校驗碼。
2)客戶端的每次網絡訪問請求都需上傳與服務器相同加密方式加密的apk簽名和crc碼。(這裏使用so庫獲取加密的簽名和crc碼,比較安全。甚至可以考慮對so庫加殼保護)
這樣一來黑客不能註銷掉驗證,因爲服務必須接收到正確的簽名和crc碼才允許繼續通信。黑客也不能打印我們加密後的crc碼,因爲加入打印代碼會使crc發生改變,從而得到錯誤的crc碼。
漏洞:黑客獲得apk原始crc碼,並且攻破加殼的so庫。(這難度非常大!!)
備註:crc指classes.dex的校驗碼。
部分實例代碼:
package com.test;
import java.util.HashMap;
import java.util.Map;
import android.app.Activity;
import android.os.Bundle;
public class TestActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
LoginServelet sv=new LoginServelet();
Map<String,Object> params=new HashMap<String,Object>();
params.put("userName", "test");
params.put("password", "123");
params.put("appSignCiphered", ProguardNavtive.getCipheredAppSign());
params.put("crcCiphered",ProguardNavtive.getCipheredAppCrc());
sv.get(params);
}
}
package com.test;
import java.util.Map;
public class LoginServelet {
public void get(Map<String,Object> params){
String userName=(String) params.get("userName");
String password=(String) params.get("password");
String appSignCiphered=(String) params.get("appSignCiphered");
String crcCiphered=(String) params.get("crcCiphered");
/**
* upload to server check.
*/
}
}
package com.test;
/**
* 可以考慮對so庫加殼,增加破解難度。
*
* @author lchli
*
*/
public class ProguardNavtive {
public static native String getCipheredAppSign();
public static native String getCipheredAppCrc();
}
package com.test;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
public class AppUtil {
public static String getAppSign(Context context){
PackageManager pm = context.getPackageManager();
try {
PackageInfo info = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
Signature sig = info.signatures[0];
return calcSHA1(sig.toByteArray());
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
private static String calcSHA1(byte[] sig) throws Exception{
MessageDigest digest = MessageDigest.getInstance("SHA1");
digest.update(sig);
byte[] sigHash = digest.digest();
return bytesToHex(sigHash);
}
private static String bytesToHex(byte[] bytes){
final char[] hexArray="0123456789ABCDEF".toCharArray();
char[] hexChars = new char[bytes.length * 2];
for ( int j = 0; j < bytes.length; j++ ) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
public static long getAppCrc(Context context){
try {
ZipFile zip = new ZipFile(context.getPackageCodePath());
ZipEntry entry = zip.getEntry("classes.dex");
return entry.getCrc();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
throw new RuntimeException(e);
}
}
}