apk签名原理

APK的签名信息位于META-INF目录下的MANIFEST.MF、CERT.SF和CERT.RSA文件。MANIFEST.MF记录所有文件的SHA1摘要,CERT.SF包含MANIFEST.MF的SHA1摘要及每个文件的详细摘要,CERT.RSA存储证书公钥和私钥签名。篡改APK文件会导致签名验证失败,确保应用安全。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

apk的签名信息在apk解压后的META-INF目录下。这三个文件分别是MANIFEST.MF,CERT.SF,CERT.RSA。下面会说明这三个文件的作用以及生成方式。

MANIFEST.MF

MANIFEST.MF会将整个apk中所有的文件进行SHA1-BASE64编码,生成的摘要信息就是SHA1-Digest。下面的就是MANIFEST.MF中记录AndroidManifest.xml的摘要信息。

	Name: AndroidManifest.xml
	SHA1-Digest: 7AWE28hOihCw2ecRxVkh0LRpQw0=

关于计算文件SHA1可以使用linux的sha1sum工具,直接输出文件sha1。值得注意的是,比如生成的SHA1是以字符串形式输出在屏幕上的,在计算base64时,不能直接将字符串计算base64,而应该将SHA1两位一组,将其十六进制对应的UTF8的字符进行base64!

比如说,AndroidManifest.xml的SHA1为ec0584dbc84e8a10b0d9e711c55921d0b469430d。我们需要将ec0584dbc84e8a10b0d9e711c55921d0b469430d按照(ec)(05)(84)(db)…(0d)组合起来,每一组看做一个十六进制数,然后将这个十六进制数转成utf8对应的字符。最后将这个字符串计算base64输出,输出的结果就是SHA1-Digest的值。

在这里用python写了一个小的测试工具,仅供参考:

	#!/usr/bin/python
	# -*- coding: utf-8 -*-

	import base64


	def test(hexstr):
	    hexarray = []
	    for i in range(0, len(hexstr), 2):
	        hex = hexstr[i] + hexstr[i + 1]
	        hexarray.append(chr(int(hex, 16)))
	    return reduce(lambda a, b: a + b, hexarray)


	if __name__ == "__main__":
	    a = test("ec0584dbc84e8a10b0d9e711c55921d0b469430d")  # 替换成sha1即可,输出为base64后的结果
	    print base64.b64encode(a)

CERT.SF

CERT.SF有两部组成,一部分是头部信息,SHA1-Digest-Manifest记录了刚才MANIFEST.MF的SHA1-BASE64的摘要;另一部分和刚刚MANIFEST.MF结构完全相同,只是SHA1-Digest不一样。其实CERT.SF中的SHA1-Digest是MANIFEST.MF对应项目的SHA1-BASE64后结果。

Signature-Version: 1.0
X-Android-APK-Signed: 2
SHA1-Digest-Manifest: uJqQ/1VgrhamBP3SWFDL1kvRaWU=
Created-By: 1.0 (Android SignApk)

...


Name: AndroidManifest.xml
SHA1-Digest: fua03tTT6MzoR5ljlADI+FymSUI=

...

主要说明下CERT.SF是如何计算出来的,因为其他文章说明的不是很清晰。

还是以AndroidManifest.xml这一项为例。其实计算CERT.SF某一项目的SHA1-Digest,是将MANIFEST.MF对应项目的整体并拼接\r\n进行计算的。需要注意的是,在ubuntu中,换行只是\n,但是在计算SHA1-Digest时,必须要将\n换成\r\n。同时,在全部换行结束后,要再在末尾添加一个\r\n。所以对于上面的AndroidManifest.xml而言,想要计算CERT.SF中对应的SHA1-Digest,就是要计算如下字符串的SHA1-BASE64。计算方式如前所述。

Name: AndroidManifest.xml\r\nSHA1-Digest: 7AWE28hOihCw2ecRxVkh0LRpQw0=\r\n\r\n

Android中对应的源码如下:

    for (Map.Entry<String, Attributes> entry : entries.entrySet()) {
        // Digest of the manifest stanza for this entry.
        print.print("Name: " + entry.getKey() + "\r\n");
        for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {
            print.print(att.getKey() + ": " + att.getValue() + "\r\n");
        }
        print.print("\r\n");
        print.flush();

        Attributes sfAttr = new Attributes();
        sfAttr.putValue("SHA1-Digest", base64.encode(md.digest()));
        sf.getEntries().put(entry.getKey(), sfAttr);
    }

注意的是,: 后面有个空格。从上面代码可见,每一个key-value后面拼接一个\r\n,同时在所有key-value输出完毕有,又会拼接一个\r\n。拼接后的字符串再计算SHA1-BASE64就是CERT.SF中的SHA1-Digest。

CERT.RSA

CERT.RSA主要保存的是证书的公钥信息和私钥对CERT.SF进行SHA1withRSA加密的签名。正常情况下,系统可以通过公钥信息对签名进行解密,得出的结果应该是CERT.SF的SHA1计算结果。

可能出现的篡改情况

  1. 对apk的某个文件进行修改

    1.1 如果不重新计算MANIFEST.MF以及CERT.SF,就会导致两个文件中的对应项目的SHA1-Digest与修改后的文件不一致,从而验证失败

    1.2 如果重新计算MANIFEST.MF以及CERT.SF,CERT.SF的SHA1就会发生变化,导致利用公钥验签失败

  2. 对apk的添加或者删除某个文件

    结果同上

  3. 添加/删除/修改某个文件后,再对CERT.SF重新签名,并生成新的CERT.RSA

    由于签名的私钥不会携带在apk中,理论上非app的所有者是不能搞到私钥的,所以想用原来的私钥对apk签名可以认为理论上是行不通的。在这种条件下,可以认为这个app已经可以被认定为不属于原来应用发布者的app。如果这个被篡改后的app被装到手机上,即使包名相同,将有两种情况会发生:

    3.1 原来的手机上已经装了没有被篡改后的app,由于证书和原来的app不同,系统不会安装这个app。
    3.2 原来的手机上没有装被篡改后的app,这时会安装成功。但假设有一天这个被篡改的app东窗事发,偷跑了这个用户几个亿的比特币之类的,也跟原来的应用开发者没有关系,因为从这个被篡改的apk中得出,这个apk不是他们发布的apk,自然也不是开发者偷跑的几个亿。所以,不要随便安装未知来源的app!

所以,android的验签机制是自签名机制。这套机制保证的并不是app一定不会被篡改,而是可以证明被篡改后app不再是原来开发者的app。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值