將關(guān)鍵字段打包到so中

需求:項目中某些關(guān)鍵字段放在java文件中不安全,需要將其放在so文件中,使用java native方法調(diào)用so中的方法,由于so是不能被反編譯的,所以會相對安全。但如果有人知道了so中的方法名,他會模擬對應(yīng)的包環(huán)境,調(diào)用里面的方法,同樣也可以獲取字段信息,所以so中要對apk的簽名進行驗證,如果是指定的簽名,才返回字段值。而簽名文件一般不會泄露給他人。
AS版本3.4.2,
步驟:

1. 生成so文件,并在java代碼中調(diào)用so文件中的方法。

在原來項目上進行編譯的話會比較慢,而且build.gradle文件會修改多次,很是麻煩。所以,新建個Native C++項目,利用這個項目生成so文件。創(chuàng)建步驟好像和之前不太一樣。沒有了support c++這個選項。如下:
New Project,選擇Native C++


image.png

指定項目名、包名。就起名為Native吧。


image.png

image.png

一切默認,最后點擊finish創(chuàng)建項目。
創(chuàng)建成功后,注意看這個東西:

image.png

這個cpp中的方法名,是由"Java_包名類名方法名"組成的,也就是說想把native方法放在哪里,這個cpp中的方法名就要與之對應(yīng),不能搞錯了。如:想要把native方法放在原來項目的aaa/bbb/ccc/utils/JNIToos.java中,那就得這樣寫了
image.png

對應(yīng)的要修改cpp中的方法名
image.png

關(guān)于CMakeLists.txt只需要關(guān)注兩點:
image.png

native-lib是要和JNITools.java中的這個對應(yīng)


image.png

native-lib.cpp就是相對于native-lib.cpp的路徑。


image.png

需要在app/build.gradle中加上


image.png

以適配不同的cpu架構(gòu)。
在Activity中調(diào)用JNITools.stringFromJNI()方法,設(shè)置給TextView
運行結(jié)果:


image.png

運行成功后,下面需要在cpp中實現(xiàn)具體邏輯了。
獲取當(dāng)前apk簽名,如果和原有項目的apk簽名一致,則返回字段信息。

獲取原有項目的簽名(必須由簽名文件打包后再調(diào)用該方法)

private String getSign() {
    String sign = null;
    try {
        PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNATURES);
        Signature[] signatures = packageInfo.signatures;
        sign = signatures[0].toCharsString();
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
    }
    return sign;
}

getSign()方法會生成一個長長長長長的由0-9和a-f組成的字符串。在so中會將獲取到當(dāng)前apk的簽名后和這個字符串作對比,如果相等,則apk是由我們自己的簽名文件簽過名的,這時可以返回所需信息,否則返回error。

下面是cpp中獲取簽名的方法。
注意多了一個參數(shù)jclass jclazz,且,java native方法需要傳入context。

#include <jni.h>
#include <string>


const char* APP_ID = "aaaaa";
const char* SECRET_KEY = "bbbbbbbbbbbbbbbbbbbbbbb";
//應(yīng)用簽名
const char* RELEASE_SIGN = "30a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e0630a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e0630a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e06";

extern "C" JNIEXPORT jstring JNICALL
Java_aaa_bbb_ccc_utils_JNITools_stringFromJNI
(JNIEnv *env, jclass jclazz, jobject contextObject){

   jclass native_class = env->GetObjectClass(contextObject);
   jmethodID pm_id = env->GetMethodID(native_class, "getPackageManager", "()Landroid/content/pm/PackageManager;");
   jobject pm_obj = env->CallObjectMethod(contextObject, pm_id);
   jclass pm_clazz = env->GetObjectClass(pm_obj);
// 得到 getPackageInfo 方法的 ID
   jmethodID package_info_id = env->GetMethodID(pm_clazz, "getPackageInfo","(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
   jclass native_classs = env->GetObjectClass(contextObject);
   jmethodID mId = env->GetMethodID(native_classs, "getPackageName", "()Ljava/lang/String;");
   jstring pkg_str = static_cast<jstring>(env->CallObjectMethod(contextObject, mId));
// 獲得應(yīng)用包的信息
   jobject pi_obj = env->CallObjectMethod(pm_obj, package_info_id, pkg_str, 64);
// 獲得 PackageInfo 類
   jclass pi_clazz = env->GetObjectClass(pi_obj);
// 獲得簽名數(shù)組屬性的 ID
   jfieldID signatures_fieldId = env->GetFieldID(pi_clazz, "signatures", "[Landroid/content/pm/Signature;");
   jobject signatures_obj = env->GetObjectField(pi_obj, signatures_fieldId);
   jobjectArray signaturesArray = (jobjectArray)signatures_obj;
   jsize size = env->GetArrayLength(signaturesArray);
   jobject signature_obj = env->GetObjectArrayElement(signaturesArray, 0);
   jclass signature_clazz = env->GetObjectClass(signature_obj);
   jmethodID string_id = env->GetMethodID(signature_clazz, "toCharsString", "()Ljava/lang/String;");
   jstring str = static_cast<jstring>(env->CallObjectMethod(signature_obj, string_id));
   char *c_msg = (char*)env->GetStringUTFChars(str,0);
   //return str;
   if(strcmp(c_msg,RELEASE_SIGN)==0)//簽名一致  返回合法的 api key,否則返回錯誤
   {
       return (env)->NewStringUTF(APP_ID);
   }else
   {
       return (env)->NewStringUTF("error");
   }
}

public static native String stringFromJNI(Object contextObject);

運行:


image.png

由于新建的Native項目沒有使用原項目的簽名文件簽過名,故,返回error。
到此為止已經(jīng)在Native項目中實現(xiàn)了驗證apk簽名并獲取關(guān)鍵字段功能.
AS編譯時會自動把生成的so打包到apk中。So文件生成的路徑是: Native\app\build\intermediates\cmake\。但在我的AS中app\build\intermediates目錄打不開,


image.png

不知道是不是AS升級后故意這樣的。在對應(yīng)的windows目錄下則可以看到。


image.png

接下來將JNITools.java和生成的so文件拷貝到原有項目中去。
我們新建個項目MyApplication,假裝MyApplication就是我們原有的項目。


image.png

在app/build.gradle中設(shè)置jniLibs的路徑
image.png

運行
image.png

由于MyApplication沒有使用原有項目的簽名文件簽名,故返回了error,cpp中的方法是得到了正常調(diào)用的。
當(dāng)然也可以將so文件放在src/main/jniLibs中,如下圖


image.png

更改jniLibs的配置


image.png

下面問題又來了
由于so中的方法對應(yīng)java中的固定的包名+類名+方法名,故如果將native方法放到項目中,一旦項目包結(jié)構(gòu)發(fā)生改變,則還需要保留原有的native方法所在的包。
考慮到上面原因,解決方法有兩種:

1.可以另外新建一個module,將so和native方法放到該module中。在主module(app module)中調(diào)用新建module中的native方法。
2.將so文件和native方法打包成一個aar,并引入到app項目中來。
新建module


image.png

image.png
  1. 將app中的jniLibs移動到j(luò)nilib下的main目錄下,
  2. 將aaa/bbb/ccc/utils/JNITools.java復(fù)制到j(luò)nilib中,
  3. 同樣的在jnilib中的build.gradle中的配置


    image.png
  4. 刪除app的build.gradle中的該配置。在dependencies下添加對jnilib的依賴
  5. 重新build項目,運行。
    到此已經(jīng)將該功能從app中移動到了新的module中。
打包生成aar文件,

選擇Gradle,雙擊build,


image.png

然后在D:\AndroidStudioProjects\MyApplication\jnilib\build\outputs\aar目錄下生成了兩個aar文件。


image.png

這時aar就可以放在項目中使用了,步驟如下:
aar放在libs中,build.gradle中加入

image.png

,將aar當(dāng)作一個倉庫使用
至此,從cpp文件到so到module再到aar流程就走完了.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容