要使用NDK首先要了解NDK到底是什么?
(英语:native development kit,简称NDK)是一种基于原生程序接口的软件开发工具。通过此工具开发的程序直接以本地语言运行,而非虚拟机。因此只有java等基于虚拟机运行的语言的程序才会有原生开发工具包。[维基百科]
NDK是一系列工具的集合
NDK提供了一系列的工具,帮助开发者快速开发C(或C++)的动态库,并能自动将so和java应用一起打包成apk。这些工具对开发者的帮助是巨大的.
NDK集成了交叉编译器,并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件(指出“哪些文件需要编译”、“编译特性要求”等),就可以创建出so。
NDK可以自动地将so和Java应用一起打包,极大地减轻了开发人员的打包工作。
为什么要使用?
1、代码的保护。由于apk的java层代码很容易被反编译,而C/C++库反汇难度较大。能够阻挡一定的开发者进行逆向。
2、可以方便地使用现存的开源库。大部分现存的开源库都是用C/C++代码编写的。
3、提高程序的执行效率。将要求高性能的应用逻辑使用C开发,从而提高应用程序的执行效率。
4、便于移植。用C/C++写得库可以方便在其他的嵌入式平台上再次使用。
NDK和CMake 的下载和安装
打开android studio 找到Sdk管理器,进行下载安装即可。
下面开发正式开发:
1、创建jni文件夹用来存放调用C++相关的文件,有两个配置文件和头文件以及源文件
配置文件:Android.mk
作用:命令行cd到src/main/jni文件夹下,使用命令ndk-build生成.so文件,这里边定义了生成.so库的文件名、指定编译的c++源文件和头文件,用于向构建系统描述源文件和共享库,让ndk去按照指定的方式编译c++源文件和头文件。
编译目标目录声明
LOCAL_PATH:=$(call my-dir) LOCAL_PATH需要编译源文件所在的目录,$(call my-dir)使用宏定义,my-dir是返回值对应Android.mk所在的目录。
重置全局变量
include $(CLEAR_VARS) CLEAR_VARS变量指向特殊 GNU Makefile, 可为您清除许多LOCAL_XXX变量.不包括LOCAL_PATH因为系统在单一 GNU Make 执行环境(其中所有变量都是全局的)中解析所有构建控制文件. 在描述每个模块之前, 必须声明(重新声明)此变量
LOCAL_MODULE:=secretkey 指定模块的名称
LOCAL_MODULE, 指定模块的名称,唯一且不含空格, 之后会编译出librecorder-jni.so, 如果模块名已包含前缀lib, 则不会自动添加lib前缀.
LOCAL_SRC_FILES:=secretkey.cpp secretkey.h 指定编译c++源文件和头文件
待编译的源文件
LOCAL_SRC_FILES,指定源文件列表, 多个文件使用空格分割.可以使用相对文件路径(指向 LOCAL_PATH)和绝对文件路径
LOCAL_LDLIBS :=-llog 打印log
include $(BUILD_SHARED_LIBRARY)整合
BUILD_SHARED_LIBRARY变量指向GNU Makefile脚本, 用于收集您自最近 include 后在 LOCAL_XXX 变量中定义的所有信息.其实就是让上一次include到这里之间的内容生效
编译模块输出名称
LOCAL_MODULE_FILENAME, 真正的库输出文件名. 如果不喜欢系统自动生成的文件名, 可以指定这个值
LOCAL_MODULE := foo
LOCAL_MODULE_FILENAME := libnewfoo
Application.mk
此文件枚举并描述您的应用需要的模块。Android.mk有效的前提是依靠该文件的保证位于jni的目录下.包含下面几个方面的内容:
用于针对特定平台进行编译的 ABI。
工具链。
要包含的标准库(静态和动态 STLport 或默认系统)。
APP_STL := stlport_static 静态标准库
Android NDK 默认使用的是最小支持的C++运行库,如果你需要你的NDK程序中使用STL,则可以设置APP_STL := stlport_static,APP_STL有表二中的几种取值。
NameExplanation
system(default)系统默认的C++运行库
stlport_static以静态链接方式使用的sttport版本的STL
stlport_shared以动态链接方式使用的sttport版本的STL
gnustl_static以静态链接方式使用的gnustl版本的STL
gnustl_shared以动态链接方式使用的gnustl版本的STL
gabi++_static以静态链接方式使用的gabi++
gabi++_shared以动态链接方式使用的gabi++
c++_static以静态链接方式使用的LLVM libc++
c++_shared以动态链接方式使用的LLVM libc++
表二:NDK运行库
若APK中有多个SO文件用到STL,建议都使用动态方式链接STL,这样可以减小整个APK文件大小。
另外需要注意的是官方提供的NDK运行库除了默认的以外都支持RTTI和异常,然而默认是禁用的,将在下面的Android.mk中说明如何开启。
APP_OPTIM(编译模式)
“release”模式为默认的,生成的是优化后的二进制;也可以设置为“debug”模式,“debug”模式生成的是未优化二进制,提供很多BUG信息,便于调试和分析
APP_PLATFORM
指定当前程序支持android最低api水平,如APPP_PLATFORM:=16 最低支持到api16
编码实战
这里以我项目中使用ndk生成密匙一个例子进行讲解,实际项目中肯定会用到加密功能,有一些敏感的数据如用户的密码、账号等,需要将这些信息保存到本地一旦数据保存到磁盘上,如果不进行加密很有可能泄漏,造成不良的后果。
通过生成.so文件生成密匙,在一定程度上降低被盗风险,毕竟这里边设计到底层的东西比较多,安全性比硬编码之类的高了好几个档次。
1、所有文件存放的位置截图
2、Android.mk配置详细信息
LOCAL_PATH:=$(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE:=secretkey
LOCAL_SRC_FILES:=secretkey.cpp secretkey.h
LOCAL_LDLIBS :=-llog
include $(BUILD_SHARED_LIBRARY)
3、Application.mk配置详细信息
APP_STL := stlport_static
APP_ABI := all
#app支持最小api level
APP_PLATFORM:= android-16
APP_OPTIM := release
4、secretkey.h
#include
#defineUTF_8"UTF-8"
#ifdef__cplusplus
extern"C"{
//com.mine.cui.zxandroidlib.jni.com.mine.cui.zxandroidlib.security.SecretKeyHelper.createSecretKey
jstring
Java_com_test_lib_security_SecretKeyHelper_createSecretKey(JNIEnv*,jobject,jobject);
}
#endif
方法格式:jstring Java+全类名 (其中.用下划线分隔开)+java定义native方法的类名和native方法名
5、secretkey.app
#include
#include
#include"secretkey.h"
#include
#include
#defineLOG_TAG"robin_jni"// 自定义的LOG的标识
#defineLOGOPEN1//日志开关,1为开,其它为关
#defineLOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__)
#defineLOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__)
#defineLOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__)
#defineLOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG ,__VA_ARGS__)
#defineLOGF(...) __android_log_print(ANDROID_LOG_FATAL,TAG ,__VA_ARGS__)
//char* to jstring
jstringtoString(JNIEnv* env,jbyteArraybyteArray) {
jclassstring_cls = env->FindClass("java/lang/String");
jmethodIDnew_string_mid = env->GetMethodID(string_cls,"",
"([BLjava/lang/String;)V");
return reinterpret_cast(env->NewObject(string_cls, new_string_mid,
byteArray, env->NewStringUTF(UTF_8)));
}
jbyteArraytoBytes(JNIEnv* env,const char* bytes) {
jclassstring_cls = env->FindClass("java/lang/String");
jmethodIDget_bytes_mid = env->GetMethodID(string_cls,"getBytes",
"(Ljava/lang/String;)[B");
return reinterpret_cast(env->CallObjectMethod(
env->NewStringUTF(bytes), get_bytes_mid, env->NewStringUTF(UTF_8)));
}
jbyteArraytoBytes(JNIEnv* env,jstringstring) {
jclassstring_cls = env->FindClass("java/lang/String");
jmethodIDget_bytes_mid = env->GetMethodID(string_cls,"getBytes",
"(Ljava/lang/String;)[B");
return reinterpret_cast(env->CallObjectMethod(string,
get_bytes_mid, env->NewStringUTF(UTF_8)));
}
jbyteArraygetDigestedBytes(JNIEnv* env,jbyteArraycomplex_bytes) {
staticjobjectsatic_message_digest_obj = __null;
jclassmessage_digest_cls = env->FindClass("java/security/MessageDigest");
jmethodIDget_instance_mid = env->GetStaticMethodID(message_digest_cls,
"getInstance","(Ljava/lang/String;)Ljava/security/MessageDigest;");
if(satic_message_digest_obj == __null) {
jobjectlocal_message_digest_obj = env->CallStaticObjectMethod(
message_digest_cls, get_instance_mid, env->NewStringUTF("MD5"));
satic_message_digest_obj = env->NewGlobalRef(local_message_digest_obj);
env->DeleteLocalRef(local_message_digest_obj);
}
jmethodIDdigest_mid = env->GetMethodID(message_digest_cls,"digest",
"([B)[B");
env->DeleteLocalRef(message_digest_cls);
return reinterpret_cast(env->CallObjectMethod(
satic_message_digest_obj, digest_mid, complex_bytes));
}
jstringtoHex(JNIEnv* env,jbyteArraydigested_bytes) {
jclassbig_integer_cls = env->FindClass("java/math/BigInteger");
jmethodIDnew_big_integer_mid = env->GetMethodID(big_integer_cls,"",
"(I[B)V");
jobjectbig_integer_obj = env->NewObject(big_integer_cls,
new_big_integer_mid,1, digested_bytes);
env->DeleteLocalRef(digested_bytes);
jmethodIDto_String_mid = env->GetMethodID(big_integer_cls,"toString",
"(I)Ljava/lang/String;");
env->DeleteLocalRef(big_integer_cls);
return reinterpret_cast(env->CallObjectMethod(big_integer_obj,
to_String_mid,16));
}
jstringgetMD5(JNIEnv* env,jstringjInfo) {
jbyteArraydigested_bytes = getDigestedBytes(env, toBytes(env, jInfo));
returntoHex(env, digested_bytes);
}
jstringgetAppendedString(JNIEnv* env,jobjectthiz,jstrings1,jstrings2) {
const char*s1x = (env)->GetStringUTFChars(s1,NULL);
const char*s2x = (env)->GetStringUTFChars(s2,NULL);
char*sall =new char[strlen(s1x) + strlen(s2x) +1];
strcpy(sall, s1x);
strcat(sall, s2x);
jstringretval = (env)->NewStringUTF(sall);
(env)->ReleaseStringUTFChars(s1, s1x);
(env)->ReleaseStringUTFChars(s2, s2x);
free(sall);
returnretval;
}
jobjectgetInstance(JNIEnv* env,jclassobj_class) {
jmethodIDconstruction_id = env->GetMethodID(obj_class,"","()V");
jobjectobj = env->NewObject(obj_class, construction_id);
returnobj;
}
//获取deviceid
jstringgetDeviceID(JNIEnv*env,jobjectthiz,jobjectmContext) {
jclasscls_context = (env)->FindClass("android/content/Context");
if(cls_context ==0) {
return(env)->NewStringUTF("unknown");
}
jmethodIDgetSystemService = (env)->GetMethodID(cls_context,
"getSystemService","(Ljava/lang/String;)Ljava/lang/Object;");
if(getSystemService ==0) {
return(env)->NewStringUTF("unknown");
}
jfieldIDTELEPHONY_SERVICE = (env)->GetStaticFieldID(cls_context,
"TELEPHONY_SERVICE","Ljava/lang/String;");
if(TELEPHONY_SERVICE ==0) {
return(env)->NewStringUTF("unknown");
}
jobjectstr = (env)->GetStaticObjectField(cls_context, TELEPHONY_SERVICE);
jobjecttelephonymanager = (env)->CallObjectMethod(mContext,
getSystemService, str);
if(telephonymanager ==0) {
return(env)->NewStringUTF("unknown");
}
jclasscls_tm = (env)->FindClass("android/telephony/TelephonyManager");
if(cls_tm ==0) {
return(env)->NewStringUTF("unknown");
}
jmethodIDgetDeviceId = (env)->GetMethodID(cls_tm,"getDeviceId",
"()Ljava/lang/String;");
if(getDeviceId ==0) {
return(env)->NewStringUTF("unknown");
}
jobjectdeviceid = (env)->CallObjectMethod(telephonymanager, getDeviceId);
return(jstring) deviceid;
}
//获取SerialNumber
jstringgetSerialNumber(JNIEnv*env,jobjectthiz,jobjectmContext) {
jclasscls_tm = (env)->FindClass("android/os/SystemProperties");
if(cls_tm ==0) {
return(env)->NewStringUTF("unknown");
}
jmethodIDgetDeviceId = (env)->GetStaticMethodID(cls_tm,"get",
"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
if(getDeviceId ==0) {
return(env)->NewStringUTF("unknown");
}
jstringparam1 = (env)->NewStringUTF("ro.serialno");
jstringparam2 = (env)->NewStringUTF("unknown");
jobjectdeviceid = (env)->CallStaticObjectMethod(cls_tm, getDeviceId,
param1, param2);
return(jstring) deviceid;
}
jstringjint2jstring(JNIEnv*env,jintfirst) {
charbuf[64];// assumed large enough to cope with result
sprintf(buf,"%d", first);// error checking omitted
returnenv->NewStringUTF( buf);
}
//获取公钥
jstringgetPublicKey(JNIEnv* env,jobjectthiz,jobjectcontext) {
jclasscontext_cls = env->GetObjectClass(context);
jmethodIDget_package_manager_mid = env->GetMethodID(context_cls,
"getPackageManager","()Landroid/content/pm/PackageManager;");
jmethodIDget_package_name_mid = env->GetMethodID(context_cls,
"getPackageName","()Ljava/lang/String;");
env->DeleteLocalRef(context_cls);
jobjectpm_obj = env->CallObjectMethod(context, get_package_manager_mid);
jclasspm_cls = env->FindClass("android/content/pm/PackageManager");
jmethodIDget_package_info_mid = env->GetMethodID(pm_cls,"getPackageInfo",
"(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
jstringpackage_name =reinterpret_cast(env->CallObjectMethod(
context, get_package_name_mid));
jfieldIDflag_fid = env->GetStaticFieldID(pm_cls,"GET_SIGNATURES","I");
jintflag = env->GetStaticIntField(pm_cls, flag_fid);
env->DeleteLocalRef(pm_cls);
jobjectpi_obj = env->CallObjectMethod(pm_obj, get_package_info_mid,
package_name, flag);
env->DeleteLocalRef(package_name);
jclasspi_cls = env->FindClass("android/content/pm/PackageInfo");
jfieldIDsignatures_fid = env->GetFieldID(pi_cls,"signatures",
"[Landroid/content/pm/Signature;");
env->DeleteLocalRef(pi_cls);
jobjectsig_obj = env->GetObjectField(pi_obj, signatures_fid);
env->DeleteLocalRef(pi_obj);
jobjectArraysigs =reinterpret_cast(sig_obj);
jclasssignature_cls = env->FindClass("android/content/pm/Signature");
jmethodIDto_byte_array_mid = env->GetMethodID(signature_cls,"toByteArray",
"()[B");
jbyteArraysig_bytes =reinterpret_cast(env->CallObjectMethod(
env->GetObjectArrayElement(sigs,0), to_byte_array_mid));
jclasscertificate_factory_cls = env->FindClass(
"java/security/cert/CertificateFactory");
jmethodIDget_certificate_instance_mid = env->GetStaticMethodID(
certificate_factory_cls,"getInstance",
"(Ljava/lang/String;)Ljava/security/cert/CertificateFactory;");
jobjectcertificate_factory_obj = env->CallStaticObjectMethod(
certificate_factory_cls, get_certificate_instance_mid,
env->NewStringUTF("X509"));
jmethodIDgenerate_certificate_mid = env->GetMethodID(
certificate_factory_cls,"generateCertificate",
"(Ljava/io/InputStream;)Ljava/security/cert/Certificate;");
env->DeleteLocalRef(certificate_factory_cls);
jclasscertificate_cls = env->FindClass("java/security/cert/Certificate");
jclassbyte_input_stream_cls = env->FindClass(
"java/io/ByteArrayInputStream");
jmethodIDnew_sig_bytes_is_mid = env->GetMethodID(byte_input_stream_cls,
"","([B)V");
jobjectsig_bytes_is = env->NewObject(byte_input_stream_cls,
new_sig_bytes_is_mid, sig_bytes);
env->DeleteLocalRef(sig_bytes);
env->DeleteLocalRef(byte_input_stream_cls);
jobjectcert = env->CallObjectMethod(certificate_factory_obj,
generate_certificate_mid, sig_bytes_is);
env->DeleteLocalRef(sig_bytes_is);
env->DeleteLocalRef(certificate_factory_obj);
jmethodIDget_pubic_key_mid = env->GetMethodID(certificate_cls,
"getPublicKey","()Ljava/security/PublicKey;");
env->DeleteLocalRef(certificate_cls);
jobjectpublicKey = env->CallObjectMethod(cert, get_pubic_key_mid);
jclasspublicKey_cls = env->GetObjectClass(publicKey);
jmethodIDtoString_mid = env->GetMethodID(publicKey_cls,"toString","()Ljava/lang/String;");
jstringpublicKey_str =static_cast(env->CallObjectMethod(publicKey,toString_mid));
env->DeleteLocalRef(cert);
env->DeleteLocalRef(publicKey_cls);
env->DeleteLocalRef(publicKey);
jclassstring_cls = env->GetObjectClass(publicKey_str);
jmethodIDindexOf_mid = env->GetMethodID(string_cls,"indexOf","(Ljava/lang/String;)I");
jstringparam = env->NewStringUTF("modulus");
jintaa = env->CallIntMethod(publicKey_str,indexOf_mid,param);
jstringparam2 = env->NewStringUTF("publicExponent");
jintbb = env->CallIntMethod(publicKey_str,indexOf_mid,param2);
jmethodIDsubstring_mid = env->GetMethodID(string_cls,"substring","(II)Ljava/lang/String;");
jstringpublicKey2_str =static_cast(env->CallObjectMethod(publicKey_str,substring_mid,aa+8,bb-1));
returnpublicKey2_str;
}
//获取签名
jstringgetSignatures(JNIEnv* env,jobjectthizz,
jobjectthiz) {
jclassnative_clazz = env->GetObjectClass(thiz);
// 得到 getPackageManager 方法的 ID
jmethodIDmethodID_func = env->GetMethodID(native_clazz,
"getPackageManager","()Landroid/content/pm/PackageManager;");
// 获得应用包的管理器
jobjectpackage_manager = env->CallObjectMethod(thiz, methodID_func);
// 获得 PackageManager 类
jclasspm_clazz = env->GetObjectClass(package_manager);
// 得到 getPackageInfo 方法的 ID
jmethodIDmethodID_pm = env->GetMethodID(pm_clazz,"getPackageInfo",
"(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
//获取包名
jmethodIDmethodID_packagename = env->GetMethodID(native_clazz,
"getPackageName","()Ljava/lang/String;");
jstringname_str =static_cast(env->CallObjectMethod(thiz,
methodID_packagename));
// 获得应用包的信息
jobjectpackage_info = env->CallObjectMethod(package_manager, methodID_pm,
name_str,64);//env->NewStringUTF("com.example.contasdf")
// 获得 PackageInfo 类
jclasspi_clazz = env->GetObjectClass(package_info);
// 获得签名数组属性的 ID
jfieldIDfieldID_signatures = env->GetFieldID(pi_clazz,"signatures",
"[Landroid/content/pm/Signature;");
// 得到签名数组,待修改
jobjectsignatur = env->GetObjectField(package_info, fieldID_signatures);
jobjectArraysignatures =reinterpret_cast(signatur);
// 得到签名
jobjectsignature = env->GetObjectArrayElement(signatures,0);
// 获得 Signature 类,待修改
jclasss_clazz = env->GetObjectClass(signature);
// 得到 hashCode 方法的 ID
jmethodIDmethodID_hc = env->GetMethodID(s_clazz,"hashCode","()I");
// 获得应用包的管理器,待修改
inthash_code = env->CallIntMethod(signature, methodID_hc);
charstr[100];
sprintf(str,"%u", hash_code);
jstringsign = env->NewStringUTF(str);
returnsign;
}
jstringgetPackageName(JNIEnv* env,jobjectthizz,jobjectthiz) {
jclassnative_clazz = env->GetObjectClass(thiz);
// 得到 getPackageManager 方法的 ID
jmethodIDmethodID_func = env->GetMethodID(native_clazz,
"getPackageManager","()Landroid/content/pm/PackageManager;");
// 获得应用包的管理器
jobjectpackage_manager = env->CallObjectMethod(thiz, methodID_func);
// 获得 PackageManager 类
jclasspm_clazz = env->GetObjectClass(package_manager);
// 得到 getPackageInfo 方法的 ID
jmethodIDmethodID_pm = env->GetMethodID(pm_clazz,"getPackageInfo",
"(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
//获取包名
jmethodIDmethodID_packagename = env->GetMethodID(native_clazz,
"getPackageName","()Ljava/lang/String;");
jstringname_str =static_cast(env->CallObjectMethod(thiz,
methodID_packagename));
returnname_str;
}
char* jstringTostring(JNIEnv* env,jstringjstr)
{
char* rtn =NULL;
jclassclsstring = env->FindClass("java/lang/String");
jstringstrencode = env->NewStringUTF("utf-8");
jmethodIDmid = env->GetMethodID(clsstring,"getBytes","(Ljava/lang/String;)[B");
jbyteArraybarr= (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);
jsizealen = env->GetArrayLength(barr);
jbyte* ba = env->GetByteArrayElements(barr,JNI_FALSE);
if(alen >0)
{
rtn = (char*)malloc((size_t) (alen +1));
memcpy(rtn, ba, (size_t) alen);
rtn[alen] =0;
}
env->ReleaseByteArrayElements(barr, ba,0);
returnrtn;
}
jstring Java_com_test_lib_security_SecretKeyHelper_createSecretKey(JNIEnv* env,jobjectthizz,
jobjectthiz) {
jstringimei = getAppendedString(env, thizz, getDeviceID(env, thizz, thiz),getSerialNumber(env, thizz, thiz));
if(LOGOPEN==1){
LOGD("imei = %s",jstringTostring(env,imei));
}
jstringsign = getPublicKey(env, thizz, thiz);
if(LOGOPEN==1){
LOGD("sign = %s",jstringTostring(env,sign));
}
jstringimei_sign = getAppendedString(env, thizz, imei, sign);
if(LOGOPEN==1){
LOGD("imei_sign = %s",jstringTostring(env,imei_sign));
}
jstringpackage = getPackageName(env, thizz, thiz);
if(LOGOPEN==1){
LOGD("package = %s",jstringTostring(env,package));
}
jstringimei_sign_package = getAppendedString(env, thizz, imei_sign,package);
if(LOGOPEN==1){
LOGD("imei_sign_package = %s",jstringTostring(env,imei_sign_package));
}
//请再加入自己的移位或替换 或其他加密算法,例如我又append了一次imei
imei_sign_package = getAppendedString(env, thizz, imei_sign_package, imei);
if(LOGOPEN==1){
LOGD("imei_sign_package2 = %s",jstringTostring(env,imei_sign_package));
}
imei_sign_package = getAppendedString(env, thizz, imei_sign_package, sign);
if(LOGOPEN==1){
LOGD("imei_sign_package3 = %s",jstringTostring(env,imei_sign_package));
}
jstringsecretKey = getMD5(env, imei_sign_package);
if(LOGOPEN==1){
LOGD("secretKey = %s",jstringTostring(env,secretKey));
}
returnsecretKey;
}
6、、
public class SecretKeyHelper {
static{
System.loadLibrary("secretkey");
}
private staticStringmKey;
public staticString getSecretKey(Context context) {
if(mKey==null) {
mKey=createSecretKey(context);
}
returnmKey;
}
public static nativeString createSecretKey(Context context);
}
8、