安卓加固基礎

安卓加固基礎(一)

1.Dex字符串加密

1.1 前序

Android應用當中,很多隱私信息都是以字符串的形式存在的。這些隱私信息是明文,對於軟件來說是想當不安全的,如果我們能在打包時對Dex中的字符串加密替換,並在運行時調用解密,這樣就能夠避免字符串明文存在於Dex中。雖然,無法完全避免被破解,但是加大了逆向提取信息的難度,安全性無疑提高了很多。

1.2 字符串加密方法簡介

目前市面上主要存在兩種字符串加密方式:
(1)在開發階段開發者使用加密後的字符串然後手動調用解密,這種方法工程量太大了,缺點很明顯。
(2)編譯後修改字節碼,然後再動態植入加密後的字符串,最後讓其自動調用進行解密,這裏重點分析的是第二種

1.3 強大的工具字符串加密工具StringFog
1.3.1 環境配置和運行效果

工具:

(1)AS版本3.5.0
(2)StringFog配置方法:https://github.com/MegatronKing/StringFog
(3)反編譯工具 jeb3

代碼(MainActivity.java):

package com.example.testdex;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate (savedInstanceState);
        setContentView (R.layout.activity_main);
        String str ="Stringfog";               //這是加密字符串
    }
}

jeb3反編譯的效果(MainActivity.java):

package com.example.testdex;

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    public MainActivity() {
        super();
    }

    protected void onCreate(Bundle arg2) {
        super.onCreate(arg2);
        this.setContentView(0x7F09001C);
        StringFog.decrypt("GxEeBQFHMQAV");    //顯然"StringFog"被加密了
    }
}
1.3.2 StringFog加密原理分析

首先看加密的方法:StringFog採用的是base64+xor(異或)算法

import java.util.Base64;
public class StringFog {
	private static byte[] xor(byte[] data, String key) {     //異或算法
	    int len = data.length;
	    int lenKey = key.length();
	    int i = 0;
	    int j = 0;
	    while (i < len) {
	        if (j >= lenKey) {
	            j = 0;
	        }
	        data[i] = (byte) (data[i] ^ key.charAt(j));
	        i++;
	        j++;
	    }
	    return data;
	}
	public static String encode(String data, String key) {
	    return new String(Base64.getEncoder().encode(xor(data.getBytes(), key)));
	    //調用base64加密包
	}

	public static String decode(String data, String key) {
	    return new String(xor(Base64.getDecoder().decode(data), key));
	    //調用base64解密包
	}

按照安卓程序加密的字符串測試:

	public static void main(String[] args) {
		String test = encode("Stringfog","Hello World");
		System.out.println(test);
	}

輸出結果爲:
在這裏插入圖片描述
顯然跟jeb3裏面看到的一樣。

ps:Base64 packge 下載地址:http://commons.apache.org/proper/commons-codec/download_codec.cgi 下載後導入commons-codec-1.14.jar

1.3.2 StringFog加密原理分析
StringFog實際上是操作了class文件,編譯class文件的字節碼文件,發現如果都是字符串常量的話,指令都爲LDC

****************************************************************************
[root@iZbp1dubkpj5g938jakcouZ ~]# javac Hello.java
[root@iZbp1dubkpj5g938jakcouZ ~]# javap -c Hello
Compiled from "Hello.java"
public class Hello {
  public Hello();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String This is a String!
       2: astore_1
       3: return
}

那麼我們可以藉助asm庫攔截方法中的每條LDC指令,然後插入該指令即可

總結:字符串加密方法能夠較爲有效的阻止他人通過字符串搜素定位代碼,但是不能防止Hook,總而言之,這只是一個比較普通的混淆方法

2.資源加密

java代碼將png圖片加密:

package cn.zzh;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

public class pngEncrypt {
	public static void main(String[] args){
	    //調用加密方法
	    KMD.encrypt("f://1.png");
	}

    //加密後,會在原圖片的路徑下生成加密後的圖片
    public static void encrypt(String filePath){
        byte[] tempbytes = new byte[5000];
        try {
            InputStream in = new FileInputStream(filePath);
            OutputStream out = new FileOutputStream(filePath.subSequence(0, filePath.lastIndexOf("."))+"2.png");
            while (in.read(tempbytes) != -1) {
                byte a = tempbytes[0];
                tempbytes[0] = tempbytes[1]; //將第一個字符和第二個字符交換
                tempbytes[1] = a;
                out.write(tempbytes);//寫文件
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在這裏插入圖片描述

使用winhex查看發現:

在這裏插入圖片描述變成了:
在這裏插入圖片描述

然後我們只需要在AS中將編寫解密代碼即可:

下面是一個簡單的測試demo:

(1)建立asserts文件,將加密的12.png文件放進去
(2)代碼編寫:
package com.example.testdex;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.content.res.AssetManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;
import android.widget.Toast;

import java.io.IOException;
import java.io.InputStream;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate (savedInstanceState);
        setContentView (R.layout.activity_main);
        Bitmap bitmap= getImageFromAssets(this,"12.png");//圖片資源加密
        if(bitmap != null) {
            //imageView.setImage(ImageSource.bitmap(bitmap));
            Toast.makeText (this, "圖片解密成功", Toast.LENGTH_SHORT).show ();
        } else {
           // Log.i(TAG,"圖片爲空");
            Toast.makeText (this, "圖片解密失敗", Toast.LENGTH_SHORT).show ();
        }

    }
    public Bitmap getImageFromAssets(Context context, String fileName) {
        Bitmap image = null;
        AssetManager am = context.getResources().getAssets();
        try {
            InputStream is = am.open(fileName);
            byte[] buffer = new byte[1500000];//記得要足夠大
            is.read(buffer);
            for(int i=0; i<buffer.length; i+= 5000){//和加密相同
                byte temp = buffer[i];
                buffer[i] = buffer[i+1];
                buffer[i+1] = temp;
            }
            image = BitmapFactory.decodeByteArray(buffer, 0, buffer.length);
            if (is!=null){
                is.close();
            }

        } catch (IOException e) {
            e.printStackTrace();

        }
        return image;
    }

}



測試結果:
在這裏插入圖片描述

此時,我們打開apk查看assert文件時依然無法看到有用的文件
在這裏插入圖片描述

ps:同時我們還可以進行資源路徑混淆,詳情見:https://github.com/shwenzhang/AndResGuard/blob/master/README.zh-cn.md

3.對抗反編譯

3.1類名混淆

現在的反編譯工具都太先進了,很多純粹的對抗反編譯技術都不在適應了,
但是我們可以將類名進行混淆:

gradle版本在3.4以下時我們使用proguard-rules.pro進行混淆,達到3.4以上時我們使用R8穿插一些proguard規則進行混淆
下面重點研究的是3.4以上版本的情況:

官方文檔:https://developer.android.com/studio/build/shrink-code?hl=zh-cn

通用教程:https://www.jianshu.com/p/65027e18c2fe

混淆規則(如下):


#############################################
#
# 對於一些基本指令的添加
#
#############################################
# 代碼混淆壓縮比,在0~7之間,默認爲5,一般不做修改
-optimizationpasses 5

# 混合時不使用大小寫混合,混合後的類名爲小寫
-dontusemixedcaseclassnames

# 指定不去忽略非公共庫的類
-dontskipnonpubliclibraryclasses

# 這句話能夠使我們的項目混淆後產生映射文件
# 包含有類名->混淆後類名的映射關係
-verbose

# 指定不去忽略非公共庫的類成員
-dontskipnonpubliclibraryclassmembers

# 不做預校驗,preverify是proguard的四個步驟之一,Android不需要preverify,去掉這一步能夠加快混淆速度。
-dontpreverify

# 保留Annotation不混淆
-keepattributes *Annotation*,InnerClasses

# 避免混淆泛型
-keepattributes Signature

# 拋出異常時保留代碼行號
-keepattributes SourceFile,LineNumberTable

# 指定混淆是採用的算法,後面的參數是一個過濾器
# 這個過濾器是谷歌推薦的算法,一般不做更改
-optimizations !code/simplification/cast,!field/*,!class/merging/*


#############################################
#
# Android開發中一些需要保留的公共部分
#
#############################################

# 保留我們使用的四大組件,自定義的Application等等這些類不被混淆
# 因爲這些子類都有可能被外部調用
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Appliction
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class * extends android.view.View
-keep public class com.android.vending.licensing.ILicensingService


# 保留support下的所有類及其內部類
-keep class android.support.** {*;}

# 保留繼承的
-keep public class * extends android.support.v4.**
-keep public class * extends android.support.v7.**
-keep public class * extends android.support.annotation.**

# 保留R下面的資源
-keep class **.R$* {*;}

# 保留本地native方法不被混淆
-keepclasseswithmembernames class * {
    native <methods>;
}

# 保留在Activity中的方法參數是view的方法,
# 這樣以來我們在layout中寫的onClick就不會被影響
-keepclassmembers class * extends android.app.Activity{
    public void *(android.view.View);
}

# 保留枚舉類不被混淆
-keepclassmembers enum * {
    public static **[] values();
    public static ** valueOf(java.lang.String);
}

# 保留我們自定義控件(繼承自View)不被混淆
-keep public class * extends android.view.View{
    *** get*();
    void set*(***);
    public <init>(android.content.Context);
    public <init>(android.content.Context, android.util.AttributeSet);
    public <init>(android.content.Context, android.util.AttributeSet, int);
}

# 保留Parcelable序列化類不被混淆
-keep class * implements android.os.Parcelable {
    public static final android.os.Parcelable$Creator *;
}

# 保留Serializable序列化的類不被混淆
-keepclassmembers class * implements java.io.Serializable {
    static final long serialVersionUID;
    private static final java.io.ObjectStreamField[] serialPersistentFields;
    !static !transient <fields>;
    !private <fields>;
    !private <methods>;
    private void writeObject(java.io.ObjectOutputStream);
    private void readObject(java.io.ObjectInputStream);
    java.lang.Object writeReplace();
    java.lang.Object readResolve();
}

# 對於帶有回調函數的onXXEvent、**On*Listener的,不能被混淆
-keepclassmembers class * {
    void *(**On*Event);
    void *(**On*Listener);
}

# webView處理,項目中沒有使用到webView忽略即可
-keepclassmembers class fqcn.of.javascript.interface.for.webview {
    public *;
}
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap);
    public boolean *(android.webkit.WebView, java.lang.String);
}
-keepclassmembers class * extends android.webkit.webViewClient {
    public void *(android.webkit.webView, jav.lang.String);
}


3.2 so層混淆

這個之前已經寫過了:
文檔:函數混淆(JNI_Onload).note
鏈接:http://note.youdao.com/noteshare?id=f6add1ff6a6c4b78aac4a9e27fe390ed&sub=04FF4E2B1F504587A06C69F39C4DF22C

3.3 簽名驗證
3.3.1 在MainActivity.java中進行簽名驗證
package com.example.signatureverify;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.os.Bundle;
import android.widget.Toast;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Locale;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate (savedInstanceState);
        setContentView (R.layout.activity_main);
        Context context =getApplicationContext ();
        Toast.makeText (context, "我是正版", Toast.LENGTH_SHORT).show ();
        String cert_sha1="59F8A6B86A367F0586F1A15DDDB63D75263C5D62"; // 通過調試提前獲取apk的sha1簽名
        boolean is_org_app = false;
        try {
            is_org_app = isOrgApp(context,cert_sha1);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace ();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace ();
        }
        if(!is_org_app){
            android.os.Process.killProcess ((android.os.Process.myPid ()));
            //如果簽名不一致,說明程序被修改了,直接退出
        }
    }
//比較簽名
    private boolean isOrgApp(Context context, String cert_sha1) throws PackageManager.NameNotFoundException, NoSuchAlgorithmException {
        String current_sha1=getAppSha1(context);
        current_sha1=current_sha1.replace (":","");
        return cert_sha1.equals (current_sha1);
    }
//生成sha1的簽名
    private String getAppSha1(Context context) throws PackageManager.NameNotFoundException, NoSuchAlgorithmException {
        PackageInfo info=context.getPackageManager ().getPackageInfo (context.getPackageName (),PackageManager.GET_SIGNATURES);
        byte[] cert =info.signatures[0].toByteArray ();
        MessageDigest md =MessageDigest.getInstance ("SHA1");
        byte[] publicKey=md.digest (cert);
        StringBuffer hexString =new StringBuffer ();
        for(int i=0;i<publicKey.length;i++){
            String appendString=Integer.toHexString (0xFF&publicKey[i]).toUpperCase(Locale.US);
            if(appendString.length ()==1){
                hexString.append("0");
            }
            hexString.append(appendString);//簽名的格式是11:22,所以需要加上":"
            hexString.append (":");
        }
        String result=hexString.toString ();
        return result.substring (0,result.length ()-1);
    }

}

完成程序後,使用AndroidKiller進行修改時,將"我是正版"字符串修改成"我是盜版",在運行程序時,程序會直接閃退
在這裏插入圖片描述

3.3.2 在So層中進行簽名驗證

MainActivity:

package com.example.jnisignatureverify;

import androidx.appcompat.app.AppCompatActivity;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import java.security.MessageDigest;
import java.util.Locale;

public class MainActivity extends AppCompatActivity {
    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    protected TextView appSignaturesTv;
    protected TextView jniSignaturesTv;
    protected Button checkBtn;
    protected Button tokenBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        super.setContentView(R.layout.activity_main);

        initView();

        appSignaturesTv.setText(getSha1Value(MainActivity.this));
        jniSignaturesTv.setText(getSignaturesSha1(MainActivity.this));
    }

    private View.OnClickListener clickListener = new View.OnClickListener(){
        @Override
        public void onClick(View v) {
            boolean result = checkSha1(MainActivity.this);

            if(result){
                Toast.makeText(getApplicationContext(),"驗證通過",Toast.LENGTH_LONG).show();
            }else{
                Toast.makeText(getApplicationContext(),"驗證不通過,請檢查valid.cpp文件配置的sha1值",Toast.LENGTH_LONG).show();
            }
        }
    };

    private View.OnClickListener tokenClickListener = new View.OnClickListener(){
        @Override
        public void onClick(View v) {
            String result = getToken(MainActivity.this,"12345");

            Toast.makeText(getApplicationContext(),result,Toast.LENGTH_LONG).show();
        }
    };

    private void initView() {
        appSignaturesTv = (TextView) findViewById(R.id.app_signatures_tv);
        jniSignaturesTv = (TextView) findViewById(R.id.jni_signatures_tv);
        checkBtn = (Button) findViewById(R.id.check_btn);
        tokenBtn = (Button) findViewById(R.id.token_btn);

        checkBtn.setOnClickListener(clickListener);
        tokenBtn.setOnClickListener(tokenClickListener);
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String getSignaturesSha1(Context context);
    public native boolean checkSha1(Context context);
    public native String getToken(Context context,String userId);

//獲取apk當前的簽名
    public String getSha1Value(Context context) {
        try {
            PackageInfo info = context.getPackageManager().getPackageInfo(
                    context.getPackageName(), PackageManager.GET_SIGNATURES);
            byte[] cert = info.signatures[0].toByteArray();
            MessageDigest md = MessageDigest.getInstance("SHA1");
            byte[] publicKey = md.digest(cert);
            StringBuffer hexString = new StringBuffer();
            for (int i = 0; i < publicKey.length; i++) {
                String appendString = Integer.toHexString(0xFF & publicKey[i])
                        .toUpperCase(Locale.US);
                if (appendString.length() == 1)
                    hexString.append("0");
                hexString.append(appendString);
            }
            String result = hexString.toString();
            return result.substring(0, result.length());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

native-lib.cpp:

#include <stdio.h>
#include <stdlib.h>
#include <jni.h>
#include <android/log.h>
#include <cstring>

#define TAG    "jni-log"
#define LOGD(...)  __android_log_print(ANDROID_LOG_DEBUG,TAG,__VA_ARGS__)

//簽名信息
const char *app_sha1="59F8A6B86A367F0586F1A15DDDB63D75263C5D62";
const char hexcode[] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};

char* getSha1(JNIEnv *env, jobject context_object){
    //上下文對象
    jclass context_class = env->GetObjectClass(context_object);

    //反射獲取PackageManager
    jmethodID methodId = env->GetMethodID(context_class, "getPackageManager", "()Landroid/content/pm/PackageManager;");
    jobject package_manager = env->CallObjectMethod(context_object, methodId);
    if (package_manager == NULL) {
        //LOGD("package_manager is NULL!!!");
        return NULL;
    }

    //反射獲取包名
    methodId = env->GetMethodID(context_class, "getPackageName", "()Ljava/lang/String;");
    jstring package_name = (jstring)env->CallObjectMethod(context_object, methodId);
    if (package_name == NULL) {
        //LOGD("package_name is NULL!!!");
        return NULL;
    }
    env->DeleteLocalRef(context_class);

    //獲取PackageInfo對象
    jclass pack_manager_class = env->GetObjectClass(package_manager);
    methodId = env->GetMethodID(pack_manager_class, "getPackageInfo", "(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;");
    env->DeleteLocalRef(pack_manager_class);
    jobject package_info = env->CallObjectMethod(package_manager, methodId, package_name, 0x40);
    if (package_info == NULL) {
        LOGD("getPackageInfo() is NULL!!!");
        return NULL;
    }
    env->DeleteLocalRef(package_manager);

    //獲取簽名信息
    jclass package_info_class = env->GetObjectClass(package_info);
    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, fieldId);
    if (signature_object_array == NULL) {
        LOGD("signature is NULL!!!");
        return NULL;
    }
    jobject signature_object = env->GetObjectArrayElement(signature_object_array, 0);
    env->DeleteLocalRef(package_info);

    //簽名信息轉換成sha1值
    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);

    //轉換成char
    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';

    LOGD("hex_sha %s ",hex_sha);
    return hex_sha;
}

jboolean checkValidity(JNIEnv *env,char *sha1){
    //比較簽名
    if (strcmp(sha1,app_sha1)==0)
    {
        LOGD("驗證成功");
        return true;
    }
    LOGD("驗證失敗");
    return false;
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_jnisignatureverify_MainActivity_getSignaturesSha1(JNIEnv *env, jobject thiz,
                                                                   jobject context) {
    // TODO: implement getSignaturesSha1()
    return env->NewStringUTF(app_sha1);
}extern "C"
JNIEXPORT jboolean JNICALL
Java_com_example_jnisignatureverify_MainActivity_checkSha1(JNIEnv *env, jobject thiz,
                                                           jobject contextObject) {
    // TODO: implement checkSha1()
    char *sha1 = getSha1(env,contextObject);

    jboolean result = checkValidity(env,sha1);
    return result;
}extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_jnisignatureverify_MainActivity_getToken(JNIEnv *env, jobject thiz,
                                                          jobject contextObject, jstring user_id) {
    // TODO: implement getToken()
    char *sha1 = getSha1(env,contextObject);
    jboolean result = checkValidity(env,sha1);

    if(result){
        return env->NewStringUTF("獲取Token成功");
    }else{
        return env->NewStringUTF("獲取失敗,請檢查native-lib.cpp文件配置的sha1值");
    }
}

activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_margin="16dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.jnisignatureverify.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:orientation="vertical"
        android:layout_height="wrap_content">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="APP簽名信息:"/>

        <TextView
            android:id="@+id/app_signatures_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </LinearLayout>

    <LinearLayout
        android:layout_marginTop="16dp"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="jni配置的簽名信息:"/>

        <TextView
            android:id="@+id/jni_signatures_tv"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </LinearLayout>

    <Button
        android:id="@+id/check_btn"
        android:layout_marginTop="16dp"
        android:text="驗證簽名是否正確"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <Button
        android:id="@+id/token_btn"
        android:layout_marginTop="16dp"
        android:text="獲取token"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章