개발 관련/Android

NDK 를 이용하여 Signaiture (서명) 값 가져오기.

snoworca 2015. 4. 8. 18:40


앱의 위변조를 방지하기 위하여 서명 값을 가져와서 서버에 미리 등록된 값과 비교하는 과정이 필요하다.

하지만, 안드로이드 Java 코드상으로 제공되는 API 를 활용하는 것은 안전하지 못하다.

(안드로이드 자바 코드상에서 서명 값 가져오기 : http://www.dev.re.kr/70)


그렇기 때문에 JNI 를 활용해야 한다. context 를 인자값으로 받아서 jni 에서 제공되는 리플렉션을 사용하여 SHA1 으로 변환된 서명 값을 가져오는 코드를 첨부한다. 


주의할 점은 아래 코드는 C++ 로 작성되었기 때문에 C 에서 사용하려면 약간의 수정이 필요하다. 

이를테면 


env->GetObjectClass(context);


를 C로 표현 하려면 


(*env)->GetObjectClass(env, context);


로 변환해야 한다.



스크롤바 때문에 코드를 보기 불편할 수 있으므로 파일도 추가한다.



getSignaiture.txt


// Context 를 인자값을 받아서 Signature 의 값을 얻는다.
char* getSignaiture(JNIEnv *env, jobject context) {
          jstring packageName;
          jobject packageManagerObj;
          jobject packageInfoObj;
          jclass contextClass =  env->GetObjectClass( context);
               jmethodID getPackageNameMid = env->GetMethodID( contextClass, "getPackageName", "()Ljava/lang/String;");
               jmethodID getPackageManager =  env->GetMethodID( contextClass, "getPackageManager", "()Landroid/content/pm/PackageManager;");

          jclass packageManagerClass = env->FindClass("android/content/pm/PackageManager");
               jmethodID getPackageInfo = env->GetMethodID( packageManagerClass, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");

          jclass packageInfoClass = env->FindClass("android/content/pm/PackageInfo");
               jfieldID signaturesFid = env->GetFieldID( packageInfoClass, "signatures", "[Landroid/content/pm/Signature;");

          jclass signatureClass = env->FindClass("android/content/pm/Signature");
               jmethodID signatureToByteArrayMid = env->GetMethodID( signatureClass, "toByteArray", "()[B");

          jclass messageDigestClass = env->FindClass("java/security/MessageDigest");
               jmethodID messageDigestUpdateMid = env->GetMethodID( messageDigestClass, "update", "([B)V");
               jmethodID getMessageDigestInstanceMid  = env->GetStaticMethodID( messageDigestClass, "getInstance", "(Ljava/lang/String;)Ljava/security/MessageDigest;");
               jmethodID digestMid = env->GetMethodID( messageDigestClass,"digest", "()[B");

          jclass base64Class = env->FindClass("android/util/Base64");
               jmethodID encodeToStringMid = env->GetStaticMethodID( base64Class,"encodeToString", "([BI)Ljava/lang/String;");

          packageName =  (jstring)env->CallObjectMethod( context, getPackageNameMid);

          packageManagerObj = env->CallObjectMethod(context, getPackageManager);
          // PackageManager.GET_SIGNATURES = 0x40
          packageInfoObj = env->CallObjectMethod( packageManagerObj,getPackageInfo, packageName, 0x40);
          jobjectArray signatures = (jobjectArray)env->GetObjectField( packageInfoObj, signaturesFid);
          //int signatureLength =  env->GetArrayLength(signatures);
          jobject signatureObj = env->GetObjectArrayElement(signatures, 0);
          jobject messageDigestObj  = env->CallStaticObjectMethod(messageDigestClass, getMessageDigestInstanceMid, env->NewStringUTF("SHA1"));
          env->CallVoidMethod(messageDigestObj, messageDigestUpdateMid, env->CallObjectMethod( signatureObj,signatureToByteArrayMid));

          // Base64.DEFAULT = 0 그렇기 때문에 맨 마지막 인자값은 0이다.
          jstring signatureHash = (jstring)env->CallStaticObjectMethod( base64Class, encodeToStringMid,env->CallObjectMethod( messageDigestObj, digestMid, signatureObj), 0);

          return (char*)env->GetStringUTFChars(signatureHash,0);
}