本文介绍如何使用ApkVerifier获取APK的签名信息。

引入

1
implementation("com.android.tools.build:apksig:8.2.2")

使用

1
2
3
val verifier: ApkVerifier = ApkVerifier.Builder(inputFile).build()
val result = verifier.verify()
val isSuccess = result.isVerified

inputFile为输入Apk的文件路径,构造一个新的Builder来验证提供的 APK 文件,该验证器旨在密切模仿 Android 平台的行为。这是为了使验证器能够用于检查 APK 的签名是否需要在 Android 上进行验证。

使用verifier.verify()获取结果。如果结果的ApkVerifier.Result.isVerified()返回true ,则可以认为 APK 已验证。验证结果还包括错误、警告以及有关签名者的信息(例如签名证书)

通过ApkVerifier.Result.errors获取错误信息。

1
2
3
4
5
var error = ""
result.errors.filter { it.issue == ApkVerifier.Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY }
.forEach {
error += it.toString() + "\n"
}

后续使用ApkVerifier.Result.v(1/2/3)SchemeSigners获取签名方案,并获取信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
if (result.v1SchemeSigners.isNotEmpty()) {
for (signer in result.v1SchemeSigners) {
val cert = signer.certificate ?: continue
if (signer.certificate.type == "X.509") {
val subject = cert.subjectX500Principal.name
val validFrom = cert.notBefore.toString()
val validUntil = cert.notAfter.toString()
val publicKeyType = (cert.publicKey as? RSAPublicKey)?.algorithm ?: ""
val modulus = (cert.publicKey as? RSAPublicKey)?.modulus?.toString(10) ?: ""
val signatureType = cert.sigAlgName
val md5 = getThumbPrint(cert, "MD5") ?: ""
val sha1 = getThumbPrint(cert, "SHA-1") ?: ""
val sha256 = getThumbPrint(cert, "SHA-256") ?: ""
val apkVerifier = model.ApkVerifier(1, subject, validFrom, validUntil, publicKeyType, modulus, signatureType, md5, sha1, sha256)
list.add(apkVerifier)
}
signer.errors.filter { it.issue == ApkVerifier.Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY }
.forEach {
error += it.toString() + "\n"
}
}
}

V2和V3同理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
if (result.v2SchemeSigners.isNotEmpty()) {
for (signer in result.v2SchemeSigners) {
val cert = signer.certificate ?: continue
if (signer.certificate.type == "X.509") {
val subject = cert.subjectX500Principal.name
val validFrom = cert.notBefore.toString()
val validUntil = cert.notAfter.toString()
val publicKeyType = (cert.publicKey as? RSAPublicKey)?.algorithm ?: ""
val modulus = (cert.publicKey as? RSAPublicKey)?.modulus?.toString(10) ?: ""
val signatureType = cert.sigAlgName
val md5 = getThumbPrint(cert, "MD5") ?: ""
val sha1 = getThumbPrint(cert, "SHA-1") ?: ""
val sha256 = getThumbPrint(cert, "SHA-256") ?: ""
val apkVerifier = model.ApkVerifier(2, subject, validFrom, validUntil, publicKeyType, modulus, signatureType, md5, sha1, sha256)
list.add(apkVerifier)
}
signer.errors.filter { it.issue == ApkVerifier.Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY }
.forEach {
error += it.toString() + "\n"
}
}
}

if (result.v3SchemeSigners.isNotEmpty()) {
for (signer in result.v3SchemeSigners) {
val cert = signer.certificate ?: continue
if (signer.certificate.type == "X.509") {
val subject = cert.subjectX500Principal.name
val validFrom = cert.notBefore.toString()
val validUntil = cert.notAfter.toString()
val publicKeyType = (cert.publicKey as? RSAPublicKey)?.algorithm ?: ""
val modulus = (cert.publicKey as? RSAPublicKey)?.modulus?.toString(10) ?: ""
val signatureType = cert.sigAlgName
val md5 = getThumbPrint(cert, "MD5") ?: ""
val sha1 = getThumbPrint(cert, "SHA-1") ?: ""
val sha256 = getThumbPrint(cert, "SHA-256") ?: ""
val apkVerifier = model.ApkVerifier(3, subject, validFrom, validUntil, publicKeyType, modulus, signatureType, md5, sha1, sha256)
list.add(apkVerifier)
}
signer.errors.filter { it.issue == ApkVerifier.Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY }
.forEach {
error += it.toString() + "\n"
}
}
}

这样就可以获取到Apk的签名信息,界面如图

完整代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
/**
* 签名验证
*/
fun apkVerifier(input: String) {
launch(Dispatchers.IO) {
updateApkVerifierState(UIState.Loading)
val list = ArrayList<model.ApkVerifier>()
val inputFile = File(input)
val path = inputFile.path
val name = inputFile.name
val verifier: ApkVerifier = ApkVerifier.Builder(inputFile).build()
try {
val result = verifier.verify()
var error = ""
val isSuccess = result.isVerified

result.errors.filter { it.issue == ApkVerifier.Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY }
.forEach {
error += it.toString() + "\n"
}

if (result.v1SchemeSigners.isNotEmpty()) {
for (signer in result.v1SchemeSigners) {
val cert = signer.certificate ?: continue
if (signer.certificate.type == "X.509") {
val subject = cert.subjectX500Principal.name
val validFrom = cert.notBefore.toString()
val validUntil = cert.notAfter.toString()
val publicKeyType = (cert.publicKey as? RSAPublicKey)?.algorithm ?: ""
val modulus = (cert.publicKey as? RSAPublicKey)?.modulus?.toString(10) ?: ""
val signatureType = cert.sigAlgName
val md5 = getThumbPrint(cert, "MD5") ?: ""
val sha1 = getThumbPrint(cert, "SHA-1") ?: ""
val sha256 = getThumbPrint(cert, "SHA-256") ?: ""
val apkVerifier = model.ApkVerifier(1, subject, validFrom, validUntil, publicKeyType, modulus, signatureType, md5, sha1, sha256)
list.add(apkVerifier)
}
signer.errors.filter { it.issue == ApkVerifier.Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY }
.forEach {
error += it.toString() + "\n"
}
}
}

if (result.v2SchemeSigners.isNotEmpty()) {
for (signer in result.v2SchemeSigners) {
val cert = signer.certificate ?: continue
if (signer.certificate.type == "X.509") {
val subject = cert.subjectX500Principal.name
val validFrom = cert.notBefore.toString()
val validUntil = cert.notAfter.toString()
val publicKeyType = (cert.publicKey as? RSAPublicKey)?.algorithm ?: ""
val modulus = (cert.publicKey as? RSAPublicKey)?.modulus?.toString(10) ?: ""
val signatureType = cert.sigAlgName
val md5 = getThumbPrint(cert, "MD5") ?: ""
val sha1 = getThumbPrint(cert, "SHA-1") ?: ""
val sha256 = getThumbPrint(cert, "SHA-256") ?: ""
val apkVerifier = model.ApkVerifier(2, subject, validFrom, validUntil, publicKeyType, modulus, signatureType, md5, sha1, sha256)
list.add(apkVerifier)
}
signer.errors.filter { it.issue == ApkVerifier.Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY }
.forEach {
error += it.toString() + "\n"
}
}
}

if (result.v3SchemeSigners.isNotEmpty()) {
for (signer in result.v3SchemeSigners) {
val cert = signer.certificate ?: continue
if (signer.certificate.type == "X.509") {
val subject = cert.subjectX500Principal.name
val validFrom = cert.notBefore.toString()
val validUntil = cert.notAfter.toString()
val publicKeyType = (cert.publicKey as? RSAPublicKey)?.algorithm ?: ""
val modulus = (cert.publicKey as? RSAPublicKey)?.modulus?.toString(10) ?: ""
val signatureType = cert.sigAlgName
val md5 = getThumbPrint(cert, "MD5") ?: ""
val sha1 = getThumbPrint(cert, "SHA-1") ?: ""
val sha256 = getThumbPrint(cert, "SHA-256") ?: ""
val apkVerifier = model.ApkVerifier(3, subject, validFrom, validUntil, publicKeyType, modulus, signatureType, md5, sha1, sha256)
list.add(apkVerifier)
}
signer.errors.filter { it.issue == ApkVerifier.Issue.JAR_SIG_UNPROTECTED_ZIP_ENTRY }
.forEach {
error += it.toString() + "\n"
}
}
}

if (isSuccess || list.isNotEmpty()) {
val apkVerifierResult = ApkVerifierResult(isSuccess, path, name, list)
updateApkVerifierState(UIState.Success(apkVerifierResult))
} else {
if (error.isBlank()) {
error = "APK签名验证失败"
}
updateApkVerifierState(UIState.Error(error))
}
} catch (e: Exception) {
updateApkVerifierState(UIState.Error(e.message ?: "APK签名验证失败"))
e.printStackTrace()
}
if (apkVerifierState is UIState.Error) {
delay(1000)
updateApkVerifierState(UIState.WAIT)
}
}
}

private fun getThumbPrint(cert: X509Certificate?, type: String?): String? {
val md = MessageDigest.getInstance(type) // lgtm [java/weak-cryptographic-algorithm]
val der: ByteArray = cert?.encoded ?: return null
md.update(der)
val digest = md.digest()
return hexify(digest)
}

private fun hexify(bytes: ByteArray): String {
val hexDigits = charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
val buf = StringBuilder(bytes.size * 3)
for (aByte in bytes) {
buf.append(hexDigits[aByte.toInt() and 0xf0 shr 4])
buf.append(hexDigits[aByte.toInt() and 0x0f])
if (bytes.indexOf(aByte) != bytes.size - 1) {
buf.append(':')
}
}
return buf.toString()
}

参考:Jadx