Android的签名与校验机制

本文详细介绍了Android的签名与校验机制,包括消息摘要、数字签名和数字证书的概念,强调了Android系统对应用签名的要求和作用,以及签名验证的过程。Android应用必须经过签名,签名过程涉及MANIFEST.MF、CERT.SF和CERT.RSA文件的生成,系统在安装时进行签名校验。此外,还探讨了签名验证的多种方式和应用场景。

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

一、相关算法:

1、安全哈希算法(SHA1)、MD5算法

2、非对称公钥算法(RSA)

3、BASE64算法

二、基本概念

1)消息摘要(Message Digest)

简称摘要,也称数字指纹,指在消息数据上执行一个单向的Hash函数,生成一个固定的Hash值,是Android安装程序用来对APK完整性校验的基础,它有以下两个特点:

1、摘要无法推算出消息本身

2、消息不同,摘要自会不同

摘要生成算法主要有:MD5/SHA-0 SHA-1


2)数字签名(Signature)

数字签名就是信息的发送者用自己的私钥对消息摘要加密产生一个字符串,加密算法确保别人无法伪造生成这段字符串,这段数字串也是对信息的发送者发送信息真实性的一个有效证明。数字签名技术是非对称密钥加密技术(RSA)+数字摘要技术(SHA1等)的结合。

数字签名技术是将信息摘要用发送者的私钥加密,与原文一起传送给接收者。接收者只有用发送者的公钥才能解密被加密的信息摘要,然后接收者用相同的Hash函数对收到的原文产生一个信息摘要,与解密的信息摘要做比对。如果相同,则说明收到的信息是完整的,在传输过程中没有被修改;不同则说明信息被修改过,因此数字签名能保证信息的完整性。并且由于只有发送者才有加密摘要的私钥,所以我们可以确定信息一定是发送者发送的。————[百度百科]


3)数字证书(Certificate)

数字证书是一个经证书授权、中心数字签名的包含公开密钥拥有者信息以及公开密钥的文件。CERT.RSA包含了一个数字签名以及一个数字证书。
需要注意的是Android APK中的CERT.RSA证书是自签名的,并不需要这个证书是第三方权威机构发布或者认证的,用户可以在本地机器自行生成这个自签名证书。


三、Android签名的作用与意义

Android系统要求所有安装到系统的应用必须经过签名,它有以下几个特点:

  • 所有的应用程序都必须有数字证书 ,Android系统不会安装一个没有数字证书的应用程序;
  • Android程序包使用的数字证书可以是自签名的,不需要一个权威的数字证书机构签名认证;
  • 如果要正式发布一个Android,必须使用一个合适的私钥生成的数字证书来给程序签名 ,而不能使用Debug证书来发布。
  • 数字证书都是有有效期的,Android只是在应用程序安装的时候才会检查证书的有效期。如果程序已经安装在系统中,即使证书过期也不会影响程序的正常功能。

Android系统签名主要有ROM签名和应用程序APK签名两种形式。ROM签名是针对已经生成的Android系统ROM包进行签名。应用程序APK签名是针对开发者开发的应用程序安装包APK进行签名。前者是对整个Android系统包签名,后者只对Android系统中一个应用程序APK签名。

Android的应用程序APK签名主要有以下几点作用:

1. 标识应用程序的发布机构与作者

2. 校验APK数据包的完整性,但签名并不能保证数据包的完整性

3. 防止交易中的抵赖发生, Android市场对软件的要求

四、Android的签名机制

签名的应用未签名的应用
对比未签名的APK与签名的APK,会发现经过签名的应用中比未签名的应用多了一个叫META-INF的文件夹,没错,该文件夹就是签名后生成的文件夹,打开该文件可发现META-INF下有三个文件:MANIFEST.MF、CERT.SF、CERT.RSA。
这里写图片描述

signapk.jar是Android源码包中的一个签名工具。通过追踪signapk.jar的
源码,我们可以完整了解Android的签名过程,(signapk.jar的源码路经:/build/tools/signapk/SignApk.java。)

签名方法:


//签名关键方法
private static Manifest addDigestsToManifest(JarFile jar);

//签名入口函数
public static void main(String[] args) {
        if (args.length != 4) {
            System.err.println("Usage: signapk " +
                    "publickey.x509[.pem] privatekey.pk8 " +
                    "input.jar output.jar");
            System.exit(2);
        }

        JarFile inputJar = null;
        JarOutputStream outputJar = null;

        try {
            X509Certificate publicKey = readPublicKey(new File(args[0]));

            // Assume the certificate is valid for at least an hour.
            long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000;

            PrivateKey privateKey = readPrivateKey(new File(args[1]));
            inputJar = new JarFile(new File(args[2]), false);  // Don't verify.
            outputJar = new JarOutputStream(new FileOutputStream(args[3]));
            outputJar.setLevel(9);

            JarEntry je;

            // MANIFEST.MF
            Manifest manifest = addDigestsToManifest(inputJar);
            je = new JarEntry(JarFile.MANIFEST_NAME);
            je.setTime(timestamp);
            outputJar.putNextEntry(je);
            manifest.write(outputJar);

            // CERT.SF
            Signature signature = Signature.getInstance("SHA1withRSA");
            signature.initSign(privateKey);
            je = new JarEntry(CERT_SF_NAME);
            je.setTime(timestamp);
            outputJar.putNextEntry(je);
            writeSignatureFile(manifest,
                    new SignatureOutputStream(outputJar, signature));

            // CERT.RSA
            je = new JarEntry(CERT_RSA_NAME);
            je.setTime(timestamp);
            outputJar.putNextEntry(je);
            writeSignatureBlock(signature, publicKey, outputJar);

            // Everything else
            copyFiles(manifest, inputJar, outputJar, timestamp);
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        } finally {
            try {
                if (inputJar != null) inputJar.close();
                if (outputJar != null) outputJar.close();
            } catch (IOException e) {
                e.printStackTrace();
                System.exit(1);
            }
        }
    }

以下分段解说:

1、生成MANIFEST.MF

签名时会首先遍历APK下所有的非文件夹非签名文件的文件,将这些文件通过单向Hash算法(MD5/SHA1等)分别得到一个数字摘要,然后写人MANIFEST.MF中,代码如下:

//关键代码
for (JarEntry entry: byName.values()) {
            String name = entry.getName();
            if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) &&
                !name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) &&
                (stripPattern == null ||
                 !stripPattern.matcher(name).matches())) {
                InputStream data = jar.getInputStream(entry);
                while ((num = data.read(buffer)) > 0) {
                    md.update(buffer, 0, num);
                }

                Attributes attr = null;
                if (input != null) attr = input.getAttributes(name);
                attr = attr != null ? new Attributes(attr) : new Attributes();
                attr.putValue("SHA1-Digest", base64.encode(md.digest()));
                output.getEntries().put(name, attr);
            }
        }

//最后执行
Manifest manifest = addDigestsToManifest(inputJar);
je = new JarEntry(JarFile.MANIFEST_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
manifest.write(outputJar);

2、生成CERT.SF文件

对第一步生成的Manifest,使用SHA1-RSA或者MD5-RSA算法,用私钥进行签名。此处需要说明的是该文件名不一定叫CERT.SF,该文件名与CERT.RSA的名称取决于签发证书。关键代码如下

// CERT.SF
 Signature signature = Signature.getInstance("SHA1withRSA");
 signature.initSign(privateKey);
 e = new JarEntry(CERT_SF_NAME);
 je.setTime(timestamp);
 outputJar.putNextEntry(je);
 writeSignatureFile(manifest,
         new SignatureOutputStream(outputJar, signature));

3、 生成CERT.RSA文件:

MANIFEST.MF存储信息为明文,CERT.SF存储了使用了私钥文件加密的密文。而CERT.RSA文件存储了公钥和数字证书相关信息。

核心代码如下:

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

五、签名的验证

Android系统会在APK安装时进行签名校验,对于校验不通过的则不予安装,主要表现为以下几种:

1) 两个应用,名字相同,签名不同

2) 升级时前一版本签名,后一版本没签名

3) 升级时前一版本为 DEBUG 签名,后一个为自定义签名

4) 升级时前一版本为 Android 源码中的签名,后一个为 DEBUG 签名或自定义签名

5) 安装未签名的程序

6) 安装升级已过有效期的程序

7) APK文件信息不完整

对于APK的验证的意义,可归纳为以下几点:

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

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

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

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

附:Android签名方法

使用命令行方式进行签名需要JDK中的两个命令行工具:
keytool.exe和jarsigner.exe。可按如下两步对apk文件进行签名:

(1)使用keytool生成专用密钥(Private Key)文件。

(2)使用jarsigner根据keytool生成的专用密钥对apk文件进行签名。

//生成密钥的命令如下:
keytool -genkey -v -keystore demo.keystore -alias demoKey -keyalg RSA -validity 30000

执行完命令后会提示继续输入:

 输入keystore密码:  
 再次输入新密码:  
 您的名字与姓氏是什么?  
 [Unknown]:  名字  
 您的组织单位名称是什么?  
 [Unknown]:  单位  
 您的组织名称是什么?  
 [Unknown]:  组织
 您所在的城市或区域名称是什么?  
 [Unknown]:  城市
 您所在的州或省份名称是什么?  
 [Unknown]:  省份
 该单位的两字母国家代码是什么?  
 [Unknown]:  CN  
 CN=名字, OU=单位, O=组织, 
 L=城市, ST=省份, C=CN 正确吗?  
   [否]:  Y  
 正在为以下对象生成 1,024 位 RSA 密钥对和自签名证书 
 (SHA1withRSA)(有效期为 30,000 天):  
          CN=lining, OU=nokiaguy.blogjava.net, 
 O=nokiaguy, L=shenyang, ST=liaoning, C=CN 
 输入<androidguy>的主密码  
         (如果和 keystore 密码相同,按回车):  
 [正在存储 androidguy-release.keystore]  

以上,录入完毕后即会生成一个demo.keystore,可将此密钥用于对APK签名,签名命令如下:

jarsigner -verbose -keystore demo.keystore -signedjar
 签名后.apk  待签名.apk  密钥别名 -digestalg SHA1 -sigalg SHA1withRSA  

该命令同样可对已签名的应用重新签名,只不过需要删除META-INF目录。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值