Android 签名/认证机制


Android为了确认apk开发者身份和防止内容的篡改,设计了一套apk签名的方案保证apk的安全性,即在打包时由开发者进行apk的签名,在安装apk时Android系统会有相应的开发者身份和内容正确性的验证,只有验证通过才可以安装apk,签名过程和验证的设计就是基于 非对称加密的思想。

Android在7.0以前使用的一套签名方案:在apk根目录下的META-INF/文件夹下生成签名文件,然后在安装时在系统的PackageManagerService里进行签名文件的验证。

从7.0开始,Android提供了新的V2签名方案:利用apk(zip)压缩文件的格式,在几个原始内容区之外增加了一块用于存放签名信息的数据区,然后同样在安装时在系统的PackageManagerService里进行V2版本的签名验证,V2方案会更安全、使校验更快安装更快。

当然V2签名方案会向后兼容,如果没有使用V2签名就会默认走V1签名方案的验证过程。

一.V1签名

(一)签名方式

有两个工具可以进行android的签名,jarsign和signapk。

jarsign是Java本生自带的一个工具,他可以对jar进行签名的;而signapk是门为了Android应用程序apk进行签名的工具,他们两的签名算法没什么区别,主要是签名时使用的文件不一样。

jarsign签名时使用的是keystore文件(就是我们AS里经常见得keystore文件,由我们指定的密码生成的密钥文件),signapk签名时使用的是pk8(私钥)和x509.pem(公钥)文件,因为两种方式签名都可行,所以说明两种文件可以相互转换。

而我们对apk进行签名过后,会在apk根目录的META-INF/目录下产生三个文件
在这里插入图片描述
下面就来看看AS打包时如何使用我们生成的密钥文件来签名(生成这三个文件)的,以signapk源码为例。

1.MANIFEST.MF

在这里插入图片描述

Manifest manifest = addDigestsToManifest(inputJar);//使用inputJar向Manifest里生成内容
je = new JarEntry(JarFile.MANIFEST_NAME);//输出文件为META-INF/MANIFEST.MF  
je.setTime(timestamp);  
outputJar.putNextEntry(je);  
manifest.write(outputJar);//写到输出文件中

在signapk的main函数中,inputJar就是输入的apk文件,而je就是outputJar输出目标的文件—JarFile.MANIFEST_NAME,即META-INF/MANIFEST.MF。

也就是说,调用addDigestsToManifest方法,将生成的内容写到META-INF/MANIFEST.MF文件中,那么来看看addDigestsToManifest生成了什么内容。

private static Manifest addDigestsToManifest(JarFile jar)  
        throws IOException, GeneralSecurityException {
     
    Manifest input = jar.getManifest();  
    Manifest output = new Manifest();  
    Attributes main = output.getMainAttributes();  
    ...
	main.putValue("Manifest-Version", "1.0");  //输出版本
    main.putValue("Created-By", "1.0 (Android SignApk)");
  
    BASE64Encoder base64 = new BASE64Encoder();  //Base64
    MessageDigest md = MessageDigest.getInstance("SHA1");//SHA1加密  
    byte[] buffer = new byte[4096];  
    int num;  
	//记录apk中所有文件的名字和文件的映射
    TreeMap<String, JarEntry> byName = new TreeMap<String, JarEntry>();  
    for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) {
     
        JarEntry entry = e.nextElement();  
        byName.put(entry.getName(), entry);  
    }  
    for (JarEntry entry: byName.values()) {
     
        String name = entry.getName();  
		//循环除了META-INF/下的MANIFEST.MF、CERT.SF、CERT.RSA的文件
        if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) &&  
            !name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) &&  
            (stripPattern == null ||  
             !stripPattern.matcher(name).matches())) {
     
			//读取文件内容并用SHA1读取摘要
            InputStream data = jar.getInputStream(entry);  
            while ((num = data.read(buffer)) > 0) {
     
                md.update(buffer, 0, num);  
            }  
            ...
			//将摘要再用base64编码,写入文件
            attr.putValue("SHA1-Digest", base64.encode(md.digest()));  
			//将文件名和对应的编码摘要信息写入output中
            output.getEntries().put(name, attr);  
        }  
    }  
    return output;  
}

该方法其实就是将apk中除了要生成的这三个文件外的所有文件,生成对应的摘要并base64编码,然后写入到MANIFEST.MF中,每个文件的名字和编码摘要单独输出,就形成了上述MANIFEST.MF文件的样子。

2.CERT.SF

在这里插入图片描述
看文件内容感觉和MANIFEST.MF类似,我们来看看代码吧

Signature signature = Signature.getInstance("SHA1withRSA");  
signature.initSign(privateKey);//签名对象,使用SHA1withRSA算法,用开发者私钥进行签名
je = new JarEntry(CERT_SF_NAME);//META-INF/CERT.SF
je.setTime(timestamp);  
outputJar.putNextEntry(je);  
writeSignatureFile(manifest,new SignatureOutputStream(outputJar, signature));//manifest就是上述生成的MANIFEST.MF文件内容

signapk的main函数中,生成完上述的MANIFEST.MF文件后,会调用writeSignatureFile并传入上述MANIFEST.MF文件内容,将生成的内容写到META-INF/CERT.SF文件中,下面来看看writeSignatureFile方法生成了什么

 private static void writeSignatureFile(Manifest manifest, OutputStream out)  
        throws IOException, GeneralSecurityException {
     
    Manifest sf = new Manifest();
    Attributes main = sf.getMainAttributes();  
	//写入版本信息
    main.putValue("Signature-Version", "1.0");  
    main.putValue("Created-By", "1.0 (Android SignApk)");  
    BASE64Encoder base64 = new BASE64Encoder();  
    MessageDigest md = MessageDigest.getInstance("SHA1");  
    PrintStream print = new PrintStream(  
            new DigestOutputStream(new ByteArrayOutputStream(), md),  
            true, "UTF-8");  
    //将整个MANIFEST.MF文件的内容做一次SHA1摘要提取并用base64编码,写入CERT.SF 
    manifest.write(print);  
    print.flush();  
    main.putValue("SHA1-Digest-Manifest", base64.encode(md.digest()));  
  	//将MANIFEST.MF里的每个文件的编码摘要,在做一次摘要提取并base64编码,并与文件名映射输出到CERT.SF中
    Map<String, Attributes> entries = manifest.getEntries();  
    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);  
    }  
    sf.write(out);  
}

该方法其实就是先将整个MANIFEST.MF内容生成摘要进行编码输出,再把MANIFEST.MF中每个文件的编码摘要再进行一次编码摘要进行输出,形成了CERT.SF文件。

3.CERT.RSA

在这里插入图片描述
最后的这个CERT.RSA文件是二进制文件,是一个加密文件,我们来看看是什么内容

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

在signapk的main方法最后,会调用writeSignatureBlock方法,将生成内容输出到META-INF/CERT.RSA文件中

private static void writeSignatureBlock(  
        Signature signature, X509Certificate publicKey, OutputStream out)  
        throws IOException, GeneralSecurityException {
     
	//用私钥签名CERT.SF文件生成数字签名
    SignerInfo signerInfo = new SignerInfo(  
            new X500Name(publicKey.getIssuerX500Principal().getName()),  
            publicKey.getSerialNumber(),  
            AlgorithmId.get("SHA1"),  
            AlgorithmId.get("RSA"),  
            signature.sign());  
	//数字签名连同包含公钥的证书等信息一起生成PKCS7格式的文件
    PKCS7 pkcs7 = new PKCS7(  
            new AlgorithmId[] {
    AlgorithmId.get("SHA1") },  
            new ContentInfo(ContentInfo.DATA_OID, null),  
            new X509Certificate[] {
    publicKey },  
            new SignerInfo[] {
    signerInfo });  
	//写入META-INF/CERT.RSA
    pkcs7
  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值