Android | 簽名安全

檢驗和簽名

校驗開發者在數據傳送時采用的一種校正數據的一種方式, 常見的校驗有:簽名校驗(最常見)、dexcrc校驗、apk完整性校驗、路徑文件校驗等。

通過對 Apk 進行簽名,開發者可以證明對 Apk 的所有權和控制權,可用于安裝和更新其應用。而在 Android 設備上的安裝 Apk ,如果是一個沒有被簽名的 Apk,則會被拒絕安裝。在安裝 Apk 的時候,軟件包管理器也會驗證 Apk 是否已經被正確簽名,并且通過簽名證書和數據摘要驗證是否合法沒有被篡改。只有確認安全無篡改的情況下,才允許安裝在設備上。

簡單來說,APK 的簽名主要作用有兩個:

  1. 證明 APK 的所有者。
  2. 允許 Android 市場和設備校驗 APK 的正確性。

Android 目前支持以下四種應用簽名方案:
v1 方案:基于 JAR 簽名。
v2 方案:APK 簽名方案 v2(在 Android 7.0 中引入)
v3 方案:APK 簽名方案 v3(在 Android 9 中引入)
v4 方案:APK 簽名方案 v4(在 Android 11 中引入)

簽名校驗-防君子不防小人

就是驗證APK是否被重新簽名過,這種校驗是在代碼層面的校驗。

校驗的處理通常是:

kill/killProcess-----kill/KillProcess()可以殺死當前應用活動的進程,這一操作將會把所有該進程內的資源(包括線程全部清理掉).當然,由于ActivityManager時刻監聽著進程,一旦發現進程被非正常Kill,它將會試圖去重啟這個進程。這就是為什么,有時候當我們試圖這樣去結束掉應用時,發現它又自動重新啟動的原因.system.exit-----殺死了整個進程,這時候活動所占的資源也會被釋放。finish----------僅僅針對Activity,當調用finish()時,只是將活動推向后臺,并沒有立即釋放內存,活動的資源并沒有被清理

校驗的方法

private boolean SignCheck() {String trueSignMD5 = "d0add9987c7c84aeb7198c3ff26ca152";String nowSignMD5 = "";try {// 得到簽名的MD5PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(),PackageManager.GET_SIGNATURES);Signature[] signs = packageInfo.signatures;String signBase64 = Base64Util.encodeToString(signs[0].toByteArray());nowSignMD5 = MD5Utils.MD5(signBase64);} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();}return trueSignMD5.equals(nowSignMD5);
}

這種校驗的方式是在代碼層面,對于有心者來說破解毫無難度。

可以適當的增加校驗的難度:

package com.ctuav.common.utilsimport android.content.Context
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.os.Process
import android.util.Log
import java.security.MessageDigest/*** author  : ls* time    : 2025/6/19 08:51* desc    : 防君子不防小人*/
object SecurityUtils {// 用于混淆的密鑰private  val SIGNATURE_KEY = "d8q1_Kp9#mN3vX7"// 這里請替換為你實際的簽名SHA1(大寫、無冒號)private const val CORRECT_SIGNATURE = "-----------"/*** 多重簽名校驗*/@JvmStaticfun verifyAppSignature(context: Context): Boolean {try {// 1. 基礎簽名校驗val primary = checkPrimarySignature(context)if (!primary) {System.exit(0)return false}// 2. 二次加密校驗val secondary = checkSecondarySignature(context)if (!secondary) {System.exit(0)return false}// 3. 反調試措施if (!antiDebugCheck(context)) {System.exit(0)return false}return true} catch (e: Exception) {System.exit(0)return false}}// 基礎簽名校驗(SHA1)private fun checkPrimarySignature(context: Context): Boolean {return try {val packageInfo = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {context.packageManager.getPackageInfo(context.packageName,PackageManager.GET_SIGNING_CERTIFICATES)} else {context.packageManager.getPackageInfo(context.packageName,PackageManager.GET_SIGNATURES)}val signatures = if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {packageInfo.signingInfo.apkContentsSigners} else {packageInfo.signatures}if (signatures.isEmpty()) return falseval cert = signatures[0].toByteArray()val md = MessageDigest.getInstance("SHA1")// 生成無冒號、全大寫的 SHA1 字符串val sha1 = md.digest(cert).joinToString("") { "%02X".format(it) }Log.d("---------", "sha1: $sha1 md: $md")// 混淆校驗obfuscateCheck(sha1)} catch (e: Exception) {false}}// 二次加密校驗private fun checkSecondarySignature(context: Context): Boolean {return try {val pid = Process.myPid()val uid = Process.myUid()val combined = "$pid:$uid:${context.packageName}"val encrypted = encryptData(combined)validateEncryption(encrypted)} catch (e: Exception) {false}}// 反調試措施(安裝時間校驗+隨機延時)private fun antiDebugCheck(context: Context): Boolean {val now = System.currentTimeMillis()val installTime = context.packageManager.getPackageInfo(context.packageName, 0).firstInstallTimeif (now - installTime < 0) return falsetry {Thread.sleep((1..5).random().toLong())} catch (_: Exception) {}return true}// 簽名混淆校驗private fun obfuscateCheck(signature: String): Boolean {fun obfuscate(str: String): Int {return str.toByteArray().map { it.toInt() xor SIGNATURE_KEY.hashCode() }.sum()}return obfuscate(signature) == obfuscate(CORRECT_SIGNATURE)}// 簡單加密算法private fun encryptData(data: String): String {return data.toByteArray().map { (it.toInt() xor SIGNATURE_KEY.hashCode()) + 1 }.joinToString("")}// 加密校驗private fun validateEncryption(encrypted: String): Boolean {return try {val checkSum = encrypted.toCharArray().map { it.code }.reduce { acc, i -> acc xor i }checkSum != 0} catch (e: Exception) {false}}
}

這里對簽名進行了加密二次校驗和混淆校驗,此處的簽名還是保留在客戶端的,最好的做法是校驗的工作放在服務端處理。

簽名校驗是如何被破解的?

反編譯、二次打包
+ 通過反編譯工具(如 jadx、apktool)獲取應用源碼 + 定位簽名校驗的代碼位置 + 修改校驗邏輯或替換正確的簽名值 + 重新打包簽名
Hook
+ 使用 Xposed、Frida 等 Hook 框架 + Hook 簽名校驗相關方法 + 直接返回 true 或修改返回值 + 無需重新打包,運行時動態修改
修改smali
定位、找到地方,直接替換或者刪除判斷邏輯,這也是去除廣告、VIP的奇技淫巧。

該怎么辦

大多數的基礎措施都無法攔住有心者,只是增加難度和成本。
增加class.dex的校驗
重新打包通常都會修改源文件,需要重新打包編譯,所以生成的dex的 Hash值是有變化的,可以對其增加校驗,這個工作和代理檢測、簽名校驗一樣是加在業務端的。
    public static long getApkCRC(Context context) {ZipFile zf;try {zf = new ZipFile(context.getPackageCodePath());// 獲取apk安裝后的路徑ZipEntry ze = zf.getEntry("classes.dex");return ze.getCrc();}catch (Exception e){return 0;}}

判斷邏輯

    String srcStr = MD5Util.getMD5(String.valueOf(CommentUtils.getApkCRC(getApplicationContext())));if(!srcStr.equals(getString(R.string.classes_txt))){// 可能被重編譯了,需要退出android.os.Process.killProcess(android.os.Process.myPid());}

比較脆弱 可以進行二次較密增加破解的難度,依然是擋不住有心者。

加上Native 層簽名校驗
這可能是最靠譜的措施了,C++和SO的安全度較高,逆向的難度大,同樣的平時處理一些加密的操作的時候寫在cpp里也是最好的。
  1. Java 層通過 JNI 調用 native 方法
  2. Native 層獲取包名、簽名信息
  3. Native 層對簽名做校驗(如 SHA1、MD5、Base64 等)
  4. 校驗結果返回 Java 層,決定是否繼續運行
#include <jni.h>
#include <string>
#include <android/log.h>
#include <time.h>
#include <string.h>#define LOG_TAG "NativeCheck"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)// 簽名SHA1(無冒號、全大寫)
const char* CORRECT_SHA1 = "-----";
// 動態密鑰生成用的鹽值
const char* SALT = "d8q1_Kp9#mN3vX7";// 生成動態密鑰
std::string generateDynamicKey() {time_t now = time(nullptr);std::string key;for(int i = 0; i < strlen(SALT); i++) {key += (SALT[i] ^ ((now >> (i % 8)) & 0xFF));}return key;
}// 二次加密
std::string encryptSignature(const std::string& signature, const std::string& key) {std::string encrypted;for(size_t i = 0; i < signature.length(); i++) {encrypted += signature[i] ^ key[i % key.length()];}return encrypted;
}// 安全比較
bool secureCompare(const std::string& a, const std::string& b) {if(a.length() != b.length()) return false;int result = 0;for(size_t i = 0; i < a.length(); i++) {result |= a[i] ^ b[i];}return result == 0;
}extern "C"
JNIEXPORT jboolean JNICALL
Java_com_ctuav_common_utils_SecurityUtils_verifySignatureNative(JNIEnv *env, jobject thiz, jstring sha1_) {// 獲取傳入的SHA1const char* actualSha1 = env->GetStringUTFChars(sha1_, 0);// 生成動態密鑰std::string dynamicKey = generateDynamicKey();// 對實際SHA1和正確SHA1都進行二次加密std::string encryptedActual = encryptSignature(actualSha1, dynamicKey);std::string encryptedCorrect = encryptSignature(CORRECT_SHA1, dynamicKey);// 安全比較bool result = secureCompare(encryptedActual, encryptedCorrect);// 釋放資源env->ReleaseStringUTFChars(sha1_, actualSha1);// 混淆返回結果return (result ^ 1) ^ 1 ? JNI_TRUE : JNI_FALSE;
} 

代碼層面的校驗和native層的校驗交叉,破解的難度又上去了。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/85380.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/85380.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/85380.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Android14 耳機按鍵拍照

在相機拍照預覽界面 通過耳機按鍵實現拍照功能 耳機按鍵定義 frameworks/base/core/java/android/view/KeyEvent.java public static final int KEYCODE_HEADSETHOOK 79;相機界面 拍照邏輯 DreamCamera2\src\com\android\camera\PhotoModule.java Override public bool…

【AI作畫】第2章comfy ui的一般輸入節點,文本框的類型和輸入形式

目錄 CLIP文本編碼器 條件輸出和文本輸出 轉換某一變量為輸入 展示作品集 在默認的工作流之外&#xff0c;我們如何自己添加節點呢&#xff1f; 一般我們用到的sampler采樣器在“鼠標右鍵——添加節點——采樣——K采樣器” 我們用的clip文本編碼器在“鼠標右鍵——添加節…

vue3仿高德地圖官網路況預測時間選擇器

<template><div class"time-axis-container"><div class"time-axis" ref"axisRef"><!-- 刻度線 - 共25個刻度(0-24) --><divv-for"hour in 25":key"hour - 1"class"tick-mark":class&…

ZArchiver:高效解壓縮,輕松管理文件

在數字時代&#xff0c;文件的壓縮與解壓已成為我們日常操作中不可或缺的一部分。無論是接收朋友分享的大文件&#xff0c;還是下載網絡資源&#xff0c;壓縮包的處理都極為常見。ZArchiver正是一款為安卓用戶精心打造的解壓縮軟件&#xff0c;它以強大的功能、簡潔的界面和高效…

1432.改變一個整數能得到的最大差值

貪心思想&#xff0c;為了得到最大差&#xff0c;想辦法變成一個最大的數和一個最小的數。 這里有規則&#xff0c;從最高位開始&#xff0c; 變成最大&#xff0c;如果<9&#xff0c;則將該數位代表的數都變成9&#xff0c;如果該數位已經是9了&#xff0c;則將下一個數位…

前端跨域解決方案(4):postMessage

1 postMessage 核心 postMessage 是現代瀏覽器提供的跨域通信標準 API&#xff0c;允許不同源的窗口&#xff08;如主頁面與 iframe、彈出窗口、Web Worker&#xff09;安全交換數據。相比其他跨域方案&#xff0c;它的核心優勢在于&#xff1a; 雙向通信能力&#xff1a;支持…

大語言模型指令集全解析

在大語言模型的訓練與優化流程中&#xff0c;指令集扮演著關鍵角色&#xff0c;它直接影響模型對任務的理解與執行能力。以下對常見指令集展開詳細介紹&#xff0c;涵蓋構建方式、規模及適用場景&#xff0c;助力開發者精準選用 為降低指令數據構建成本&#xff0c;學術界和工…

OpenCV CUDA模塊設備層-----用于封裝CUDA紋理對象+ROI偏移量的一個輕量級指針類TextureOffPtr()

操作系統&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 編程語言&#xff1a;C11 算法描述 TextureOffPtr<T, R> 是 OpenCV 的 CUDA 模塊&#xff08;opencv_cudev&#xff09;中用于封裝 CUDA 紋理對象 ROI 偏移量 的一個輕量級指…

Python 數據分析10

2.3.3其他 除了前面所介紹的常用語數據挖掘建模的庫之外&#xff0c;還有許多庫也運用于數據挖掘建模&#xff0c;如jieba、SciPy、OpenCV、Pillow等。 1.jieba jieba是一個被廣泛使用的Python第三方中文分詞庫。jieba使用簡單&#xff0c;并且支持Python、R、C等多種編程語言的…

css 制作一個可以旋轉的水泵效果

如圖&#xff0c;項目里面有一個小圖片可以旋轉&#xff0c;達到看起來像是一個在工作的水泵。我使用css旋轉動畫實現。 一、HTML結構部分 <div className"ceshixuanzhuan"><img src{lunkuo} className"lunkuo"/><img src{yepian} classN…

數據結構期末程序題型

一、 隊列 1、簡單模擬隊列排列 #include<bits/stdc.h> using namespace std; int main(){int n;cin>>n;queue<int>q;string str;while(true){cin>>str;if(str"#")break;if(str"In"){int t;cin>>t;if(q.size()<n){q.pu…

SpringCloud+Vue汽車、單車充電樁源碼實現:從架構設計到核心模塊解析

智慧充電管理平臺技術實現&#xff1a;從架構設計到核心模塊解析 智慧充電管理平臺作為新能源汽車生態的核心基礎設施&#xff0c;需要實現充電設備管理、訂單處理、數據統計分析等復雜功能。本文將從技術架構、核心模塊設計、關鍵技術實現三個維度&#xff0c;深度解析平臺的…

Kafka入門及實戰應用指南

1、Kafka概述 Apache Kafka是由LinkedIn公司于2010年開發的一款分布式消息系統&#xff0c;旨在解決當時傳統消息隊列&#xff08;如ActiveMQ、RabbitMQ&#xff09;在高吞吐量和實時性場景下的性能瓶頸。隨著LinkedIn內部對實時日志處理、用戶行為追蹤等需求的激增&#xff0…

智能指針 c++

C 智能指針詳解 智能指針是 C11 引入的內存管理工具&#xff0c;位于 <memory> 頭文件中&#xff0c;用于自動管理動態分配的內存&#xff0c;防止內存泄漏。主要類型如下&#xff1a; 1. std::unique_ptr (獨占所有權) 特點&#xff1a;唯一擁有所指對象&#xff0c;不…

Python應用八股文

大家好!在 Python 學習的道路上&#xff0c;掌握一些基礎知識要點至關重要&#xff0c;這些要點常被稱為“Python 八股”。以下是對它們的簡易總結&#xff0c;幫助你快速回顧和鞏固 Python 的核心概念。 一、數據結構 列表&#xff08;List&#xff09;&#xff1a;有序可變序…

【技術深度】領碼SPARK破解微服務數據依賴困局:架構設計與實踐指南

——深度解析分布式數據冗余與異步消息機制&#xff0c;驅動企業數字化轉型加速 ? 核心摘要 本文從技術架構與工程實現的角度&#xff0c;系統講解領碼SPARK融合平臺如何精準解決微服務架構下數據依賴“卡脖子”問題。通過設計高效的數據冗余模型和完善的異步消息更新機制&am…

關于前端的防抖和節流

給我解釋下 前端開發中的防抖和節流 并舉個具體的例子 防抖&#xff08;Debounce&#xff09;與節流&#xff08;Throttle&#xff09;詳解 在前端開發中&#xff0c;防抖&#xff08;Debounce&#xff09; 和 節流&#xff08;Throttle&#xff09; 是兩種優化高頻觸發事件的…

React-router 多類型歷史記錄棧

react-router 為了滿足開發者更多路由歷史存儲場景&#xff0c;提供了以下幾種模式&#xff1a; 瀏覽器原生歷史記錄 瀏覽器 hash 內存型 服務端記錄 以上實現分別對應于一下 API 實現&#xff1a; createBrowserRouter&#xff1a;瀏覽器提供的歷史管理。 createHashRou…

java設計模式[3]之結構型模式

文章目錄 一 代理模式1.1 靜態代理1.1.1 靜態代理的結構1.1.2 靜態代理的特點1.1.3 靜態代理的應用場景1.1.4 靜態代理的案例代碼 1.2 JDK動態代理1.2.1 JDK動態代理概述1.2.2 JDK動態代理案例代碼1.2.3 JDK動態代理的應用場景1.2.4 JDK動態代理的特點1.2.5 與創建型模式的區別…

鴻蒙Harmony測試-wukong穩定性工具(類似Android的Monkey測試)

一、功能介紹 wukong是系統自帶的一種命令行工具&#xff0c;支持Ability的隨機事件注入、控件注入、異常捕獲、報告生成和對Ability數據遍歷截圖等特性。通過模擬用戶行為&#xff0c;對系統或應用進行穩定性壓力測試。wukong分為隨機測試、專項測試和專注測試。 隨機測試是指…