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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈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分为随机测试、专项测试和专注测试。 随机测试是指…