You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android设备所有者应用获取下载APK签名证书遇null问题求助

问题分析与解决方案

原因分析

1. keytool报错的原因

APK存在两种主流签名方案:

  • v1(JAR签名):传统基于JAR结构的签名方式,keytool可识别
  • v2/v3/v4(APK Signature Scheme):Android 7.0+引入的新签名方案,直接对APK文件的特定块签名,不依赖JAR结构。apksigner支持所有签名方案,但keytool仅能识别v1签名,因此会返回“Not a signed jar file”错误——这是正常现象,不代表APK签名无效。

2. signingInfo返回null的可能原因

  • URI路径无效:如果uri是FileProvider等Content Provider返回的内容URI,uri.path并非真实本地文件路径,getPackageArchiveInfo无法识别该路径导致解析失败。
  • 文件读取权限不足:即便拥有设备所有者权限,若APK存储在外部存储且未获取READ_EXTERNAL_STORAGE权限,或未通过ContentResolver正确读取文件,也会导致无法解析APK信息。
  • API版本不兼容:GET_SIGNING_CERTIFICATES是Android 9(API 28)才引入的常量,若运行在低于API 28的设备上,该参数无效,需改用旧版的GET_SIGNATURES。

正确获取APK签名证书的方法

方法1:修复路径与权限,使用PackageManager(API 28+)

先将Content URI转换为真实文件路径,确保应用能读取到APK文件:

// 转换Content URI为真实文件路径
fun getRealPathFromUri(context: Context, uri: Uri): String? {
    return when (uri.scheme) {
        "content" -> {
            val cursor = context.contentResolver.query(uri, arrayOf(MediaStore.Files.FileColumns.DATA), null, null, null)
            cursor?.use {
                if (it.moveToFirst()) {
                    return it.getString(it.getColumnIndexOrThrow(MediaStore.Files.FileColumns.DATA))
                }
            }
            null
        }
        "file" -> uri.path
        else -> null
    }
}

// 获取APK签名信息
fun getApkSigningInfo(context: Context, apkUri: Uri): SigningInfo? {
    val apkPath = getRealPathFromUri(context, apkUri) ?: return null
    val packageInfo = context.packageManager.getPackageArchiveInfo(
        apkPath,
        PackageManager.GET_SIGNING_CERTIFICATES
    )
    return packageInfo?.signingInfo
}

调用后可提取签名证书:

val signingInfo = getApkSigningInfo(context, apkUri)
signingInfo?.let {
    // 根据签名情况获取证书数组
    val certificates = if (it.hasMultipleSigners()) {
        it.apkContentsSigners
    } else {
        it.signingCertificateHistory
    }
    // 遍历处理证书
    certificates.forEach { cert ->
        val certHash = cert.toByteArray().joinToString("") { "%02x".format(it) }
        println("证书哈希值:$certHash")
    }
}

方法2:兼容低版本API(API 19+)

若需要支持API 28以下的设备,可使用已过时但仍可用的GET_SIGNATURES:

fun getApkSignatures(context: Context, apkPath: String): Array<Signature>? {
    val packageInfo = context.packageManager.getPackageArchiveInfo(
        apkPath,
        PackageManager.GET_SIGNATURES
    )
    return packageInfo?.signatures
}

方法3:直接解析APK签名块(最可靠,不受API版本限制)

若上述方法仍有问题,可直接解析APK的签名相关条目(仅支持v1签名;若需支持v2/v3,可引入Android官方apksig库):

import java.io.File
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import java.util.zip.ZipFile

fun getApkCertificates(apkFile: File): List<X509Certificate>? {
    return try {
        ZipFile(apkFile).use { zipFile ->
            val certFactory = CertificateFactory.getInstance("X.509")
            val entries = zipFile.entries()
            val certificates = mutableListOf<X509Certificate>()
            while (entries.hasMoreElements()) {
                val entry = entries.nextElement()
                // 筛选META-INF下的RSA签名文件
                if (entry.name.startsWith("META-INF/") && entry.name.endsWith(".RSA")) {
                    zipFile.getInputStream(entry).use { inputStream ->
                        val cert = certFactory.generateCertificate(inputStream) as X509Certificate
                        certificates.add(cert)
                    }
                }
            }
            if (certificates.isEmpty()) null else certificates
        }
    } catch (e: Exception) {
        e.printStackTrace()
        null
    }
}

内容的提问来源于stack exchange,提问作者Kugesh Rajasekaran

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 15:22:41