apk签名机制

2024-06-19 12:08
文章标签 apk 签名 机制

本文主要是介绍apk签名机制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

https://www.2cto.com/kf/201506/412278.html

https://www.2cto.com/kf/201506/412278.html

https://www.2cto.com/kf/201506/412278.html





发布过Android应用的朋友们应该都知道,Android APK的发布是需要签名的。签名机制在Android应用和框架中有着十分重要的作用。

例如,Android系统禁止更新安装签名不一致的APK;如果应用需要使用system权限,必须保证APK签名与Framework签名一致,等等。在《APK Crack》一文中,我们了解到,要破解一个APK,必然需要重新对APK进行签名。而这个签名,一般情况无法再与APK原先的签名保持一致。(除非APK原作者的私钥泄漏,那已经是另一个层次的软件安全问题了。)

简单地说,签名机制标明了APK的发行机构。因此,站在软件安全的角度,我们就可以通过比对APK的签名情况,判断此APK是否由“官方”发行,而不是被破解篡改过重新签名打包的“盗版软件”。


Android签名机制

为了说明APK签名比对对软件安全的有效性,我们有必要了解一下Android APK的签名机制。为了更易于大家理解,我们从Auto-Sign工具的一条批处理命令说起。

《APK Crack》一文中,我们了解到,要签名一个没有签名过的APK,可以使用一个叫作Auto-sign的工具。Auto-sign工具实际运行的是一个叫做Sign.bat的批处理命令。用文本编辑器打开这个批处理文件,我们可以发现,实现签名功能的命令主要是这一行命令:

 

java -jar signapk.jar testkey.x509.pem testkey.pk8 update.apk update_signed.apk
这条命令的意义是:通过signapk.jar这个可执行jar包,以“testkey.x509.pem”这个公钥文件和“testkey.pk8”这个私钥文件对“update.apk”进行签名,签名后的文件保存为“update_signed.apk”。

对于此处所使用的私钥和公钥的生成方式,这里就不做进一步介绍了。这方面的资料大家可以找到很多。我们这里要讲的是signapk.jar到底做了什么。

signapk.jar是Android源码包中的一个签名工具。由于Android是个开源项目,所以,很高兴地,我们可以直接找到signapk.jar的源码!路径为/build/tools/signapk/SignApk.java。

对比一个没有签名的APK和一个签名好的APK,我们会发现,签名好的APK包中多了一个叫做META-INF的文件夹。里面有三个文件,分别名为MANIFEST.MF、CERT.SF和CERT.RSA。signapk.jar就是生成了这几个文件(其他文件没有任何改变。因此我们可以很容易去掉原有签名信息)。

通过阅读signapk源码,我们可以理清签名APK包的整个过程。


1、 生成MANIFEST.MF文件:

程序遍历update.apk包中的所有文件(entry),对非文件夹非签名文件的文件,逐个生成SHA1的数字签名信息,再用Base64进行编码。具体代码见这个方法:

 

private static Manifest addDigestsToManifest(JarFile jar)

关键代码如下:

1 for (JarEntry entry: byName.values()) {
2 String name = entry.getName();
3 if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) &&
4 !name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) &&
5 (stripPattern == null ||!stripPattern.matcher(name).matches())) {
6 InputStream data = jar.getInputStream(entry);
7 while ((num = data.read(buffer)) > 0) {
8 md.update(buffer, 0, num);
9 }
10 Attributes attr = null;
11 if (input != null) attr = input.getAttributes(name);
12 attr = attr != null ? new Attributes(attr) : new Attributes();
13 attr.putValue("SHA1-Digest", base64.encode(md.digest()));
14 output.getEntries().put(name, attr);
15 }
16 }


之后将生成的签名写入MANIFEST.MF文件。关键代码如下:

 

1 Manifest manifest = addDigestsToManifest(inputJar);
2 je = new JarEntry(JarFile.MANIFEST_NAME);
3 je.setTime(timestamp);
4 outputJar.putNextEntry(je);
5 manifest.write(outputJar);

这里简单介绍下SHA1数字签名。简单地说,它就是一种安全哈希算法,类似于MD5算法。它把任意长度的输入,通过散列算法变成固定长度的输出(这里我们称作“摘要信息”)。你不能仅通过这个摘要信息复原原来的信息。另外,它保证不同信息的摘要信息彼此不同。因此,如果你改变了apk包中的文件,那么在apk安装校验时,改变后的文件摘要信息与MANIFEST.MF的检验信息不同,于是程序就不能成功安装。


2、 生成CERT.SF文件:

对前一步生成的Manifest,使用SHA1-RSA算法,用私钥进行签名。关键代码如下:

1 Signature signature = Signature.getInstance("SHA1withRSA");
2 signature.initSign(privateKey);
3 je = new JarEntry(CERT_SF_NAME);
4 je.setTime(timestamp);
5 outputJar.putNextEntry(je);
6 writeSignatureFile(manifest,
7 new SignatureOutputStream(outputJar, signature));

RSA是一种非对称加密算法。用私钥通过RSA算法对摘要信息进行加密。在安装时只能使用公钥才能解密它。解密之后,将它与未加密的摘要信息进行对比,如果相符,则表明内容没有被异常修改。


3、 生成CERT.RSA文件:

生成MANIFEST.MF没有使用密钥信息,生成CERT.SF文件使用了私钥文件。那么我们可以很容易猜测到,CERT.RSA文件的生成肯定和公钥相关。

CERT.RSA文件中保存了公钥、所采用的加密算法等信息。核心代码如下:

 

1 je = new JarEntry(CERT_RSA_NAME);
2 je.setTime(timestamp);
3 outputJar.putNextEntry(je);
4 writeSignatureBlock(signature, publicKey, outputJar);

其中writeSignatureBlock的代码如下:

 

1 private static void writeSignatureBlock(
2 Signature signature, X509Certificate publicKey, OutputStream out)
3 throws IOException, GeneralSecurityException {
4 SignerInfo signerInfo = new SignerInfo(
5 new X500Name(publicKey.getIssuerX500Principal().getName()),
6 publicKey.getSerialNumber(),
7 AlgorithmId.get("SHA1"),
8 AlgorithmId.get("RSA"),
9 signature.sign());
10
11 PKCS7 pkcs7 = new PKCS7(
12 new AlgorithmId[] { AlgorithmId.get("SHA1") },
13 new ContentInfo(ContentInfo.DATA_OID, null),
14 new X509Certificate[] { publicKey },
15 new SignerInfo[] { signerInfo });
16
17 pkcs7.encodeSignedData(out);
18 }

好了,分析完APK包的签名流程,我们可以清楚地意识到:

1、 Android签名机制其实是对APK包完整性和发布机构唯一性的一种校验机制。

2、 Android签名机制不能阻止APK包被修改,但修改后的再签名无法与原先的签名保持一致。(拥有私钥的情况除外)。

3、 APK包加密的公钥就打包在APK包内,且不同的私钥对应不同的公钥。换句话言之,不同的私钥签名的APK公钥也必不相同。所以我们可以根据公钥的对比,来判断私钥是否一致。


APK签名比对的实现方式

好了,通过Android签名机制的分析,我们从理论上证明了通过APK公钥的比对能判断一个APK的发布机构。并且这个发布机构是很难伪装的,我们暂时可以认为是不可伪装的。

有了理论基础后,我们就可以开始实践了。那么如何获取到APK文件的公钥信息呢?因为Android系统安装程序肯定会获取APK信息进行比对,所以我们可以通过Android源码获得一些思路和帮助。

源码中有一个隐藏的类用于APK包的解析。这个类叫PackageParser,路径为frameworks\base\core\java\android\content\pm\PackageParser.java。当我们需要获取APK包的相关信息时,可以直接使用这个类,下面代码就是一个例子函数:

 

1 private PackageInfo parsePackage(String archiveFilePath, int flags){
2
3 PackageParser packageParser = new PackageParser(archiveFilePath);
4 DisplayMetrics metrics = new DisplayMetrics();
5 metrics.setToDefaults();
6 final File sourceFile = new File(archiveFilePath);
7 PackageParser.Package pkg = packageParser.parsePackage(
8 sourceFile, archiveFilePath, metrics, 0);
9 if (pkg == null) {
10 return null;
11 }
12
13 packageParser.collectCertificates(pkg, 0);
14
15 return PackageParser.generatePackageInfo(pkg, null, flags, 0, 0);
16 }

其中参数archiveFilePath指定APK文件路径;flags需设置PackageManager.GET_SIGNATURES位,以保证返回证书签名信息。

具体如何通过PackageParser获取签名信息在此处不做详述,具体代码请参考PackageParser中的public boolean collectCertificates(Package pkg, int flags)和private Certificate[] loadCertificates(JarFile jarFile, JarEntry je, byte[] readBuffer)方法。至于如何在Android应用开发中使用隐藏的类及方法,可以参看我的这篇文章:《Android应用开发中如何使用隐藏API》

紧接着,我们就可以通过packageInfo.signatures来访问到APK的签名信息。还需要说明的是 Android中Signature和Java中Certificate的对应关系。它们的关系如下面代码所示:

 

1 pkg.mSignatures = new Signature[certs.length];
2 for (int i=0; i 3 pkg.mSignatures[i] = new Signature(
4 certs[i].getEncoded());
5 }

也就是说signature = new Signature(certificate.getEncoded()); certificate证书中包含了公钥和证书的其他基本信息。公钥不同,证书肯定互不相同。我们可以通过certificate的getPublicKey方法获取公钥信息。所以比对签名证书本质上就是比对公钥信息。

OK,获取到APK签名证书之后,就剩下比对了。这个简单,功能函数如下所示:

1 private boolean IsSignaturesSame(Signature[] s1, Signature[] s2) {
2 if (s1 == null) {
3 return false;
4 }
5 if (s2 == null) {
6 return false;
7 }
8 HashSet set1 = new HashSet();
9 for (Signature sig : s1) {
10 set1.add(sig);
11 }
12 HashSet set2 = new HashSet();
13 for (Signature sig : s2) {
14 set2.add(sig);
15 }
16 // Make sure s2 contains all signatures in s1.
17 if (set1.equals(set2)) {
18 return true;
19 }
20 return false;
21 }


APK签名比对的应用场景

经过以上的论述,想必大家已经明白签名比对的原理和我的实现方式了。那么什么时候什么情况适合使用签名对比来保障Android APK的软件安全呢?

个人认为主要有以下三种场景:

1、 程序自检测。在程序运行时,自我进行签名比对。比对样本可以存放在APK包内,也可存放于云端。缺点是程序被破解时,自检测功能同样可能遭到破坏,使其失效。

2、 可信赖的第三方检测。由可信赖的第三方程序负责APK的软件安全问题。对比样本由第三方收集,放在云端。这种方式适用于杀毒安全软件或者APP Market之类的软件下载市场。缺点是需要联网检测,在无网络情况下无法实现功能。(不可能把大量的签名数据放在移动设备本地)。

3、 系统限定安装。这就涉及到改Android系统了。限定仅能安装某些证书的APK。软件发布商需要向系统发布上申请证书。如果发现问题,能追踪到是哪个软件发布商的责任。适用于系统提供商或者终端产品生产商。缺点是过于封闭,不利于系统的开放性。

以上三种场景,虽然各有缺点,但缺点并不是不能克服的。例如,我们可以考虑程序自检测的功能用native method的方法实现等等。软件安全是一个复杂的课题,往往需要多种技术联合使用,才能更好的保障软件不被恶意破坏




这篇关于apk签名机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1074987

相关文章

Linux系统稳定性的奥秘:探究其背后的机制与哲学

在计算机操作系统的世界里,Linux以其卓越的稳定性和可靠性著称,成为服务器、嵌入式系统乃至个人电脑用户的首选。那么,是什么造就了Linux如此之高的稳定性呢?本文将深入解析Linux系统稳定性的几个关键因素,揭示其背后的技术哲学与实践。 1. 开源协作的力量Linux是一个开源项目,意味着任何人都可以查看、修改和贡献其源代码。这种开放性吸引了全球成千上万的开发者参与到内核的维护与优化中,形成了

20170723 做的事 ecdsa的签名验证时间短于bls signature

1 今天在虚拟机 /home/smile/Desktop/20170610/Test//time_ecdsa 文件夹下,找到ecdsa的验证时间是 989.060606μs μs 先 make ,然后run。 再取BLS的签名生成时间: ./run  2  gnuplot 画图,画对比的时间 gnuplot 画图参考教程 http://blog.sciencen

Spring中事务的传播机制

一、前言 首先事务传播机制解决了什么问题 Spring 事务传播机制是包含多个事务的方法在相互调用时,事务是如何在这些方法间传播的。 事务的传播级别有 7 个,支持当前事务的:REQUIRED、SUPPORTS、MANDATORY; 不支持当前事务的:REQUIRES_NEW、NOT_SUPPORTED、NEVER,以及嵌套事务 NESTED,其中 REQUIRED 是默认的事务传播级别。

多头注意力机制(Multi-Head Attention)

文章目录 多头注意力机制的作用多头注意力机制的工作原理为什么使用多头注意力机制?代码示例 多头注意力机制(Multi-Head Attention)是Transformer架构中的一个核心组件。它在机器翻译、自然语言处理(NLP)等领域取得了显著的成功。多头注意力机制的引入是为了增强模型的能力,使其能够从不同的角度关注输入序列的不同部分,从而捕捉更多层次的信息。 多头注意力机

Linux-笔记 线程同步机制

目录 前言 实现 信号量(Semaphore) 计数型信号量 二值信号量  信号量的原语操作 无名信号量的操作函数 例子 互斥锁(mutex) 互斥锁的操作函数 例子 自旋锁 (Spinlock) 自旋锁与互斥锁的区别 自旋锁的操作函数 例子 前言         线程同步是为了对共享资源的访问进行保护,确保数据的一致性,由于进程中会有多个线程的存在,

Spring 集成 RabbitMQ 与其概念,消息持久化,ACK机制

目录 RabbitMQ 概念exchange交换机机制 什么是交换机binding?Direct Exchange交换机Topic Exchange交换机Fanout Exchange交换机Header Exchange交换机RabbitMQ 的 Hello - Demo(springboot实现)RabbitMQ 的 Hello Demo(spring xml实现)RabbitMQ 在生产环境

Rust:Future、async 异步代码机制示例与分析

0. 异步、并发、并行、进程、协程概念梳理 Rust 的异步机制不是多线程或多进程,而是基于协程(或称为轻量级线程、微线程)的模型,这些协程可以在单个线程内并发执行。这种模型允许在单个线程中通过非阻塞的方式处理多个任务,从而实现高效的并发。 关于“并发”和“并行”的区别,这是两个经常被提及但含义不同的概念: 并发(Concurrency):指的是同时处理多个任务的能力,这些任务可能在同一时

ROS话题通信机制实操C++

ROS话题通信机制实操C++ 创建ROS工程发布方(二狗子)订阅方(翠花)编辑配置文件编译并执行注意订阅的第一条数据丢失 ROS话题通信的理论查阅ROS话题通信流程理论 在ROS话题通信机制实现中,ROS master 不需要实现,且连接的建立也已经被封装了,需要关注的关键点有三个: 发布方(二狗子)订阅方(翠花)数据(此处为普通文本) 创建ROS工程 创建一个ROS工程

Java面试题:内存管理、类加载机制、对象生命周期及性能优化

1. 说一下 JVM 的主要组成部分及其作用? JVM包含两个子系统和两个组件:Class loader(类装载)、Execution engine(执行引擎)、Runtime data area(运行时数据区)、Native Interface(本地接口)。 Class loader(类装载):根据给定的全限定名类名(如:java.lang.Object)装载class文件到Runtim

Android Apk瘦身方案1——R.java文件常量内联

R.java 文件结构 R.java 是自动生成的,它包含了应用内所有资源的名称到数值的映射关系。先创建一个最简单的工程,看看 R.java 文件的内容: R文件生成的目录为app/build/generated/not_namespaced_r_class_sources/xxxxxDebug/processXXXXDebugResources/r/com/xxx/xxx/R.java