欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

apk签名验证实现

程序员文章站 2022-04-11 15:47:38
...

前言

在学习的路途中,留下一点痕迹。签名保护的方法很多,我简单实现一下。

使用全局Context

private static Context context;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        context=getApplicationContext();
        boolean isOwnApp = TestDemo.isOwnApp();
        Log.d("fuck","isownapp:"+isOwnApp);
        if(!isOwnApp){
            Log.d("fuck","is not own app ... exit app");
          android.os.Process.killProcess(android.os.Process.myPid());
        }
    }
    public static Context getContext(){
        return context;
    }

新建签名验证类TestDemo

private static final String APP_SIGN="aaa"; //假定的正确的签名
    public static String getSignature(){
        Context context = main.getContext();
        try{
            //通过包管理器获得指定报名包含的包信息
            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
            //通过返回的包信息获得签名数组
            Signature [] signatures = packageInfo.signatures;
            StringBuilder builder = new StringBuilder();
            //遍历签名数组,进行拼接
            for(Signature signature : signatures){
                builder.append(signature.toCharsString());
            }
            return builder.toString();
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return "";
    }
    public static boolean isOwnApp(){
        String signstr = getSignature();
        Log.d("fuck","this is"+signstr);
        return APP_SIGN.equals(signstr);
    }

运行程序,通过日志来观察。

adb logcat -s fuck
02-27 15:57:32.237  8496  8496 D fuck    : this is308201dd30820146020101300d06092a864886f70d010105050030373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b3009060355040613025553301e170d3137313030353035343032315a170d3437303932383035343032315a30373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b300906035504061302555330819f300d06092a864886f70d010101050003818d0030818902818100e57dfaf64295833c4de7be5f7d8b6439f20b16436feb3d3c5dcc4483b12d8c06651a2ce19190391b6f76634e64d0e363b036b41bf3408e598debd688a1cf98ea2024dda40e50a2ca11ac4c0c891e4c24df5754204c71b8c4af6fdd1493ee482e1d7561d2620d9226b6668000da72c3a9a7d1db8a9151b607477cd191a54f40090203010001300d06092a864886f70d010105050003818100823ef063627e06cef66669be8031f9bfaf86e05581a2d86fded7b20aa2a0fd443cec1b44ef3966ce5b2c24cabc3c2f7a32f69e534b32f050b5720e3556e182552a8b5767f6de1a87d4e3a13f364429d424877741d005120c0b07e216d9ae488be962e46e97a090dc20475eb162cf138c3f8a6b254814270f3398fdf2f9fdf886
02-27 15:57:32.237  8496  8496 D fuck    : isownapp:false
02-27 15:57:32.237  8496  8496 D fuck    : is not own app ... exit app

可以看到保护已经生效了。

native层进行签名验证

对于一些**者来说,仅仅从java层进行签名验证是远远不够的,因此我们需要更深一步,在native层进行签名验证。当然签名验证的方法毕竟是通过代码来实现的,那么就可以通过代码进行**。只不过是加大**的难度而已。
要想绕过签名验证,关键在于最后的判断,因此我们需要将判断的函数放在native层中。
为了方便我新建了一个Util类,并实现了获取应用的签名。

public static String getSignature(){
        Context context = MainActivity.getContext();
        try {
            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(),
                    PackageManager.GET_SIGNATURES);
            Signature [] signatures = packageInfo.signatures;
            StringBuilder builder = new StringBuilder();
            for (Signature signature : signatures){
                builder.append(signature.toCharsString());
            }
            return builder.toString();
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return "";
    }

接着声明public native int isEqual();
编写c代码,有关NDK开发,可以参考我的这篇文章

JNIEXPORT int JNICALL Java_com_code_jsk_handlenative_MainActivity_isEqual
        (JNIEnv * env,jobject obj){
    const char *app_signature = "308201dd30820146020101300d06092a864886f70d010105050030373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b3009060355040613025553301e170d3137313030353035343032315a170d3437303932383035343032315a30373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b300906035504061302555330819f300d06092a864886f70d010101050003818d0030818902818100e57dfaf64295833c4de7be5f7d8b6439f20b16436feb3d3c5dcc4483b12d8c06651a2ce19190391b6f76634e64d0e363b036b41bf3408e598debd688a1cf98ea2024dda40e50a2ca11ac4c0c891e4c24df5754204c71b8c4af6fdd1493ee482e1d7561d2620d9226b6668000da72c3a9a7d1db8a9151b607477cd191a54f40090203010001300d06092a864886f70d010105050003818100823ef063627e06cef66669be8031f9bfaf86e05581a2d86fded7b20aa2a0fd443cec1b44ef3966ce5b2c24cabc3c2f7a32f69e534b32f050b5720e3556e182552a8b5767f6de1a87d4e3a13f364429d424877741d005120c0b07e216d9ae488be962e46e97a090dc20475eb162cf138c3f8a6b254814270f3398fdf2f9fdf886";
    char* className = "com/code/jsk/handlenative/Util";
    jclass clazz = env->FindClass(className);  //找到相应的class
    if(clazz==NULL){
        LOGD("not find class '%s'",className);
        return 1;
    }
    LOGD("class name:%p",clazz);
    jmethodID jmethodID1 = env->GetStaticMethodID(clazz,"getSignature","()Ljava/lang/String;");//找到class中的方法
    if(jmethodID1==NULL){
        LOGD("not find method '%s'",jmethodID1);
        return 1;
    }
    LOGD("method name:%p",jmethodID1);
    jstring jstring1 = (jstring)env->CallStaticObjectMethod(clazz,jmethodID1);//执行方法
    if(jstring1==NULL){
        LOGD("method invoke error ...");
        return 1;
    }
    const char* strAry=env->GetStringUTFChars(jstring1,0);
    LOGD("strAry is : %s",strAry);
    int cmpval = strcmp(strAry,app_signature);//在native层中做比较
    LOGD("cmpval is : %d",cmpval);
    if(cmpval!=0){  //如果签名不匹配则退出
        return 1;
    }
    env->ReleaseStringUTFChars(jstring1,strAry);//清除相应的内存空间
    return 0;//如果匹配正确则返回0
}

这里为了在native层中也能打印日志。可以参考这篇文章

#include <android/log.h>
#define  TAG    "fuck"
// 定义info信息
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,TAG,__VA_ARGS__)
// 定义debug信息
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
// 定义error信息
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,TAG,__VA_ARGS__)

之后最好在写一个签名处理函数,对于正确与否进行响应的处理。

public native void checkSign();
#include <stdlib.h>
JNIEXPORT void JNICALL Java_com_code_jsk_handlenative_MainActivity_checkSign
        (JNIEnv *env,jobject obj){
    int check_sign = Java_com_code_jsk_handlenative_MainActivity_isEqual(env,obj);
    LOGD("check_sign is %d",check_sign);
    if(check_sign!=0){
        LOGD("exit...");
        exit(0);
    }
}

做完这几步,就可以编译运行了,查看一下日志
apk签名验证实现
在这里,我提前获取了签名。因此正确通过了验证。

总结

其实真正的签名验证函数,往往会比较复杂,我这里做了简化,将签名明文传输。有的也可以通过访问指定服务器进行签名验证。

相关标签: apk