固定APK调试文件测试成功

This commit is contained in:
2026-01-23 21:05:41 +08:00
parent a3bc90d9b8
commit 730022a9f0
7 changed files with 458 additions and 104 deletions

View File

@@ -6,7 +6,17 @@
<!-- 拥有完全的网络访问权限 -->
<uses-permission android:name="android.permission.INTERNET"/>
<!-- 读取您共享存储空间中的内容 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!-- 修改或删除您共享存储空间中的内容 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<!-- MANAGE_EXTERNAL_STORAGE -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<application
android:requestLegacyExternalStorage="true"
android:networkSecurityConfig="@xml/network_security_config">
<activity

View File

@@ -48,18 +48,23 @@ public class SignGetDialog extends Dialog {
// 核心:获取签名+调用APPUtils校验
private void initSignAndCheck() {
// 1. 获取当前应用签名
String sign = getCurrentSign();
if (sign == null) {
etSignFingerprint.setText("签名获取失败");
} else {
// 签名字符串转0/1 bit数组每2个bit加空格每16位换行下一行无前置空格
String bitArrayStr = convertSignToBitArrayWithWrap(sign);
etSignFingerprint.setText(bitArrayStr);
}
LogUtils.d(TAG, "当前应用签名:" + sign);
// String sign = getCurrentSign();
// if (sign == null) {
// etSignFingerprint.setText("签名获取失败");
// } else {
// // 签名字符串转0/1 bit数组每2个bit加空格每16位换行下一行无前置空格
// String bitArrayStr = convertSignToBitArrayWithWrap(sign);
// etSignFingerprint.setText(bitArrayStr);
// }
// LogUtils.d(TAG, "当前应用签名:" + sign);
// 2. 正版校验+显示结果
APPUtils.checkAppValid(mContext, new APPUtils.CheckResultCallback() {
// 调用处直接删除base64SignFingerprint参数即可
new APPUtils().checkAPKSignature(
mContext,
"WinBoLL", // projectName
"WinBoLL_15.11.11.apk", // apkFileName
new APPUtils.CheckResultCallback() {
@Override
public void onResult(boolean isValid, String message) {
String szOfficialMessage;
@@ -81,7 +86,8 @@ public class SignGetDialog extends Dialog {
ToastUtils.show(szOfficialMessage);
tvAuthResult.setText(szOfficialMessage);
}
});
}
);
}
@@ -123,14 +129,14 @@ public class SignGetDialog extends Dialog {
}
// 获取签名复用SignGetUtils逻辑避免重复代码
private String getCurrentSign() {
try {
return SignGetUtils.getSignStr(mContext); // 复用工具类逻辑
} catch (Exception e) {
LogUtils.e(TAG, "获取签名失败", e);
return null;
}
}
// private String getCurrentSign() {
// try {
// return SignGetUtils.getSignStr(mContext); // 复用工具类逻辑
// } catch (Exception e) {
// LogUtils.e(TAG, "获取签名失败", e);
// return null;
// }
// }
// 校验签名是否合法匹配APPUtils目标签名
// private boolean isSignValid() {

View File

@@ -0,0 +1,180 @@
package cc.winboll.studio.libappbase.utils;
import cc.winboll.studio.libappbase.LogUtils;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* APK文件工具类单例- 终极稳定版
* 适配APK的PKCS7格式CERT.RSA直接读取原始字节与客户端getAppSignFingerprint1:1对齐
* 解决Too short解析异常保证签名稳定解析
* @Author ZhanGSKen<zhangsken@qq.com>
*/
public class APKFileUtils {
private static volatile APKFileUtils sInstance;
private static final String CONFIG_SECTION = "APP";
private static final String KEY_APKS_FOLDER = "apks_folder_path";
private String apksRootPath;
private APKFileUtils() {}
public static void init() {
if (sInstance == null) {
synchronized (APKFileUtils.class) {
if (sInstance == null) {
sInstance = new APKFileUtils();
//sInstance.loadConfig();
}
}
}
}
public static APKFileUtils getInstance() {
if (sInstance == null) {
LogUtils.e("APKFileUtils", "请先调用init()初始化");
throw new IllegalStateException("APKFileUtils未初始化请先调用init()");
}
return sInstance;
}
// private void loadConfig() {
// try {
// apksRootPath = IniConfigUtils.getConfigValue(CONFIG_SECTION, KEY_APKS_FOLDER, "").trim();
// if (apksRootPath.isEmpty()) {
// LogUtils.e("APKFileUtils", "apks_folder_path配置为空");
// return;
// }
// File rootDir = new File(apksRootPath);
// if (!rootDir.exists()) rootDir.mkdirs();
// LogUtils.i("APKFileUtils", "APK根目录加载成功" + apksRootPath);
// } catch (Exception e) {
// LogUtils.e("APKFileUtils", "加载APK根目录配置失败", e);
// apksRootPath = "";
// }
// }
public static boolean checkAPKSignature(String projectName, String apkFileName, String clientSignBase64) {
return getInstance().doCheckAPK(projectName, apkFileName, clientSignBase64);
}
private boolean doCheckAPK(String projectName, String apkFileName, String clientSignBase64) {
// 1. 入参校验
if (projectName == null || projectName.trim().isEmpty()
|| apkFileName == null || apkFileName.trim().isEmpty()
|| clientSignBase64 == null || clientSignBase64.trim().isEmpty()) {
LogUtils.w("APKFileUtils", "参数不能为空projectName/apkFileName/clientSignBase64");
return false;
}
// 2. 根目录校验
if (apksRootPath == null || apksRootPath.trim().isEmpty()) {
LogUtils.w("APKFileUtils", "APK根目录未配置apks_folder_path");
return false;
}
// 3. APK文件校验
String apkFullPath = apksRootPath + File.separator + projectName
+ File.separator + "tag" + File.separator + apkFileName;
File apkFile = new File(apkFullPath);
if (!apkFile.exists() || !apkFile.isFile() || !apkFileName.endsWith(".apk")) {
LogUtils.w("APKFileUtils", "APK文件不存在或格式错误" + apkFullPath);
return false;
}
// 4. 解析APK签名终极稳定版直接读取CERT.RSA原始字节与客户端对齐
String apkSignBase64 = getAPKSignFingerprint(apkFile);
if (apkSignBase64 == null || apkSignBase64.trim().isEmpty()) {
LogUtils.w("APKFileUtils", "解析APK签名失败返回null/空值");
return false;
}
// 5. 签名对比
LogUtils.d("APKFileUtils", "【签名对比】APK解析签名" + apkSignBase64);
LogUtils.d("APKFileUtils", "【签名对比】客户端传入签名:" + clientSignBase64);
boolean isMatch = apkSignBase64.equals(clientSignBase64);
LogUtils.i("APKFileUtils", "【签名对比结果】" + (isMatch ? "✅ 匹配" : "❌ 不匹配"));
return isMatch;
}
/**
* 终极稳定解析与客户端getAppSignFingerprint逻辑1:1完全对齐
* 直接读取CERT.RSA原始字节适配PKCS7格式跳过证书解析彻底解决Too short异常
*/
public String getAPKSignFingerprint(File apkFile) {
JarFile jarFile = null;
try {
jarFile = new JarFile(apkFile);
// 适配APK标准签名文件CERT.RSA兼容大小写
JarEntry sigEntry = jarFile.getJarEntry("META-INF/CERT.RSA");
if (sigEntry == null) {
sigEntry = jarFile.getJarEntry("META-INF/cert.rsa");
if (sigEntry == null) {
LogUtils.w("APKFileUtils", "APK中未找到META-INF/CERT.RSA含大小写");
return null;
}
}
// 核心直接读取CERT.RSA原始字节和客户端Signature.toByteArray()底层一致适配PKCS7
InputStream is = jarFile.getInputStream(sigEntry);
byte[] sigRawBytes = readStreamToBytes(is);
if (sigRawBytes == null || sigRawBytes.length == 0) {
LogUtils.w("APKFileUtils", "读取CERT.RSA原始字节为空");
return null;
}
// 与客户端完全一致的流程:原始字节 → SHA1摘要 → Base64编码去换行=NO_WRAP
MessageDigest md = MessageDigest.getInstance("SHA1");
md.update(sigRawBytes);
byte[] sha1Digest = md.digest();
// 标准Base64编码去换行符等效Android的Base64.NO_WRAP
String signBase64 = Base64.getEncoder().encodeToString(sha1Digest)
.replaceAll("\\r", "").replaceAll("\\n", "");
LogUtils.d("APKFileUtils", "APK解析出的签名(Base64)" + signBase64);
return signBase64;
} catch (NoSuchAlgorithmException e) {
LogUtils.e("APKFileUtils", "解析签名失败SHA1算法不存在", e);
return null;
} catch (Exception e) {
LogUtils.e("APKFileUtils", "解析APK签名异常", e);
e.printStackTrace();
return null;
} finally {
if (jarFile != null) {
try {
jarFile.close();
} catch (IOException e) {
LogUtils.e("APKFileUtils", "关闭JarFile流失败", e);
}
}
}
}
/**
* 稳定的流转字节数组:适配各种输入流,无空指针/截断问题
*/
private byte[] readStreamToBytes(InputStream is) throws IOException {
if (is == null) {
LogUtils.w("APKFileUtils", "readStreamToBytes: 输入流为null");
return new byte[0];
}
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buffer = new byte[4096]; // 加大缓冲区,适配大签名文件
int len;
while ((len = is.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
byte[] result = bos.toByteArray();
// 关闭流(顺序不可换)
is.close();
bos.close();
return result;
}
}

View File

@@ -1,18 +1,26 @@
package cc.winboll.studio.libappbase.utils;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.os.Handler;
import android.os.Looper;
import android.util.Base64;
import cc.winboll.studio.libappbase.GlobalApplication;
import cc.winboll.studio.libappbase.LogUtils;
import cc.winboll.studio.libappbase.models.SignCheckResponse;
import com.google.gson.Gson;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.OkHttpClient;
@@ -22,7 +30,7 @@ import okhttp3.Response;
/**
* @Author 豆包&ZhanGSKen<zhangsken@qq.com>
* @Date 2026/01/20 19:17
* @Describe APPUtils 应用包名、签名校验工具类OKHTTP网络校验版
* @Describe APPUtils 应用包名、签名校验工具类OKHTTP网络校验版兼容Java7含URL编码+APK包签名校验
*/
public class APPUtils {
public static final String TAG = "APPUtils";
@@ -36,46 +44,75 @@ public class APPUtils {
/**
* 检查应用合法性(包名校验+OKHTTP网络校验签名
* @param context 上下文
* @param projectName 项目名称(入参)
* @param apkFileName APK文件名入参
* @param callback 校验结果回调(主线程回调)
*/
public static void checkAppValid(Context context, final CheckResultCallback callback) {
public void checkAPKSignature(Context context, String projectName, String apkFileName, final CheckResultCallback callback) {
if (context == null) {
LogUtils.w(TAG, "checkAppValid: context为空跳过校验");
if (callback != null) callback.onResult(false, "context为空");
LogUtils.w(TAG, "checkAPKSignature: context为空跳过校验");
if (callback != null) {
callback.onResult(false, "context为空");
}
return;
}
// 校验剩余入参非空
if (projectName == null || projectName.trim().isEmpty()
|| apkFileName == null || apkFileName.trim().isEmpty()) {
String errorMsg = "校验入参为空projectName/apkFileName不可为空";
LogUtils.e(TAG, "checkAPKSignature: " + errorMsg);
if (callback != null) {
callback.onResult(false, errorMsg);
}
return;
}
// 2. 获取当前应用签名SHA1+Base64和证书生效时间
String currentSign = getAppSignFingerprint(context);
long certValidTime = getCertValidTime(context); // 证书生效时间(毫秒时间戳)
// 方式1从PackageManager获取签名原逻辑快速
APKFileUtils.init();
File apkFile = new File("/sdcard/WinBoLLStudio/APKs/WinBoLL/tag/WinBoLL_15.11.11.apk");
String currentSign = APKFileUtils.getInstance().getAPKSignFingerprint(apkFile);
//String currentSign = APKFileUtils.getInstance().getAPKSignFingerprint(getCurrentAppApkFile(context));
LogUtils.d(TAG, String.format("currentSign : %s", currentSign));
// 方式2从当前应用APK包文件解析签名兜底和服务端校验逻辑1:1对齐
// if (currentSign == null) {
// LogUtils.w(TAG, "checkAPKSignature: 从PackageManager获取签名失败尝试从APK包文件解析");
// currentSign = getAPKSignFingerprint(getCurrentAppApkFile(context));
// }
if (currentSign == null) {
String errorMsg = "获取应用签名失败";
LogUtils.e(TAG, "checkAppValid: " + errorMsg);
if (callback != null) callback.onResult(false, errorMsg);
String errorMsg = "获取应用签名失败PackageManager+APK包解析均失败";
LogUtils.e(TAG, "checkAPKSignature: " + errorMsg);
if (callback != null) {
callback.onResult(false, errorMsg);
}
return;
}
LogUtils.d(TAG, "checkAPKSignature: 应用最终签名SHA1+Base64=" + currentSign);
// 新增对currentSign进行Base64二次加密URL安全编码,避免特殊字符
String encryptedSign = base64Encode(currentSign);
LogUtils.d(TAG, "checkAppValid: 原始签名=" + currentSign + "Base64二次加密后=" + encryptedSign);
// 对动态参数做URL编码避免特殊字符/、=、&、空格等)导致解析异常
String encodeProjectName = urlEncode(projectName);
String encodeApkFileName = urlEncode(apkFileName);
String encodeSignature = urlEncode(currentSign);
LogUtils.d(TAG, "checkAPKSignature: URL编码后-项目名=" + encodeProjectName + "APK名=" + encodeApkFileName + ",签名=" + encodeSignature);
// 3. 构建请求URL(拼接加密后的签名参数
String requestUrl = String.format("%s?signature=%s&validTime=%d",
GlobalApplication.getWinbollHost() + CHECK_API_URI,
encryptedSign, // 替换为加密后的签名
certValidTime);
LogUtils.d(TAG, "checkAppValid: 发起网络校验请求URL=" + requestUrl);
// 构建请求URL - 拼接**编码后**的参数
String requestUrl = String.format("%s?projectName=%s&apkFileName=%s&signature=%s",
GlobalApplication.getWinbollHost() + CHECK_API_URI,
encodeProjectName,
encodeApkFileName,
encodeSignature);
LogUtils.d(TAG, "checkAPKSignature: 发起网络校验请求URL=" + requestUrl);
// 4. OKHTTP发起异步GET请求
// OKHTTP发起异步GET请求
Request request = new Request.Builder().url(requestUrl).build();
sOkHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
final String errorMsg = "网络校验请求失败:" + e.getMessage();
LogUtils.e(TAG, "checkAppValid: " + errorMsg, e);
LogUtils.e(TAG, "checkAPKSignature: " + errorMsg, e);
if (callback != null) {
// 切换到主线程回调
new android.os.Handler(android.os.Looper.getMainLooper()).post(new Runnable() {
// 切换到主线程回调Java7 匿名Runnable
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
callback.onResult(false, errorMsg);
@@ -88,13 +125,14 @@ public class APPUtils {
public void onResponse(Call call, Response response) throws IOException {
if (response.isSuccessful() && response.body() != null) {
String responseJson = response.body().string();
LogUtils.d(TAG, "checkAppValid: 网络校验响应JSON=" + responseJson);
LogUtils.d(TAG, "checkAPKSignature: 网络校验响应JSON=" + responseJson);
// 解析JSON响应
SignCheckResponse checkResponse = sGson.fromJson(responseJson, SignCheckResponse.class);
final SignCheckResponse checkResponse = sGson.fromJson(responseJson, SignCheckResponse.class);
final boolean isValid = checkResponse != null && checkResponse.isValid();
final String msg = checkResponse != null ? checkResponse.getMessage() : "响应解析失败";
if (callback != null) {
new android.os.Handler(android.os.Looper.getMainLooper()).post(new Runnable() {
// 切换到主线程回调Java7 匿名Runnable
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
callback.onResult(isValid, msg);
@@ -103,9 +141,10 @@ public class APPUtils {
}
} else {
final String errorMsg = "网络校验响应失败code=" + response.code();
LogUtils.e(TAG, "checkAppValid: " + errorMsg);
LogUtils.e(TAG, "checkAPKSignature: " + errorMsg);
if (callback != null) {
new android.os.Handler(android.os.Looper.getMainLooper()).post(new Runnable() {
// 切换到主线程回调Java7 匿名Runnable
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
callback.onResult(false, errorMsg);
@@ -118,23 +157,164 @@ public class APPUtils {
}
/**
* 新增Base64加密工具URL安全编码避免特殊字符影响URL拼接
* @param content 待加密内容
* @return 加密后的Base64字符串
* 工具方法获取当前应用的APK包文件对象
* @param context 上下文
* @return 当前应用APK文件File失败返回null
*/
private static String base64Encode(String content) {
private File getCurrentAppApkFile(Context context) {
try {
// 使用URL安全的Base64编码替换+为-/为_去除=
byte[] contentBytes = content.getBytes("UTF-8");
return Base64.encodeToString(contentBytes, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP);
// 从PackageManager获取当前应用的APK安装路径
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(
context.getPackageName(), 0
);
String apkPath = appInfo.sourceDir;
LogUtils.d(TAG, "getCurrentAppApkFile: 当前应用APK路径=" + apkPath);
File apkFile = new File(apkPath);
return apkFile.exists() && apkFile.isFile() ? apkFile : null;
} catch (PackageManager.NameNotFoundException e) {
LogUtils.e(TAG, "getCurrentAppApkFile: 获取应用APK路径失败", e);
return null;
} catch (Exception e) {
LogUtils.e(TAG, "base64Encode: 加密失败", e);
return content; // 加密失败则返回原始内容,避免请求异常
LogUtils.e(TAG, "getCurrentAppApkFile: 未知异常", e);
return null;
}
}
/**
* 获取当前应用签名SHA1指纹BASE64编码
* 核心方法复刻Android系统Signature解析逻辑
* 从CERT.RSA提取与signatures[0].toByteArray()一致的字节再走SHA1+Base64
*/
private String getAPKSignFingerprint(File apkFile) {
// 先判空APK文件避免空指针
if (apkFile == null || !apkFile.exists() || !apkFile.isFile()) {
LogUtils.w(TAG, "getAPKSignFingerprint: APK文件为空或不存在");
return null;
}
JarFile jarFile = null;
try {
jarFile = new JarFile(apkFile);
JarEntry sigEntry = jarFile.getJarEntry("META-INF/CERT.RSA");
if (sigEntry == null) {
LogUtils.w(TAG, "getAPKSignFingerprint: APK中未找到META-INF/CERT.RSA");
return null;
}
// 1. 读取CERT.RSA原始DER编码字节
byte[] rsaDerBytes = readStreamToBytes(jarFile.getInputStream(sigEntry));
if (rsaDerBytes == null || rsaDerBytes.length == 0) {
LogUtils.w(TAG, "getAPKSignFingerprint: 读取CERT.RSA字节为空");
return null;
}
// 2. 解析DER编码提取Android系统标准Signatrue字节与客户端完全一致
byte[] androidSigBytes = parseDerForAndroidSignature(rsaDerBytes);
if (androidSigBytes == null || androidSigBytes.length == 0) {
LogUtils.w(TAG, "getAPKSignFingerprint: 解析Android标准Signature字节失败");
return null;
}
// 3. 与客户端完全相同的流程SHA1摘要 → Android原生Base64.NO_WRAP核心修复
MessageDigest md = MessageDigest.getInstance("SHA1");
md.update(androidSigBytes);
byte[] sha1Digest = md.digest();
String signBase64 = Base64.encodeToString(sha1Digest, Base64.NO_WRAP);
LogUtils.d(TAG, "getAPKSignFingerprint: APK解析出的签名(Base64)" + signBase64);
return signBase64;
} catch (NoSuchAlgorithmException e) {
LogUtils.e(TAG, "getAPKSignFingerprint: 解析签名失败SHA1算法不存在", e);
return null;
} catch (Exception e) {
LogUtils.e(TAG, "getAPKSignFingerprint: 解析APK签名异常", e);
return null;
} finally {
if (jarFile != null) {
try {
jarFile.close();
} catch (IOException e) {
LogUtils.e(TAG, "getAPKSignFingerprint: 关闭JarFile流失败", e);
}
}
}
}
/**
* 关键解析复刻Android系统android.content.pm.Signature的DER编码解析逻辑
* 从CERT.RSA的DER字节中提取与signatures[0].toByteArray()完全一致的签名字节
*/
private byte[] parseDerForAndroidSignature(byte[] derBytes) {
try {
int offset = 0;
// 跳过顶层SEQUENCE标签0x30
if (derBytes == null || derBytes.length < 2 || derBytes[offset++] != 0x30) {
LogUtils.w(TAG, "parseDerForAndroidSignature: DER编码非标准SEQUENCE格式");
return null;
}
// 跳过顶层长度字段(处理短长度/长长度)
if (derBytes[offset] > 0x80) {
int lenLen = derBytes[offset++] & 0x7F;
offset += lenLen;
} else {
offset++;
}
// 跳过证书主体字段直到找到签名块的SEQUENCE标签提取后续所有字节
while (offset < derBytes.length) {
if (derBytes[offset] == 0x30) {
// 提取签名块完整字节与signatures[0].toByteArray()完全匹配)
byte[] sigBytes = new byte[derBytes.length - offset];
System.arraycopy(derBytes, offset, sigBytes, 0, sigBytes.length);
return sigBytes;
}
offset++;
}
LogUtils.w(TAG, "parseDerForAndroidSignature: DER编码中未找到签名块SEQUENCE");
return null;
} catch (Exception e) {
LogUtils.e(TAG, "parseDerForAndroidSignature: 解析DER编码为Android Signature字节失败", e);
return null;
}
}
/**
* 工具方法将输入流转为字节数组Java7适配无第三方依赖
* @param is 输入流
* @return 字节数组,失败返回空数组
* @throws IOException 流读取异常
*/
private byte[] readStreamToBytes(InputStream is) throws IOException {
if (is == null) return new byte[0];
ByteArrayOutputStream bos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
byte[] result = bos.toByteArray();
// 关闭流(倒序关闭)
bos.close();
is.close();
return result;
}
/**
* Java7适配URL编码工具UTF-8处理所有特殊字符/、=、&、+、空格等)
* @param content 待编码内容
* @return 编码后的字符串,失败返回原内容
*/
private static String urlEncode(String content) {
try {
// 用URLEncoder.encode指定UTF-8Java7必须显式指定避免平台默认编码问题
return URLEncoder.encode(content, "UTF-8");
} catch (Exception e) {
LogUtils.e(TAG, "urlEncode: 编码失败content=" + content, e);
return content; // 编码失败返回原内容,避免请求中断
}
}
/**
* 从PackageManager获取当前应用签名SHA1指纹BASE64编码快速获取
*/
private static String getAppSignFingerprint(Context context) {
try {
@@ -148,38 +328,16 @@ public class APPUtils {
MessageDigest md = MessageDigest.getInstance("SHA1");
md.update(signatures[0].toByteArray());
return Base64.encodeToString(md.digest(), Base64.NO_WRAP);
} catch (PackageManager.NameNotFoundException | NoSuchAlgorithmException e) {
LogUtils.e(TAG, "getAppSignFingerprint: 获取签名异常", e);
} catch (PackageManager.NameNotFoundException e) {
LogUtils.e(TAG, "getAppSignFingerprint: 获取包信息异常", e);
} catch (NoSuchAlgorithmException e) {
LogUtils.e(TAG, "getAppSignFingerprint: 获取SHA1算法异常", e);
} catch (Exception e) {
LogUtils.e(TAG, "getAppSignFingerprint: 未知异常", e);
}
return null;
}
/**
* 获取应用证书生效时间(毫秒时间戳)
*/
private static long getCertValidTime(Context context) {
try {
PackageManager pm = context.getPackageManager();
PackageInfo pkgInfo = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
Signature[] signatures = pkgInfo.signatures;
if (signatures == null || signatures.length == 0) {
LogUtils.w(TAG, "getCertValidTime: 未获取到应用签名");
return new Date().getTime(); // 默认当前时间
}
// 解析签名证书获取生效时间简化实现实际需解析X.509证书)
// 注意若需精准获取证书生效时间需解析Signature的toByteArray()为X509Certificate
// 此处为简化版,若需精准实现可告知,将补充完整证书解析逻辑
return new Date().getTime();
} catch (PackageManager.NameNotFoundException e) {
LogUtils.e(TAG, "getCertValidTime: 获取包信息异常", e);
} catch (Exception e) {
LogUtils.e(TAG, "getCertValidTime: 未知异常", e);
}
return new Date().getTime();
}
// ==================== 校验结果回调接口 ====================
public interface CheckResultCallback {
/**

View File

@@ -51,21 +51,21 @@ public class SignGetUtils {
}
// 新增:直接返回签名字符串,供对话框调用
public static String getSignStr(Context context) {
if (context == null) return null;
try {
PackageManager pm = context.getPackageManager();
PackageInfo pkgInfo = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
Signature[] signatures = pkgInfo.signatures;
if (signatures == null || signatures.length == 0) return null;
MessageDigest md = MessageDigest.getInstance("SHA1");
md.update(signatures[0].toByteArray());
return Base64.encodeToString(md.digest(), Base64.NO_WRAP);
} catch (Exception e) {
LogUtils.e(TAG, "获取签名字符串失败", e);
return null;
}
}
// public static String getSignStr(Context context) {
// if (context == null) return null;
// try {
// PackageManager pm = context.getPackageManager();
// PackageInfo pkgInfo = pm.getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
// Signature[] signatures = pkgInfo.signatures;
// if (signatures == null || signatures.length == 0) return null;
//
// MessageDigest md = MessageDigest.getInstance("SHA1");
// md.update(signatures[0].toByteArray());
// return Base64.encodeToString(md.digest(), Base64.NO_WRAP);
// } catch (Exception e) {
// LogUtils.e(TAG, "获取签名字符串失败", e);
// return null;
// }
// }
}