Android App安全防範措施的小結

本文只是對最近工做的一些小結,方便之後的查詢。java

飛奔.jpg

關閉日誌的打印

關閉打印的日誌,防止日誌中的調試信息被看到。若是在網絡框架中使用了日誌,那就更加須要關閉了。android

代碼混淆

代碼混淆是最基本的作法,至少能讓App在被反編譯以後不那麼順暢地閱讀源碼。git

固然,即便是混淆以後的代碼,只要花費必定的時間,仍然是能夠釐清代碼之間的邏輯。github

混淆字典的使用

若是對代碼中的類名、變量名變成a、b、c還不爽,那能夠自定義一些字符來替代它們。此時須要用到混淆字典。安全

推薦一個github上的庫:https://github.com/ysrc/AndroidObfuseDictionarybash

使用混淆字典以後反編譯的效果.png

在proguard-rules.pro中添加混淆字典的配置網絡

#混淆字典
-obfuscationdictionary dic.txt
-classobfuscationdictionary dic.txt
-packageobfuscationdictionary dic.txt
複製代碼

native層校驗

除了混淆以外,App還須要防止被別人進行二次打包。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++來實現。

總結

這些措施也只是冰山一角,由於安全一直是永恆的話題。咱們還能夠考慮使用加殼、反動態調試等等。

參考資料:

  1. http://qbeenslee.com/article/about-wandoujia-proguard-config/
  2. https://github.com/Qrilee/AndroidObfuseDictionary
  3. https://www.jianshu.com/p/2576d064baf1

Java與Android技術棧:每週更新推送原創技術文章,歡迎掃描下方的公衆號二維碼並關注,期待與您的共同成長和進步。

相關文章
相關標籤/搜索