NDK 实践-应用签名校验。
应用签名
Android 应用签名是应用打包过程的重要步骤之一,Google 要求所有的应用必须被签名才可以安装到 Android 操作系统中。
应用签名不能保证 APK 不被篡改,只是为了能够校验出 APK 是否被篡改。在系统安装过程中,如果发现 APK 被篡改,安装就会失败。
NDK 应用签名校验
为了相对安全,一些敏感操作往往会使用 Native 的方式来实现。但是别人可以通过 APK 文件获取到我们的 .so
文件,进而使用我们的 .so
。
但是应用签名的证书只有我们持有,我们可以通过 Native 校验签名来判断是否是我们自己的应用,如果不是可以返回错误或直接退出应用。
动手实践
像之前一样 创建一个 Native C++ 模板项目
项目准备
查看证书指纹:
新建的 Android 项目,默认的签名证书在用户根目录的 .android 目录中 ~/.android/debug.keystore
Java 获取证书指纹
public class SignatureUtil {
public static String getSignatureStr(Context context) {
Signature signature = getSignature(context);
byte[] cert = signature.toByteArray();
try {
MessageDigest md5 = MessageDigest.getInstance("MD5");
MessageDigest sha1 = MessageDigest.getInstance("SHA1");
MessageDigest sha256 = MessageDigest.getInstance("SHA256");
byte[] md5Key = md5.digest(cert);
byte[] sha1Key = sha1.digest(cert);
byte[] sha256Key = sha256.digest(cert);
return String.format("MD5: %s/n/nSHA1: %s/n/nSHA-256: %s",
byteArrayToString(md5Key),
byteArrayToString(sha1Key),
byteArrayToString(sha256Key)
);
} catch (Exception e) {
return "";
}
}
public static Signature getSignature(Context argContext) {
Signature signature = null;
try {
String packageName = argContext.getPackageName();
PackageManager packageManager = argContext.getPackageManager();
PackageInfo packageInfo = packageManager.getPackageInfo(packageName, GET_SIGNATURES);
Signature[] signatures = packageInfo.signatures;
signature = signatures[0];
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return signature;
}
private static String byteArrayToString(byte[] array) {
StringBuilder hexString = new StringBuilder();
for (int i = 0; i < array.length; i++) {
String appendString = Integer.toHexString(0xFF & array[i]).toUpperCase();
if (appendString.length() == 1)
hexString.append("0");
hexString.append(appendString);
if(i < array.length - 1)
hexString.append(":");
}
return hexString.toString();
}
}
Native 获取证书指纹
这里用到了 HASH 算法, Android NDK JNI 入门笔记-day04-NDK实现Hash算法
jbyteArray getSignatureByte(JNIEnv *env, jobject context); void hashByteArray(HASH type, const void* data, size_t numBytes, char* resultData); void formatSignature(char* data, char* resultData); extern "C" JNIEXPORT jstring JNICALL Java_com_ihubin_ndkjni_NativeUtil_getSignature(JNIEnv *env, jclass clazz, jobject context) { jbyteArray cert_byteArray = getSignatureByte(env, context); jsize size = env->GetArrayLength(cert_byteArray); jbyte* jbyteArray = new jbyte[size]; env->GetByteArrayRegion(cert_byteArray, 0, size, jbyteArray); char certMD5[128] = {0}; hashByteArray(HASH_MD5, jbyteArray, size, certMD5); char certSHA1[128] = {0}; hashByteArray(HASH_SHA1, jbyteArray, size, certSHA1); char certSHA256[128] = {0}; hashByteArray(HASH_SHA256, jbyteArray, size, certSHA256); LOGD("MD5: %s", certMD5); LOGD("SHA1: %s", certSHA1); LOGD("SHA256: %s", certSHA256); char resultStr[1000] = {0}; strcat(resultStr, "MD5: "); strcat(resultStr, certMD5); strcat(resultStr, "/n/nSHA1: "); strcat(resultStr, certSHA1); strcat(resultStr, "/n/nSHA256: "); strcat(resultStr, certSHA256); return env->NewStringUTF(resultStr); } // Native 从 Context 中获取签名 jbyteArray getSignatureByte(JNIEnv *env, jobject context) { // Context 的类 jclass context_clazz = env->GetObjectClass(context); // 得到 getPackageManager 方法的 ID jmethodID methodID_getPackageManager = env->GetMethodID(context_clazz, "getPackageManager", "()Landroid/content/pm/PackageManager;"); // 获得 PackageManager 对象 jobject packageManager = env->CallObjectMethod(context, methodID_getPackageManager); // 获得 PackageManager 类 jclass packageManager_clazz=env->GetObjectClass(packageManager); // 得到 getPackageInfo 方法的 ID jmethodID methodID_getPackageInfo=env->GetMethodID(packageManager_clazz,"getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;"); // 得到 getPackageName 方法的 ID jmethodID methodID_getPackageName = env->GetMethodID(context_clazz,"getPackageName", "()Ljava/lang/String;"); // 获得当前应用的包名 jobject application_package_obj = env->CallObjectMethod(context, methodID_getPackageName); jstring application_package = static_cast<jstring>(application_package_obj); const char* package_name = env->GetStringUTFChars(application_package, 0); LOGD("packageName: %s", package_name); // 获得 PackageInfo jobject packageInfo = env->CallObjectMethod(packageManager, methodID_getPackageInfo, application_package, 64); jclass packageinfo_clazz = env->GetObjectClass(packageInfo); // 获取签名 jfieldID fieldID_signatures = env->GetFieldID(packageinfo_clazz, "signatures", "[Landroid/content/pm/Signature;"); jobjectArray signature_arr = (jobjectArray)env->GetObjectField(packageInfo, fieldID_signatures); // Signature 数组中取出第一个元素 jobject signature = env->GetObjectArrayElement(signature_arr, 0); // 读 signature 的 ByteArray jclass signature_clazz = env->GetObjectClass(signature); jmethodID methodID_byteArray = env->GetMethodID(signature_clazz, "toByteArray", "()[B"); jobject cert_obj = env->CallObjectMethod(signature, methodID_byteArray); jbyteArray cert_byteArray = static_cast<jbyteArray>(cert_obj); return cert_byteArray; } // 获得签名的 MD5 SHA1 SHA256 void hashByteArray(HASH type, const void* data, size_t numBytes, char* resultData){ if(type == HASH_MD5) { MD5 md5; std::string md5String = md5(data, numBytes); int len = md5String.length()+1; char * tabStr = new char [md5String.length()+1]; strcpy(tabStr, md5String.c_str()); formatSignature(tabStr, resultData); } else if(type == HASH_SHA1) { SHA1 sha1; std::string sha1String = sha1(data, numBytes); char * tabStr = new char [sha1String.length()+1]; strcpy(tabStr, sha1String.c_str()); formatSignature(tabStr, resultData); } else if(type == HASH_SHA256) { SHA256 sha256; std::string sha256String = sha256(data, numBytes); char * tabStr = new char [sha256String.length()+1]; strcpy(tabStr, sha256String.c_str()); formatSignature(tabStr, resultData); } } // 格式化输出 void formatSignature(char* data, char* resultData) { int resultIndex = 0; int length = strlen(data); for(int i = 0; i < length; i++) { resultData[resultIndex] = static_cast<char>(toupper(data[i])); if(i % 2 == 1 && i != length -1) { resultData[resultIndex+1] = ':'; resultIndex+=2; } else { resultIndex++; } } }
最终效果
至此,我们已经学会了在 Android 项目中 Native 进行签名校验,应用安全提升了。
代码:
NDKJNIday05
参考资料:
Oracle – JNI Types and Data Structures
获取Android应用签名的几种方式
签名校验通过NDK实现
原文
http://www.ihubin.com/blog/android-ndk-jni-basic-day05/
本站部分文章源于互联网,本着传播知识、有益学习和研究的目的进行的转载,为网友免费提供。如有著作权人或出版方提出异议,本站将立即删除。如果您对文章转载有任何疑问请告之我们,以便我们及时纠正。PS:推荐一个微信公众号: askHarries 或者qq群:474807195,里面会分享一些资深架构师录制的视频录像:有Spring,MyBatis,Netty源码分析,高并发、高性能、分布式、微服务架构的原理,JVM性能优化这些成为架构师必备的知识体系。还能领取免费的学习资源,目前受益良多

转载请注明原文出处:Harries Blog™ » Android NDK JNI 入门笔记-day05-NDK应用签名校验