From 26a64b44e9e61e524d0fb3ee051ed07c66806091 Mon Sep 17 00:00:00 2001 From: ZhanGSKen Date: Sat, 24 Jan 2026 19:49:51 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=AF=B9=E7=AD=BE=E5=90=8D?= =?UTF-8?q?=E8=AF=81=E4=B9=A6=E4=BF=AE=E6=94=B9=E5=90=8E=E7=9A=84=E8=AF=81?= =?UTF-8?q?=E4=B9=A6=E8=AF=86=E5=88=AB=E8=83=BD=E5=8A=9B=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/cc/winboll/app/APKFileUtils.java | 60 ++++++++++++++++++---------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/src/cc/winboll/app/APKFileUtils.java b/src/cc/winboll/app/APKFileUtils.java index 65af21e..45add2b 100644 --- a/src/cc/winboll/app/APKFileUtils.java +++ b/src/cc/winboll/app/APKFileUtils.java @@ -11,15 +11,17 @@ import java.io.InputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; +import java.util.Enumeration; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** - * APK文件工具类(单例)- 生产级签名+哈希双校验版(修复Too short异常) - * 1. 稳定解析CERT.RSA原始字节,与客户端Signature.toByteArray()1:1对齐,解决X509解析异常 + * APK文件工具类(单例)- 生产级签名+哈希双校验版(修复Too short异常,兼容MT重签名) + * 1. 稳定解析META-INF下所有RSA原始字节,与客户端Signature.toByteArray()1:1对齐,解决X509解析异常 * 2. 支持SHA256文件哈希字节级唯一校验,签名+哈希双重验证 * 3. 入参包含:项目名/版本名/APK名/客户端签名/客户端哈希,适配生产级版本管理 * 4. APK路径规范:apks_root/项目名/debug/tag/APK文件(支持调试/正式环境) + * 5. 兼容MT重签名:遍历META-INF所有.RSA/.rsa文件,适配自定义签名文件命名 * @Author ZhanGSKen */ public class APKFileUtils { @@ -31,9 +33,10 @@ public class APKFileUtils { // 算法常量(与客户端严格对齐) private static final String SIGN_ALGORITHM = "SHA1"; // 签名摘要算法 private static final String HASH_ALGORITHM = "SHA-256"; // 文件哈希算法 - // 签名文件(兼容大小写,适配所有打包工具) - private static final String CERT_RSA_UPPER = "META-INF/CERT.RSA"; - private static final String CERT_RSA_LOWER = "META-INF/cert.rsa"; + // 签名文件目录与后缀(兼容MT重签名,遍历所有RSA文件) + private static final String META_INF_DIR = "META-INF/"; + private static final String RSA_SUFFIX_UPPER = ".RSA"; + private static final String RSA_SUFFIX_LOWER = ".rsa"; // APK根目录 private String apksRootPath; @@ -91,6 +94,7 @@ public class APKFileUtils { * 对外暴露核心校验方法:签名 + SHA256文件哈希 双校验 * 入参包含:项目名/版本名/APK文件名/客户端签名Base64/客户端文件哈希 * APK路径规范:apksRootPath/项目名/版本名/APK文件 + * @param isDebug 调试环境标识 * @param projectName 项目名(非空) * @param versionName 版本名(非空,如15.11.11) * @param apkFileName APK文件名(非空,需以.apk结尾) @@ -164,7 +168,7 @@ public class APKFileUtils { } LogUtils.i("APKFileUtils", "【哈希对比结果】✅ 匹配(字节级文件完全一致)"); - // ===== 第二步:签名校验(直接读取CERT.RSA原始字节,与客户端严格对齐)===== + // ===== 第二步:签名校验(遍历META-INF所有RSA文件,与客户端严格对齐)===== String serverSignBase64 = getAPKSign(apkFile); if (isParamEmpty(serverSignBase64)) { LogUtils.w("APKFileUtils", "解析服务端APK签名失败:" + apkFileName); @@ -189,8 +193,8 @@ public class APKFileUtils { } /** - * 稳定解析APK签名:直接读取CERT.RSA原始字节,SHA1+Base64(与客户端1:1对齐) - * 解决X509证书解析的Too short异常,兼容所有APK(普通/加固/自定义打包) + * 稳定解析APK签名:遍历META-INF所有.RSA/.rsa文件,读取第一个有效文件原始字节,SHA1+Base64(与客户端1:1对齐) + * 兼容MT重签名(自定义签名文件命名),解决X509证书解析的Too short异常,适配所有APK(普通/加固/重签名) * @param apkFile APK文件 * @return 签名Base64字符串,失败返回null */ @@ -199,23 +203,37 @@ public class APKFileUtils { InputStream certIs = null; try { jarFile = new JarFile(apkFile); - // 先找大写CERT.RSA,找不到再找小写,兼容所有打包工具 - JarEntry certEntry = jarFile.getJarEntry(CERT_RSA_UPPER); - if (certEntry == null) { - certEntry = jarFile.getJarEntry(CERT_RSA_LOWER); - if (certEntry == null) { - LogUtils.w("APKFileUtils", "APK中未找到签名文件:META-INF/CERT.RSA/cert.rsa"); - return null; + Enumeration entries = jarFile.entries(); + JarEntry targetRsaEntry = null; + + // 核心改造:遍历META-INF下所有条目,找到第一个有效.RSA/.rsa文件(兼容MT重签名) + while (entries.hasMoreElements()) { + JarEntry entry = entries.nextElement(); + String entryName = entry.getName(); + // 过滤条件:META-INF目录下 + 非目录 + 以.RSA/.rsa结尾 + if (entryName.startsWith(META_INF_DIR) && !entry.isDirectory() + && (entryName.endsWith(RSA_SUFFIX_UPPER) || entryName.endsWith(RSA_SUFFIX_LOWER))) { + targetRsaEntry = entry; + LogUtils.d("APKFileUtils", "找到有效签名文件,路径:" + entryName); + break; // 取第一个有效RSA文件即可,重签名APK仅会有一个签名文件 } } - // 核心:直接读取CERT.RSA的原始字节流(不做证书解析,适配PKCS7签名块) - certIs = jarFile.getInputStream(certEntry); - byte[] sigRawBytes = readStreamToBytes(certIs); - if (sigRawBytes == null || sigRawBytes.length == 0) { - LogUtils.w("APKFileUtils", "读取CERT.RSA原始字节为空"); + + // 未找到任何RSA签名文件 + if (targetRsaEntry == null) { + LogUtils.w("APKFileUtils", "APK中META-INF目录下未找到任何.RSA/.rsa签名文件"); return null; } - // 与客户端完全一致的处理流程:SHA1摘要 → Base64编码(去换行) + + // 读取签名文件原始字节流(不做证书解析,适配PKCS7签名块,与客户端保持一致) + certIs = jarFile.getInputStream(targetRsaEntry); + byte[] sigRawBytes = readStreamToBytes(certIs); + if (sigRawBytes == null || sigRawBytes.length == 0) { + LogUtils.w("APKFileUtils", "读取签名文件原始字节为空:" + targetRsaEntry.getName()); + return null; + } + + // 与客户端完全一致的处理流程:SHA1摘要 → Base64编码(去换行,保证格式一致) MessageDigest md = MessageDigest.getInstance(SIGN_ALGORITHM); byte[] signDigest = md.digest(sigRawBytes); String signBase64 = Base64.getEncoder().encodeToString(signDigest)