android签名机制
1.android为什么要签名
给apk签名可以带来以下好处:
1. 应用程序升级:如果你希望用户无缝升级到新的版本,那么你必须用同一个证书进行签名。这是由于只有以同一个证书签名,系统才会允许安装升级的应用程序。如果你采用了不同的证书,那么系统会要求你的应用程序采用不同的包名称,在这种情况下相当于安装了一个全新的应用程序。如果想升级应用程序,签名证书要相同,包名称要相同!
2.应用程序模块化:Android系统可以允许同一个证书签名的多个应用程序在一个进程里运行,系统实际把他们作为一个单个的应用程序,此时就可以把我们的应用程序以模块的方式进行部署,而用户可以独立的升级其中的一个模块。
3.代码或者数据共享:Android提供了基于签名的权限机制,那么一个应用程序就可以为另一个以相同证书签名的应用程序公开自己的功能。以同一个证书对多个应用程序进行签名,利用基于签名的权限检查,你就可以在应用程序间以安全的方式共享代码和数据了。
2.签名的方法
3.签名机制的原理
3.1基本知识
简称摘要,请看英文翻译,是摘要,不是签名,网上几乎所有APK签名分析的文章都混淆了这两个概念。简单的说消息摘要就是在消息数据上,执行一个单向的Hash函数,生成一个固定长度的Hash值,这个Hash值即是消息摘要也称为数字指纹,消息摘要有以下特点:
1. 通过摘要无法推算得出消息本身
2. 如果修改了消息,那么摘要一定会变化(实际上,由于长明文生成短摘要的Hash必然会产生碰撞),所以这句话并不准确,我们可以改为:很难找到一种模式,修改了消息,而它的摘要不会变化(抗冲突性)。
注意,消息摘要只能保证消息的完整性,并不能保证消息的不可篡改性。
MD5/SHA-0 SHA-1
这些都是摘要生成算法,和签名没有关系。如果非要说他们和签名有关系,那就是签名是要借助于摘要技术。
数字签名 - Signature
数字签名,百度百科对数字签名有非常清楚的介绍。数字签名就是信息的发送者用自己的私钥对消息摘要加密产生一个字符串,加密算法确保别人无法伪造生成这段字符串,这段数字串也是对信息的发送者发送信息真实性的一个有效证明。数字签名是 非对称密钥加密技术 + 数字摘要技术 的结合。
数字签名技术是将信息摘要用发送者的私钥加密,与原文一起传送给接收者。接收者只有用发送者的公钥才能解密被加密的信息摘要,然后接收者用相同的Hash函数对收到的原文产生一个信息摘要,与解密的信息摘要做比对。如果相同,则说明收到的信息是完整的,在传输过程中没有被修改;不同则说明信息被修改过,因此数字签名能保证信息的完整性。并且由于只有发送者才有加密摘要的私钥,所以我们可以确定信息一定是发送者发送的。
数字证书 - Certificate
数字证书是一个经证书授权 中心数字签名的包含公开密钥拥有者信息以及公开密钥的文件。CERT.RSA包含了一个数字签名以及一个数字证书。
需要注意的是Android APK中的CERT.RSA证书是自签名的,并不需要这个证书是第三方权威机构发布或者认证的,用户可以在本地机器自行生成这个自签名证书。
3.2 Android签名分析
2. res\ (注:存放资源文件的目录) ;
3. AndroidManifest.xml (注:程序全局配置文件) ;
4. classes.dex (注:Dalvik字节码);
5. resources.arsc (注:编译后的二进制资源文件)。
3.3META-INF\文件
1.MANIFEST.MF文件
// 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)"); } <span style="white-space:pre"> </span>...... 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算法生成这些文件的摘要信息。
2.打开MANIFEST.MF文件
Name: AndroidManifest.xml SHA1-Digest: Zovq4AVMcCjFkILZLlHgmeOLvnU=
2.CERT.SF文件
// 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'); } }虽然writeSignatureFile字面上看起来是写签名文件,但是CERT.SF的生成和私钥没有一分钱的关系,实际上也不应该有一分钱的关系,这个文件自然不保存任何签名内容。CERT.SF中保存的是MANIFEST.MF的摘要值(第一项),
Signature-Version: 1.0 Created-By: 1.0 (Android) SHA1-Digest-Manifest: nGpBbfOirA4fsY0pn0dBONop5bQ=
验证所有的摘要都是MANIFEST.MF条目:
首先:对应MANIFEST.MF文件,对应的消息摘要为SHA1-Digest-Manifest: nGpBbfOirA4fsY0pn0dBONop5bQ=,对应的实际消息摘要如图4所示。
Name: AndroidManifest.xml SHA1-Digest: PJblxooLyYkHHlr/0lKZkk2DkM0=在将MANIFEST.MF条目取出,保存为“新建文本文档.txt”,查看对应的消息摘要,并将其转换为base64编码,如图5所示。
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); }
signature这个数据会作为签名用到的摘要,writeSignatureBlock函数用privateKey对signature加密生成签名,然后把签名和公钥证书一起保存到CERT.RSA中。
最终保存在CERT.RSA中的是CERT.SF的数字签名,签名使用privateKey生成的,签名算法会在publicKey中定义。同时还会把publicKey存放在CERT.RSA中,也就是说CERT.RSA包含了签名和签名用到的证书。并且要求这个证书是自签名的。
提取CERT.RSA信息:
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
参考文章:
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。