您的位置:首页 > 移动开发 > Android开发

Android应用安全机制

2016-11-09 23:04 281 查看

目录:


Android应用在系统中的运行机制
Android应用包名的作用
Android应用签名原理
Android应用签名过程

Android应用在系统中的运行机制

  Android应用在系统中会被分配唯一的UID,用来标识应用在系统中的唯一身份。每一个UID都对应着相应的进程,系统分配UID是根据应用的包名和AndroidManifest.xml文件中manifest节点的android:sharedUserId=""属性确定的。正常情况下,如果没有特别说明sharedUserId属性,系统会为每一个包名映射一个UID,也就是说每一个应用都专属一个独立的进程。但是,特殊情况下,如果两个应用具有相同的sharedUserId属性,且使用相同的证书签名(包名可以不同),那么系统会将两个应用分配同一个UID,使得两个应用运行在同一个进程当中。


Android应用包名的作用

  应用包名相当于人的身份证,是应用的唯一标识(eclipse中包名作为应用唯一标识,studio中使用applicationId作为应用唯一标识,但是getPackageName()方法返回的是applicationId)。设想一下,如果同一设备系统当中出现多个相同包名的应用,犹如多个人具有相同的身份证。如果某个人犯事了,要依法追究责任的话,根据身份证来找的话可能会找到多个人,那社会可能就会混乱了!对于系统来说亦是如此,如果没有唯一标识应用身份的东西的话,系统管理也会很困难。所以同一设备系统当中,不能存在相同包名的应用。
  包名既然是唯一的,一般采用“反域名命名”方式定义包名。也就是说如果我公司的地址是:www.everyoo.com,那么反域名后就是:com.everyoo.***。因为域名都是唯一的,所以反域名后也可以保证包名的唯一性。

Android应用签名原理

在梳理签名原理之前,要先明确几个概念:

  消息摘要:使用单向的hash函数对消息进行处理,结果会生成一个固定长度的、唯一映射源消息的摘要;如果消息(甚至一个字节)发生了改变,接受者通过比较从接收的消息解析的摘要和原摘要,便可判断消息是否被改变,常被用作数字签名。
  私钥:用来对数据进行加密,和公钥成对出现。
  公钥:用来验证数据的合法性,和私钥成对出现。  

  应用包名在设备上都是可看到的,所以存在一种情况:第三方创建相同包名的应用,然后覆盖我的应用。为了确保应用作者的正确性及应用的完整性,所以需要对应用进行签名。
  签名的原理:
1.使用hash函数对应用生成消息摘要
2.使用签名文件对消息摘要进行签名
3.接受者使用公钥获取消息摘要,然后使用相同的hash函数处理消息体,获取消息摘要,比较两个消息摘要是否相同。
  签名的方式:
1.signjar:signjar是jdk自带的,在jdk的bin目录下,使用.keystore(eclipse中)或.jks(studio中)文件对应用签名;
     2.[b]signapk:[b]signapk是后续为android应用开发的,存在于android源码中,使用的是.x509.pem和.pk8文件签名。且.keystore/.jks文件可以和.pem和.pk8文件相互转换;[/b][/b]
[b][b] 签名文件的生成(手动):[/b][/b]
[b][b] 

[/b][/b]
[b][b] -genkey:生成秘钥[/b][/b]
[b][b] -alias:签名文件别名[/b][/b]
[b][b] -keyalg:签名算法[/b][/b]
[b][b] -keysize:算法加密后秘钥长度[/b][/b]
[b][b] -validity:签名文件有效时间[/b][/b]
[b][b] -demo.jks:签名文件名称(也可以是.keystore格式,这个无关紧要)[/b][/b]
[b][b] demo.jks的信息如下:
[/b]
[/b]

  


  签名命令:
signjar:jarsigner -verbose -keystore demo.jks -signedjar app-debug-signed.apk app-debug.apk demo
     demo.jks:签名文件
     demo:签名文件别名
     app-debug-signed.apk:签名后的apk文件
     app-debug.apk:签名前的apk文件
     signapk:java -jar signapk platform.x509.pem platform.pk8 app-debug.apk app-debug-signed.apk
     .x509.pem:包含公钥的文件
     .pk8:私钥文件
  两种签名方式的区别:jarsigned使用keystore签名,signapk使用x509.pem和pk8文件 进行签名,其实,keystore文件是可以和x509.pem及pk8文件相互转换的,具体详情请自行搜索。

Android应用签名过程

   应用签名后,修改apk文件的扩展名为rar或zip,解压后有个META-INF目录中包含三类文件
  CERT.RAS/DEMO.RSA
  CERT.SF/DEMO.SF
  MANIFEST.MF


 1.MANIFEST.MF文件  
 内容如下:
 


 看起来像是文件的SHA1摘要,源码如下:
 
// MANIFEST.MF
Manifest manifest = addDigestsToManifest(inputJar);
je = new JarEntry(JarFile.MANIFEST_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
manifest.write(outputJar);


   

/** Add the SHA1 of every file to the manifest, creating it if necessary. */
private static Manifest addDigestsToManifest(JarFile jar)
throws IOException, GeneralSecurityException {
Manifest input = jar.getManifest();
Manifest output = new Manifest();
Attributes main = output.getMainAttributes();
if (input != null) {
main.putAll(input.getMainAttributes());
} else {
main.putValue("Manifest-Version", "1.0");
main.putValue("Created-By", "1.0 (Android SignApk)");
}

BASE64Encoder base64 = new BASE64Encoder();
MessageDigest md = MessageDigest.getInstance("SHA1");
byte[] buffer = new byte[4096];
int num;

// We sort the input entries by name, and add them to the
// output manifest in sorted order.  We expect that the output
// map will be deterministic.
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();
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);
}
}

return output;
}

 可以看到,manifest集成了所有文件的SHA-1处理后base64转换后的摘要。我们可以做个测试(先下载hashtab
):
 


 查看AndroidManifest.xml文件对应的hash值
 


 取出SHA-1对应的哈希值“C9FC2C9CE58BFC335417164A0B6496F444DE0D16”,然后通过hex - base64在线转码:
 


 可以看到转换后的数据是和MANIFEST.MF中AndroidManifest.xml的SHA1-Digest相同。


   2.CERT.SF

    文件内容如下:
   


   内容与MANIFEST.MF文件中类似,我们再看源码:

    

// 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));  



/** Write a .SF file with a digest of the specified manifest. */  

    private static void writeSignatureFile(Manifest manifest, SignatureOutputStream 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");  

  

        // Digest of the entire manifest  

        manifest.write(print);  

        print.flush();  

        main.putValue("SHA1-Digest-Manifest", base64.encode(md.digest()));  

  

        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);  

        }  

<span style="white-space:pre">    </span>//签名信息在上面并没有使用的到  

        sf.write(out);  

  

        // A bug in the java.util.jar implementation of Android platforms  

        // up to version 1.6 will cause a spurious IOException to be thrown  

        // if the length of the signature file is a multiple of 1024 bytes.  

        // As a workaround, add an extra CRLF in this case.  

        if ((out.size() % 1024) == 0) {  

            out.write('\r');  

            out.write('\n');  

        }  

    }  

 

代码中的manifest文件即为MANIFEST.MF文件,对MANIFEST.MF文件的每一项都进行SHA-1摘要,其中SHA1-Digest-Manifest对应的就是MANIFEST.MF文件的base64码。
  


  MANIFEST.MF文件对应的hash值是:
 



 我们把hash值转成base64结果是:
 


我们可以看到输出的base64码和CERT.SF文件中SHA1-Digest-Manifest的值是相同的,从而印证了我们的猜测;

3.CERT.RSA
 代码如下:
 

// CERT.RSA

je = new JarEntry(CERT_RSA_NAME);

je.setTime(timestamp);

outputJar.putNextEntry(je);

writeSignatureBlock(signature, publicKey, outputJar);

 /** Write a .RSA file with a digital signature. */    private static void writeSignatureBlock(            Signature signature, X509Certificate publicKey, OutputStream out)            throws IOException, GeneralSecurityException {        SignerInfo signerInfo = new SignerInfo(                new X500Name(publicKey.getIssuerX500Principal().getName()),                publicKey.getSerialNumber(),                AlgorithmId.get("SHA1"),                AlgorithmId.get("RSA"),                signature.sign());
PKCS7 pkcs7 = new PKCS7( new AlgorithmId[] { AlgorithmId.get("SHA1") }, new ContentInfo(ContentInfo.DATA_OID, null), new X509Certificate[] { publicKey }, new SignerInfo[] { signerInfo });
pkcs7.encodeSignedData(out); }
这个文件中保存了CERT.SF文件的签名和公钥。参考文章:android签名机制
Android签名机制之---签名过程详解Android签名与认证详细分析之一(CERT.RSA剖析)重新打包apk,使用java bin目录里的jarsigner进行签名
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息