本文只是对最近工作的一些小结,方便以后的查询。
关闭日志的打印
关闭打印的日志,防止日志中的调试信息被看到。如果在网络框架中使用了日志,那就更加需要关闭了。
代码混淆
代码混淆是最基本的做法,至少能让App在被反编译之后不那么顺畅地阅读源码。
当然,即使是混淆之后的代码,只要花费一定的时间,仍然是可以厘清代码之间的逻辑。
混淆字典的使用
如果对代码中的类名、变量名变成a、b、c还不爽,那可以自定义一些字符来替代它们。此时需要用到混淆字典。
推荐一个github上的库:https://github.com/ysrc/AndroidObfuseDictionary
在proguard-rules.pro中添加混淆字典的配置
#混淆字典-obfuscationdictionary dic.txt-classobfuscationdictionary dic.txt-packageobfuscationdictionary dic.txt复制代码
native层校验
除了混淆之外,App还需要防止被别人进行二次打包。
由于release签名的唯一性,可以考虑在native层进行签名的校验。如果签名不正确,直接让App crash。
我们重写JNI_OnLoad()函数,在此处进行校验。
jint JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv *env = NULL; if (vm->GetEnv((void **) &env, JNI_VERSION_1_4) != JNI_OK) { return JNI_ERR; } if (verifySign(env) == JNI_OK) { return JNI_VERSION_1_4; } LOGE("签名不一致!"); return JNI_ERR;}复制代码
verifySign()函数会执行真正的校验,将存放在native层的签名字符串和当前App的签名进行比对。
static int verifySign(JNIEnv *env) { // Application object jobject application = getApplication(env); if (application == NULL) { return JNI_ERR; } // Context(ContextWrapper) class jclass context_clz = env->GetObjectClass(application); // getPackageManager() jmethodID getPackageManager = env->GetMethodID(context_clz, "getPackageManager", "()Landroid/content/pm/PackageManager;"); // android.content.pm.PackageManager object jobject package_manager = env->CallObjectMethod(application, getPackageManager); // PackageManager class jclass package_manager_clz = env->GetObjectClass(package_manager); // getPackageInfo() jmethodID getPackageInfo = env->GetMethodID(package_manager_clz, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;"); // context.getPackageName() jmethodID getPackageName = env->GetMethodID(context_clz, "getPackageName", "()Ljava/lang/String;"); // call getPackageName() and cast from jobject to jstring jstring package_name = (jstring) (env->CallObjectMethod(application, getPackageName)); // PackageInfo object jobject package_info = env->CallObjectMethod(package_manager, getPackageInfo, package_name, 64); // class PackageInfo jclass package_info_clz = env->GetObjectClass(package_info); // field signatures jfieldID signatures_field = env->GetFieldID(package_info_clz, "signatures", "[Landroid/content/pm/Signature;"); jobject signatures = env->GetObjectField(package_info, signatures_field); jobjectArray signatures_array = (jobjectArray) signatures; jobject signature0 = env->GetObjectArrayElement(signatures_array, 0); jclass signature_clz = env->GetObjectClass(signature0); jmethodID toCharsString = env->GetMethodID(signature_clz, "toCharsString", "()Ljava/lang/String;"); // call toCharsString() jstring signature_str = (jstring) (env->CallObjectMethod(signature0, toCharsString)); // release env->DeleteLocalRef(application); env->DeleteLocalRef(context_clz); env->DeleteLocalRef(package_manager); env->DeleteLocalRef(package_manager_clz); env->DeleteLocalRef(package_name); env->DeleteLocalRef(package_info); env->DeleteLocalRef(package_info_clz); env->DeleteLocalRef(signatures); env->DeleteLocalRef(signature0); env->DeleteLocalRef(signature_clz); const char *sign = env->GetStringUTFChars(signature_str, NULL); if (sign == NULL) { LOGE("分配内存失败"); return JNI_ERR; } int result = strcmp(sign, RELEASE_SIGN); // 使用之后要释放这段内存 env->ReleaseStringUTFChars(signature_str, sign); env->DeleteLocalRef(signature_str); if (result == 0) { // 签名一致 return JNI_OK; } return JNI_ERR;}复制代码
JNI_OnLoad()函数是只有使用了JNI,才会被调用。为了确保App一启动就能够进行验证签名。
我还写了一个方法:
jstring Java_io_merculet_core_utils_EncryptUtils_nativeCheck(JNIEnv *env, jclass type) { return env->NewStringUTF("Security str from native.");}复制代码
在App的MainActivity的onCreate()中使用。
EncryptUtils.nativeCheck()复制代码
EncryptUtils是一个工具类,用于调用native层的方法。
/** * @version V1.0 <描述当前版本功能> * @FileName: io.merculet.core.utils.EncryptUtils.java * @author: Tony Shen * @date: 2018-05-21 20:53 */public class EncryptUtils { // Used to load the 'native-lib' library on application startup. static { System.loadLibrary("codec"); } public static native String nativeCheck(); ...}复制代码 描述当前版本功能>
关键的密码不要明文传输
例如登录、支付相关的密码,最好不要明文传输,需要进行加密。如果在Java层来做加密容易被反编译,那么可以考虑使用C++来实现。
总结
这些措施也只是冰山一角,因为安全一直是永恒的话题。我们还可以考虑使用加壳、反动态调试等等。
参考资料:
- http://qbeenslee.com/article/about-wandoujia-proguard-config/
- https://github.com/Qrilee/AndroidObfuseDictionary
- https://www.jianshu.com/p/2576d064baf1
Java与Android技术栈:每周更新推送原创技术文章,欢迎扫描下方的公众号二维码并关注,期待与您的共同成长和进步。