做应用自升级有一个对下载到本地的更新Apk文件做签名校验的点, 在此记录一下.
- 首先是获取已安装应用自身签名的方法
public static Signature getSignature(Context ctx) {
try {
PackageInfo packageInfo = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(),
PackageManager.GET_SIGNATURES);
Signature[] signs = packageInfo.signatures;
return signs[0];
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
取得的Signature可以直接调用toCharsString()方法获得用来校验对比的值, 也可以对Signature做SHA-1处理得值, 建议后者.
- 接下来是获取本地未安装Apk文件签名的方法, 该方法适配了不同Android版本, 解决了Android P上取不到签名的问题.
public static Signature getApkSignature(String apkPath) {
String PATH_PackageParser = "android.content.pm.PackageParser";
try {
// apk文件的文件路径
// 这是一个Package解释器, 是隐藏的
// 构造函数的参数只有一个, apk文件的路径
// PackageParser packageParser = new PackageParser(apkPath);
Class pkgParserCls = Class.forName(PATH_PackageParser);
Class[] typeArgs = new Class[1];
typeArgs[0] = String.class;
// 这个是与显示有关的, 里面涉及到一些像素显示等等, 我们使用默认的情况
DisplayMetrics metrics = new DisplayMetrics();
metrics.setToDefaults();
Constructor pkgParserCt;
Object pkgParser;
if (Build.VERSION.SDK_INT > 20) {
pkgParserCt = pkgParserCls.getConstructor();
pkgParser = pkgParserCt.newInstance();
Method pkgParser_parsePackageMtd = pkgParserCls
.getDeclaredMethod("parsePackage", File.class, int.class);
Object pkgParserPkg = pkgParser_parsePackageMtd.invoke(pkgParser,
new File(apkPath), PackageManager.GET_SIGNATURES);
Method pkgParser_collectCertificatesMtd = pkgParserCls
.getDeclaredMethod("collectCertificates",
pkgParserPkg.getClass(), Integer.TYPE);
if (Build.VERSION.SDK_INT >= 28) {
pkgParser_collectCertificatesMtd.invoke(pkgParser, pkgParserPkg,
Build.VERSION.SDK_INT > 28);
Field mSigningDetailsField = pkgParserPkg.getClass()
.getDeclaredField("mSigningDetails"); // SigningDetails
mSigningDetailsField.setAccessible(true);
Object mSigningDetails = mSigningDetailsField.get(pkgParserPkg);
Field infoField = mSigningDetails.getClass().getDeclaredField("signatures");
infoField.setAccessible(true);
Signature[] info = (Signature[]) infoField.get(mSigningDetails);
return info[0];
} else {
pkgParser_collectCertificatesMtd.invoke(pkgParser, pkgParserPkg,
PackageManager.GET_SIGNATURES);
Field packageInfoFld = pkgParserPkg.getClass().getDeclaredField("mSignatures");
Signature[] info = (Signature[]) packageInfoFld.get(pkgParserPkg);
return info[0];
}
} else {
pkgParserCt = pkgParserCls.getConstructor(typeArgs);
pkgParser = pkgParserCt.newInstance(apkPath);
Method pkgParser_parsePackageMtd = pkgParserCls.getDeclaredMethod("parsePackage",
File.class, String.class, DisplayMetrics.class, Integer.TYPE);
Object pkgParserPkg = pkgParser_parsePackageMtd.invoke(pkgParser, new File(apkPath),
apkPath, metrics, PackageManager.GET_SIGNATURES);
Method pkgParser_collectCertificatesMtd = pkgParserCls
.getDeclaredMethod("collectCertificates", pkgParserPkg.getClass(), Integer.TYPE);
pkgParser_collectCertificatesMtd.invoke(pkgParser, pkgParserPkg, PackageManager.GET_SIGNATURES);
// 应用程序信息包, 这个公开的, 不过有些函数, 变量没公开
Field packageInfoFld = pkgParserPkg.getClass().getDeclaredField("mSignatures");
Signature[] info = (Signature[]) packageInfoFld.get(pkgParserPkg);
return info[0];
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
- 在此附上对Signature做SHA-1处理的方法
public static String signatureToSha1(Signature signature) {
if (signature == null) {
return null;
}
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(signature.toByteArray());
byte[] digest = md.digest();
return toHexString(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
public static String toHexString(byte[] bytes) {
if (bytes == null || bytes.length == 0) {
return "";
}
final StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b & 0xff));
}
return sb.toString();
}
最后, 通过取得自身和Apk文件的签名对比校验即可.