Android Studio NDK 入门教程--JNI签名验证防止恶意调用
Posted 祥云湾
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android Studio NDK 入门教程--JNI签名验证防止恶意调用相关的知识,希望对你有一定的参考价值。
概述
根据前面的文章来看,JNI其实只实现了关键代码加密,如果别人拿到了你的Java Native方法定义和对应的so,即可完成对你so里方法的调。因为native 方法和类都是不能混淆的,混淆了方法的函数名就变了,调用的时候就找不到方法了,因此如果反编译APK可以非常容易拿到相关文件和代码。 显然我们需要一些手段来在JNI的验证请求接口的是不是我们的程序。
签名验证的原理
可以用如下图来表明加了验证之后调用JNI的逻辑,用一个isValid
来表明请求的应用是不是我们自己的应用。isValid
通过init
去初始化。
如何判别调用者的有效性
直接有效的方案就是使用签名进行判定,如果你的keystore没有泄漏,第三方破解概率几乎为零。当然这都是相对的,任何防护都会有破绽。大多数第三方的Android SDK也都通过签名来判断申请的key是否用在了你申请的应用上。因此在大多数SDK申请key的时候会让你填写SHA1,因为在程序运行的时候SDK会获取你签名的SHA1去向服务器验证,你申请的appkey和SHA1是否正确。
签名验证的实现
在代码中获取签名的SHA1
先尝试在Java代码中获取签名的SHA1
private String getCertSHA1(Context context)
try
PackageManager packageManager=context.getPackageManager();
String packageName=context.getPackageName();
PackageInfo pis = packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
Signature[] signs = pis.signatures;
Signature sign=signs[0];
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
byte[] signBytes=sign.toByteArray();
ByteArrayInputStream byteIn=new ByteArrayInputStream(signBytes);
X509Certificate cert = (X509Certificate) certFactory.generateCertificate(byteIn);
MessageDigest sha1=MessageDigest.getInstance("SHA1");
byte[] certByte=cert.getEncoded();
byte[] bs=sha1.digest (certByte);
return toHex(bs);
catch (CertificateException e)
e.printStackTrace();
catch (Exception e)
e.printStackTrace();
return null;
char[] cs=new char[16];
for (int i=0;i < 10;i++)
cs[i] = (char) ('0' + i);
for (int i=10;i < 16;i++)
cs[i] = (char) ('A' + i - 10);
String toHex(byte[] bs)
char[] cs=new char[bs.length * 2];
int x;
for (int i=0;i < bs.length;i++)
x = bs[i] & 0xff;
cs[2 * i] = this.cs[x / 16];
cs[2 * i + 1] = this.cs[x % 16];
return new String(cs);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
这里使用的是debug.keystore
LogCat输出:
Keytool输出:
可以看到我们获取的SHA1是正确的。
注意:这里的signature只与签名时使用的证书有关系,这里生成的SHA1,也可以通过APK包中的CERT.RSA(解压APK之后,META-INF文件夹中)文件得到。
在JNI中获取签名SHA1
我并没有在android的C库中找到类似于PackageManager之类的东西,所以任然采用JNI的反射机制去调用相关的Java方法,其实就是将上面的Java方法翻译成C++代码。
public static native void native_init(Context context);
static bool is_valid= false;
const char *app_signature_sha1="40C438E3DAC29E04718E141BD816B3FC1A53E389";
const char HexCode[]='0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F';
JNIEXPORT void JNICALL Java_com_wastrel_signtest_NativeFunc_native_1init
(JNIEnv *env, jclass clz, jobject context_object)
jclass context_class = env->GetObjectClass(context_object);
jmethodID methodId = env->GetMethodID(context_class, "getPackageManager", "()Landroid/content/pm/PackageManager;");
jobject package_manager_object = env->CallObjectMethod(context_object, methodId);
if (package_manager_object == NULL)
LOGE("getPackageManager() Failed!");
return;
methodId = env->GetMethodID(context_class, "getPackageName", "()Ljava/lang/String;");
jstring package_name_string = (jstring)env->CallObjectMethod(context_object, methodId);
if (package_name_string == NULL)
LOGE("getPackageName() Failed!");
return ;
env->DeleteLocalRef(context_class);
jclass pack_manager_class = env->GetObjectClass(package_manager_object);
methodId = env->GetMethodID(pack_manager_class, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
env->DeleteLocalRef(pack_manager_class);
jobject package_info_object = env->CallObjectMethod(package_manager_object, methodId, package_name_string, 0x40);
if (package_info_object == NULL)
LOGE("getPackageInfo() Failed!");
return ;
env->DeleteLocalRef(package_manager_object);
jclass package_info_class = env->GetObjectClass(package_info_object);
jfieldID fieldId = env->GetFieldID(package_info_class, "signatures", "[Landroid/content/pm/Signature;");
env->DeleteLocalRef(package_info_class);
jobjectArray signature_object_array = (jobjectArray)env->GetObjectField(package_info_object, fieldId);
if (signature_object_array == NULL)
LOGE("PackageInfo.signatures[] is null");
return ;
jobject signature_object = env->GetObjectArrayElement(signature_object_array, 0);
env->DeleteLocalRef(package_info_object);
jclass signature_class = env->GetObjectClass(signature_object);
methodId = env->GetMethodID(signature_class, "toByteArray", "()[B");
env->DeleteLocalRef(signature_class);
jbyteArray signature_byte = (jbyteArray) env->CallObjectMethod(signature_object, methodId);
jclass byte_array_input_class=env->FindClass("java/io/ByteArrayInputStream");
methodId=env->GetMethodID(byte_array_input_class,"<init>","([B)V");
jobject byte_array_input=env->NewObject(byte_array_input_class,methodId,signature_byte);
jclass certificate_factory_class=env->FindClass("java/security/cert/CertificateFactory");
methodId=env->GetStaticMethodID(certificate_factory_class,"getInstance","(Ljava/lang/String;)Ljava/security/cert/CertificateFactory;");
jstring x_509_jstring=env->NewStringUTF("X.509");
jobject cert_factory=env->CallStaticObjectMethod(certificate_factory_class,methodId,x_509_jstring);
methodId=env->GetMethodID(certificate_factory_class,"generateCertificate",("(Ljava/io/InputStream;)Ljava/security/cert/Certificate;"));
jobject x509_cert=env->CallObjectMethod(cert_factory,methodId,byte_array_input);
env->DeleteLocalRef(certificate_factory_class);
jclass x509_cert_class=env->GetObjectClass(x509_cert);
methodId=env->GetMethodID(x509_cert_class,"getEncoded","()[B");
jbyteArray cert_byte=(jbyteArray)env->CallObjectMethod(x509_cert,methodId);
env->DeleteLocalRef(x509_cert_class);
jclass message_digest_class=env->FindClass("java/security/MessageDigest");
methodId=env->GetStaticMethodID(message_digest_class,"getInstance","(Ljava/lang/String;)Ljava/security/MessageDigest;");
jstring sha1_jstring=env->NewStringUTF("SHA1");
jobject sha1_digest=env->CallStaticObjectMethod(message_digest_class,methodId,sha1_jstring);
methodId=env->GetMethodID(message_digest_class,"digest","([B)[B");
jbyteArray sha1_byte=(jbyteArray)env->CallObjectMethod(sha1_digest,methodId,cert_byte);
env->DeleteLocalRef(message_digest_class);
jsize array_size=env->GetArrayLength(sha1_byte);
jbyte* sha1 =env->GetByteArrayElements(sha1_byte,NULL);
char *hex_sha=new char[array_size*2+1];
for (int i = 0; i <array_size ; ++i)
hex_sha[2*i]=HexCode[((unsigned char)sha1[i])/16];
hex_sha[2*i+1]=HexCode[((unsigned char)sha1[i])%16];
hex_sha[array_size*2]='\\0';
LOGE(" %s ",hex_sha);
if (strcmp(hex_sha,app_signature_sha1)==0)
LOGE("验证通过");
is_valid= true;
else
ThrowRuntimeExcption(env,"验证失败");
return ;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
运行结果:
显然我们的结果达到了。在JNI中获得了签名的SHA1。验证通过之后isValid=true
。别的JNIFunc调用的时候先判断一下isValid
的值再操作即可。当应用签名和so里预置签名不一致时将无法获得正确结果。
其实代码不用这么长
从上面的Java代码或C++代码都可以看到,从CertificateFactory
开始其实签名的元数据已经没有改变。因此其实我们不必大废周章的通过CertificateFactory
和MessageDigest
两个类去求签名的SHA1值。其实直接比较元数据即可完成验证。那么我们如何获得签名的元数据呢?