添加对签名证书修改后的证书识别能力。
This commit is contained in:
@@ -11,15 +11,17 @@ import java.io.InputStream;
|
|||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
import java.util.Enumeration;
|
||||||
import java.util.jar.JarEntry;
|
import java.util.jar.JarEntry;
|
||||||
import java.util.jar.JarFile;
|
import java.util.jar.JarFile;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* APK文件工具类(单例)- 生产级签名+哈希双校验版(修复Too short异常)
|
* APK文件工具类(单例)- 生产级签名+哈希双校验版(修复Too short异常,兼容MT重签名)
|
||||||
* 1. 稳定解析CERT.RSA原始字节,与客户端Signature.toByteArray()1:1对齐,解决X509解析异常
|
* 1. 稳定解析META-INF下所有RSA原始字节,与客户端Signature.toByteArray()1:1对齐,解决X509解析异常
|
||||||
* 2. 支持SHA256文件哈希字节级唯一校验,签名+哈希双重验证
|
* 2. 支持SHA256文件哈希字节级唯一校验,签名+哈希双重验证
|
||||||
* 3. 入参包含:项目名/版本名/APK名/客户端签名/客户端哈希,适配生产级版本管理
|
* 3. 入参包含:项目名/版本名/APK名/客户端签名/客户端哈希,适配生产级版本管理
|
||||||
* 4. APK路径规范:apks_root/项目名/debug/tag/APK文件(支持调试/正式环境)
|
* 4. APK路径规范:apks_root/项目名/debug/tag/APK文件(支持调试/正式环境)
|
||||||
|
* 5. 兼容MT重签名:遍历META-INF所有.RSA/.rsa文件,适配自定义签名文件命名
|
||||||
* @Author ZhanGSKen<zhangsken@qq.com>
|
* @Author ZhanGSKen<zhangsken@qq.com>
|
||||||
*/
|
*/
|
||||||
public class APKFileUtils {
|
public class APKFileUtils {
|
||||||
@@ -31,9 +33,10 @@ public class APKFileUtils {
|
|||||||
// 算法常量(与客户端严格对齐)
|
// 算法常量(与客户端严格对齐)
|
||||||
private static final String SIGN_ALGORITHM = "SHA1"; // 签名摘要算法
|
private static final String SIGN_ALGORITHM = "SHA1"; // 签名摘要算法
|
||||||
private static final String HASH_ALGORITHM = "SHA-256"; // 文件哈希算法
|
private static final String HASH_ALGORITHM = "SHA-256"; // 文件哈希算法
|
||||||
// 签名文件(兼容大小写,适配所有打包工具)
|
// 签名文件目录与后缀(兼容MT重签名,遍历所有RSA文件)
|
||||||
private static final String CERT_RSA_UPPER = "META-INF/CERT.RSA";
|
private static final String META_INF_DIR = "META-INF/";
|
||||||
private static final String CERT_RSA_LOWER = "META-INF/cert.rsa";
|
private static final String RSA_SUFFIX_UPPER = ".RSA";
|
||||||
|
private static final String RSA_SUFFIX_LOWER = ".rsa";
|
||||||
// APK根目录
|
// APK根目录
|
||||||
private String apksRootPath;
|
private String apksRootPath;
|
||||||
|
|
||||||
@@ -91,6 +94,7 @@ public class APKFileUtils {
|
|||||||
* 对外暴露核心校验方法:签名 + SHA256文件哈希 双校验
|
* 对外暴露核心校验方法:签名 + SHA256文件哈希 双校验
|
||||||
* 入参包含:项目名/版本名/APK文件名/客户端签名Base64/客户端文件哈希
|
* 入参包含:项目名/版本名/APK文件名/客户端签名Base64/客户端文件哈希
|
||||||
* APK路径规范:apksRootPath/项目名/版本名/APK文件
|
* APK路径规范:apksRootPath/项目名/版本名/APK文件
|
||||||
|
* @param isDebug 调试环境标识
|
||||||
* @param projectName 项目名(非空)
|
* @param projectName 项目名(非空)
|
||||||
* @param versionName 版本名(非空,如15.11.11)
|
* @param versionName 版本名(非空,如15.11.11)
|
||||||
* @param apkFileName APK文件名(非空,需以.apk结尾)
|
* @param apkFileName APK文件名(非空,需以.apk结尾)
|
||||||
@@ -164,7 +168,7 @@ public class APKFileUtils {
|
|||||||
}
|
}
|
||||||
LogUtils.i("APKFileUtils", "【哈希对比结果】✅ 匹配(字节级文件完全一致)");
|
LogUtils.i("APKFileUtils", "【哈希对比结果】✅ 匹配(字节级文件完全一致)");
|
||||||
|
|
||||||
// ===== 第二步:签名校验(直接读取CERT.RSA原始字节,与客户端严格对齐)=====
|
// ===== 第二步:签名校验(遍历META-INF所有RSA文件,与客户端严格对齐)=====
|
||||||
String serverSignBase64 = getAPKSign(apkFile);
|
String serverSignBase64 = getAPKSign(apkFile);
|
||||||
if (isParamEmpty(serverSignBase64)) {
|
if (isParamEmpty(serverSignBase64)) {
|
||||||
LogUtils.w("APKFileUtils", "解析服务端APK签名失败:" + apkFileName);
|
LogUtils.w("APKFileUtils", "解析服务端APK签名失败:" + apkFileName);
|
||||||
@@ -189,8 +193,8 @@ public class APKFileUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 稳定解析APK签名:直接读取CERT.RSA原始字节,SHA1+Base64(与客户端1:1对齐)
|
* 稳定解析APK签名:遍历META-INF所有.RSA/.rsa文件,读取第一个有效文件原始字节,SHA1+Base64(与客户端1:1对齐)
|
||||||
* 解决X509证书解析的Too short异常,兼容所有APK(普通/加固/自定义打包)
|
* 兼容MT重签名(自定义签名文件命名),解决X509证书解析的Too short异常,适配所有APK(普通/加固/重签名)
|
||||||
* @param apkFile APK文件
|
* @param apkFile APK文件
|
||||||
* @return 签名Base64字符串,失败返回null
|
* @return 签名Base64字符串,失败返回null
|
||||||
*/
|
*/
|
||||||
@@ -199,23 +203,37 @@ public class APKFileUtils {
|
|||||||
InputStream certIs = null;
|
InputStream certIs = null;
|
||||||
try {
|
try {
|
||||||
jarFile = new JarFile(apkFile);
|
jarFile = new JarFile(apkFile);
|
||||||
// 先找大写CERT.RSA,找不到再找小写,兼容所有打包工具
|
Enumeration<JarEntry> entries = jarFile.entries();
|
||||||
JarEntry certEntry = jarFile.getJarEntry(CERT_RSA_UPPER);
|
JarEntry targetRsaEntry = null;
|
||||||
if (certEntry == null) {
|
|
||||||
certEntry = jarFile.getJarEntry(CERT_RSA_LOWER);
|
// 核心改造:遍历META-INF下所有条目,找到第一个有效.RSA/.rsa文件(兼容MT重签名)
|
||||||
if (certEntry == null) {
|
while (entries.hasMoreElements()) {
|
||||||
LogUtils.w("APKFileUtils", "APK中未找到签名文件:META-INF/CERT.RSA/cert.rsa");
|
JarEntry entry = entries.nextElement();
|
||||||
return null;
|
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);
|
// 未找到任何RSA签名文件
|
||||||
byte[] sigRawBytes = readStreamToBytes(certIs);
|
if (targetRsaEntry == null) {
|
||||||
if (sigRawBytes == null || sigRawBytes.length == 0) {
|
LogUtils.w("APKFileUtils", "APK中META-INF目录下未找到任何.RSA/.rsa签名文件");
|
||||||
LogUtils.w("APKFileUtils", "读取CERT.RSA原始字节为空");
|
|
||||||
return null;
|
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);
|
MessageDigest md = MessageDigest.getInstance(SIGN_ALGORITHM);
|
||||||
byte[] signDigest = md.digest(sigRawBytes);
|
byte[] signDigest = md.digest(sigRawBytes);
|
||||||
String signBase64 = Base64.getEncoder().encodeToString(signDigest)
|
String signBase64 = Base64.getEncoder().encodeToString(signDigest)
|
||||||
|
|||||||
Reference in New Issue
Block a user