图1 DF_SDM_1008.zip文件

1. META-INF\ (注:签名后的信息);

2. res\ (注:存放资源文件的目录) ;

3. AndroidManifest.xml (注:程序全局配置文件) ;

4. classes.dex (注:Dalvik字节码);

5. resources.arsc (注:编译后的二进制资源文件)。

接下来针对META-INF\文件进行分析。

3.3META-INF\文件META-INF\文件中有三个文件,分别是MANIFEST.MF, CERT.SF, CERT.RSA,如图2所示。

android 二次打包原签名 安卓打包签名机制_Android

2.png (12.97 KB, 下载次数: 1)

2014-11-13 11:34 上传

现在有一个问题就是,三个文件怎么产生的的——签名产生的,第二个问题签名是怎么做的呢?这里Android提供了APK的签名工具signapk,通过xxx.keystore(java的密钥库、用来进行通信加密用的、比如数字签名。keystore就是用来保存密钥对的,比如公钥和私钥)提供的信息,对APK进行签名,生成的META-INF\文件,参考文章4。

1.MANIFEST.MF文件先看一下源代码:

[Asm] 纯文本查看 复制代码// MANIFEST.MF
Manifest manifest = addDigestsToManifest(inputJar);
je = new JarEntry(JarFile.MANIFEST_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
manifest.write(outputJar);
[Asm] 纯文本查看 复制代码/** 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)");
}
 ......
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;
}

遍历APK包中的每一个文件,利用SHA1算法生成这些文件的摘要信息。

验证是所有文件使用的SHA1算法:

1.安装hashTab工具

2.打开MANIFEST.MF文件

举个例子:

[Asm] 纯文本查看 复制代码Name: AndroidManifest.xml

SHA1-Digest: Zovq4AVMcCjFkILZLlHgmeOLvnU=

其中找到文件中的AndroidManifest.xml文件,查看其对应的hash值,如图3所示。

android 二次打包原签名 安卓打包签名机制_Android

3.png (32.13 KB, 下载次数: 1)

2014-11-13 11:36 上传

这里取出16进制的668BEAE0054C7028C59082D92E51E099E38BBE75,将16进制通过在线转码网站:hex to base64转化为对应的base64编码,看见“Zovq4AVMcCjFkILZLlHgmeOLvnU=”与记录信息相对的。

android 二次打包原签名 安卓打包签名机制_Android

4.png (16.41 KB, 下载次数: 1)

2014-11-13 11:36 上传

2.CERT.SF文件先看一下源码:

[Asm] 纯文本查看 复制代码// 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));
[Asm] 纯文本查看 复制代码/** 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 entries = manifest.getEntries();
for (Map.Entry entry : entries.entrySet()) {
// Digest of the manifest stanza for this entry.
print.print("Name: " + entry.getKey() + "\r\n");
for (Map.Entry 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);
// 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');
}
}

虽然writeSignatureFile字面上看起来是写签名文件,但是CERT.SF的生成和私钥没有一分钱的关系,实际上也不应该有一分钱的关系,这个文件自然不保存任何签名内容。CERT.SF中保存的是MANIFEST.MF的摘要值(第一项),

[Asm] 纯文本查看 复制代码

Signature-Version: 1.0
Created-By: 1.0 (Android)
SHA1-Digest-Manifest: nGpBbfOirA4fsY0pn0dBONop5bQ=

以及MANIFEST.MF中每一个摘要项的摘要值。我也没搞清楚为什么要引入CERT.SF,实际上我也觉得签名完全可以用MANIFEST.MF生成。

验证所有的摘要都是MANIFEST.MF条目:

首先:对应MANIFEST.MF文件,对应的消息摘要为SHA1-Digest-Manifest: nGpBbfOirA4fsY0pn0dBONop5bQ=,对应的实际消息摘要如图4所示。

android 二次打包原签名 安卓打包签名机制_Android

5.png (5.38 KB, 下载次数: 1)

2014-11-13 11:39 上传

android 二次打包原签名 安卓打包签名机制_Android

6.png (5.01 KB, 下载次数: 1)

2014-11-13 11:39 上传

图4 MANIFEST.MF消息摘要和对应的base64编码

其次:验证条目的消息摘要,根据条目消息摘要的算法知道内容格式为SHA1("Name: filename"+CR+LF+"SHA1-Digest: "+SHA1(file_content)+CR+LF+CR+LF)

我先把CERT.SF中的一项取出来,然后验证,取出

[Asm] 纯文本查看 复制代码Name: AndroidManifest.xml

SHA1-Digest: PJblxooLyYkHHlr/0lKZkk2DkM0=

在将MANIFEST.MF条目取出,保存为“新建文本文档.txt”,查看对应的消息摘要,并将其转换为base64编码,如图5所示。

android 二次打包原签名 安卓打包签名机制_Android

7.png (32.53 KB, 下载次数: 1)

2014-11-13 11:41 上传

android 二次打包原签名 安卓打包签名机制_Android

8.png (5.9 KB, 下载次数: 1)

2014-11-13 11:41 上传

图5 新建文本文档.txt的消息摘要和对应的base64编码

这里完成了对CERT.SF的验证。

3.CERT.RSA文件

[Asm] 纯文本查看 复制代码代码为:

// CERT.RSA
je = new JarEntry(CERT_RSA_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
writeSignatureBlock(signature, publicKey, outputJar);
[Asm] 纯文本查看 复制代码/** 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内容。

signature这个数据会作为签名用到的摘要,writeSignatureBlock函数用privateKey对signature加密生成签名,然后把签名和公钥证书一起保存到CERT.RSA中。

最终保存在CERT.RSA中的是CERT.SF的数字签名,签名使用privateKey生成的,签名算法会在publicKey中定义。同时还会把publicKey存放在CERT.RSA中,也就是说CERT.RSA包含了签名和签名用到的证书。并且要求这个证书是自签名的。

提取CERT.RSA信息:

参考有RSA公钥信息、subject信息、对应的签名信息。

[Asm] 纯文本查看 复制代码Certificate:

Data:

Version: 3 (0x2)
Serial Number: 1281971851 (0x4c69568b)
Signature Algorithm: sha1WithRSAEncryption
Issuer: CN=Michael Liu
Validity
Not Before: Aug 16 15:17:31 2010 GMT
Not After : Aug 10 15:17:31 2035 GMT
Subject: CN=Michael Liu
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (1024 bit)
Modulus (1024 bit):
00:8d:04:84:a2:1e:c6:56:39:f2:cd:a6:f0:48:a5:
f7:5e:71:8f:e1:a8:af:a7:dc:66:92:a2:b9:cf:da:
0f:32:42:ce:83:fe:bc:e1:4f:0a:fd:d9:a8:b3:73:
f4:ff:97:15:17:87:d6:d0:3c:da:01:fc:11:40:7d:
04:da:31:cc:cd:da:d0:e7:7b:e3:c1:84:30:9f:21:
93:95:20:48:b1:2d:24:02:d2:b9:3c:87:0d:fa:b8:
e1:b1:45:f4:8d:90:0a:3b:9d:d8:8a:9a:96:d1:51:
23:0e:8e:c4:09:68:7d:95:be:c6:42:e9:54:a1:5c:
5d:3f:25:d8:5c:c3:42:73:21
Exponent: 65537 (0x10001)
Signature Algorithm: sha1WithRSAEncryption
78:3c:6b:ef:71:70:55:68:28:80:4d:f8:b5:cd:83:a9:01:21:
2a:c1:e4:96:ad:bc:5f:67:0c:cd:c3:34:51:6d:63:90:a9:f9:
d5:5e:c7:ef:34:43:86:7d:68:e1:99:87:92:86:34:91:6d:67:
6d:b2:22:e9:5e:28:aa:e8:05:52:04:6e:4e:d4:7f:0f:b0:d6:
28:f5:2b:11:38:d5:15:cb:e3:e4:c9:99:23:c1:84:4f:ce:69:
e9:b1:59:7b:8e:30:01:1c:e1:92:ee:0d:54:61:29:f5:8e:9e:
42:72:26:2b:aa:c7:af:d9:c9:d1:85:95:8e:4c:8d:5c:77:c5:
ce:4e