一、签名机制

众所周知,在Android系统中,应用想要安装到设备中,必须要有签名才行,及时是debug的时候,开发工具也会对要运行的应用自动签名,那么我们先来了解一下这个签名究竟是什么。
首先Android系统为了防止以安装的应用被篡改,推出来的签名自检机制,来维护应用的安全性,可以说,签名就是一个保护个人应用不受侵害的一种机制。而且这里面说道了自检,也就是说,在Android系统中,应用的签名在安装的过程中是自行检查的,这点在本文的下面会详细说道。

那么,我们先来了解一下Android签名的整个过程,这个过程大体可以分为三个步骤,也对应了Android系统在校验签名时的三个步骤:
1、android应用打包apk的时候,会先对应用中所有的文件进行遍历,做一次SHA1算法,也就是计算出文件的摘要信息,然后再用Base64进行编码,然后将这些信息写入到MANIFEST.MF文件中。这个文件在META-INF文件夹下面,解压APK文件时就会看见了。当然,在对APK所有文件进行SHA1算法的时候,是不包括META-INF文件夹的(这句是废话)。
下文是支付宝的MANIFEST.MF文件打开后的一部分(用记事本就可以打开);

Manifest-Version: 1.0
Created-By: 1.7.0_67 (Oracle Corporation)
Name: assets/emoji/emoji_195.png
SHA1-Digest: 2YVKFrBF45WOANd9G7zdpbAm6w0=
Name: assets/emoji/emoji_356.png
SHA1-Digest: VlPA09nbS5e3iae9WvAMq8O88BM=
Name: res/layout/envelope_notify.xml
SHA1-Digest: r4LUgyo6YoeqTvKQUyME82+XINs=
Name: lib/armeabi/libsgavmp.so
SHA1-Digest: DOwWbBL42qy+Z16EggSnCNYseDI=

从上文中可以看出,在MANIFEST.MF文件中,针对应用中每个文件的SHA1算法都会以键值对的方式写入。这是为了防止APK内容被篡改。想要验证的话,可以通过安装HashTab软件(下载地址: http://www.baidu.com/s?wd=hashtab&rsv_spt=1&issp=1&f=8&rsv_bp=0&ie=utf-8&tn=baiduhome_pg&bs=hashtable ),然后找到对应的文件,右键用HashTab打开,就能看到对应的SHA1值,再到Base64网站上转码后对比一下就可以了(网址: http://tomeko.net/online_tools/hex_to_base64.php?lang=en )。
2、第二个步骤是将上文的MANIFEST.MF文件进行一次SHA1计算,然后同样Base64转码后写入到CERT.SF文件中,然后读取MANIFEST.MF文件中子条目的值再次SHA1计算,Base64转码写入到这个文件中。这个步骤同下面的第三个步骤统称为签名,都是用的签名工具来完成的。

Signature-Version: 1.0
Created-By: 1.0 (Android SignApk)
SHA1-Digest-Manifest: zg9ZRuDj9mgYGA7zbHu5lysUuxc=
Name: assets/emoji/emoji_195.png
SHA1-Digest: +HilKtU8N3AsfscyZ/3uHBc/5n4=
Name: assets/emoji/emoji_356.png
SHA1-Digest: 50goYIrW1+7aVHdrwsihe5t06pY=
Name: res/layout/envelope_notify.xml
SHA1-Digest: 1qtgFSOLlupySOaKR7m9pEgYrZM=

上文是支付宝应用中CERT.SF文件中的部分内容,我们可以发现确实和MANIFEST.MF文件中的内容类似(有的朋友可能会觉得这个步骤是重复的,下文中我会进行解释)。

3、第三个步骤是关键的步骤了,也就是对APK进行加密的过程。我们在打包签名的时候,开发工具会让我们使用一个私钥文件,如果没有就会让我们去创建一个。在Eclipse中,默认格式是.keystore文件,而在Android Studio中默认格式是.jks文件。这两个文件都是私钥库文件,里面存储了一些信息,包括所有者、有效期、证书指纹及算法等。有了私钥文件后,就可以对第二个步骤中生成的CERT.SF文件进行加密了,加密后会生成CERT.RSA文件。在Android中,总共有两种签名工具,一个是jarsigner,需要的是.keystore文件,而另一个是signapk签名用的是pk8、x509.pem文件(keystore文件可以和pk8、x509.pem文件互相转化)。这两个签名工具不同的是,jarsigner签名后生成的.SF和.RSA的名字是私钥库中别名的名字,而signapk生成的两个文件名则是默认的CERT。

生成.keystore文件是用的keytool.exe生成的,这个文件在jdk的bin目录下。生成.keystore的指令如下(最好手打,有时候编码问题,直接复制不一定能过):

keytool -genkey -v -keystore highball-key.keystore(文件名) -alias highball-key(别名) -keyalg RSA -keysize 2048 -validity 10000

查看私钥文件的指令如下:

keytool -list -keystore a.keystore(keystore文件) -alias a(别名) -v

从上图中可以看到私钥库文件中的内容。这个文件是签名机制中最重要的文件,一定要保存好,如果丢失,那么上架的应用想要更新可就麻烦了。
私钥文件已经有了,就可以对APK进行加密了。加密的指令如下:

jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore a.keystore(keystore文件) a.apk(apk文件) a-key(别名)

其实这一个指令可以完成整个这三个步骤,因为要对应到Android系统的签名校验原理,所以分开来说明。
生成.RSA文件后用记事本打开是乱码的,可以将后缀名改成.p7r。然后就可以打开查看了,截图如下:

从上图可以看出.RSA文件包含了加密后的公钥和指纹,这两个信息是非常重要的,在Android系统对apk签名校验的时候也要用到。手动加密完之后最好对apk进行对其操作:

zipalign -v 4 a.apk  a-release.apk(新的apk的命名)

校验对齐是否成功:

zipalign -c -v 4 a-release.apk(apk名)

二、多重签名
Android系统是支持多重签名的,这是在签名校验时,系统只是对相同名的.SF文件和.RSA文件进行校验,而且是在while循环中进行的校验,也就是说可以不止一对.SF和.RSA文件,详情在下文中会有说明。

如果要进行多重签名,那么经过本人多次试验后得出的结果是,不能用Eclipse或者Android Studio签名后的apk来进行二次签名,而是要导出未签名的apk。

导出未签名的apk,在Android Studio的操作如下图所示:

在Gradle视图中,找到对应的项目下的assemble,执行双击操作,然后就会在module所在文件夹的outputs\apk文件夹中出现未签名的apk。这个操作很简单就不详细描述了,得到的apk文件中是没有META-INF文件夹的。

接下来就可以按照前文介绍的操作,生成两个密钥库文件(.keystore文件)。然后对未签名apk进行两次签名操作,得到了两次签名的apk文件,其META-INF文件夹内容如下:

从上图我们可以发现,MANIFEST.MF文件只有一个,而.SF和.RSA文件则是有两对,名字是对应密钥库文件的别名。OK多重签名就成功搞定了,下面来看看在Android系统中,是如何针对应用签名进行校验的。

三、Android签名校验
Android系统下,针对应用的安装是在PMS(Package Manager Service)中完成的, PMS是一个比较庞大的系统服务,我们今天只研究PMS中关于签名校验的部分。同apk签名三步骤相对应,PMS中对签名校验也是分成了三个步骤,先是 验证CERT.SF文件的摘要信息和CERT.RSA中的签名信息数据是否一致、验证MANIFEST.MF文件和CERT.SF文件是否匹配,最后是校验 APK 中每个文件的算法同 MANIFEST.MF 文件中是否一致。
1、校验CERT.SF文件和CERT.RSA文件
首先说明一下校验这两个文件的原理。Android系统对这两个文件的校验,是拿到.RSA文件的流对象,用ASN1解码后拿到签名数据,然后从签名信息中分别获取相关信息(发布机构、序列号、签名算法、公钥等)。然后通过公钥对签名进行解密,获得了解密后的数据同.SF文件进行对比。

Android 系统对应用的安装过程是在 PackageManagerService 类中通过发送 handler 来处理针对包的一些消息的 , 比如说清理安装包 , 发送安装等 , 其中针对签名校验也在这里处理。
在PackageManagerService.java中执行安装的方法是installPackageLI(args,res);在这个方法中获取了PackageParser对象,PackageParser.java是用来解析APK包的,也就是说签名信息校验的工作是在这个类中开始进行的。而执行校验的方法是PackageParser.java中的collectCertificates方法:

private static void collectCertificates(Package pkg, File apkFile, int flags)throws PackageParserException {final String apkPath = apkFile.getAbsolutePath();StrictJarFile jarFile = null;try {jarFile = new StrictJarFile(apkPath);// Always verify manifest, regardless of sourcefinal ZipEntry manifestEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME);if (manifestEntry == null) {throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,"Package " + apkPath + " has no manifest");}...}
}

这里面new了一个StrictJarFile对象,这个类的构造方法中完成了CERT.SF文件和CERT.RSA文件中关于证书的校验。因为在这个构造方法中有个Boolean类型的变量的赋值,就是完成证书校验的关键。那么我们看一下这个变量是如何赋值的:

public StrictJarFile(String fileName) throws IOException {this.nativeHandle = nativeOpenJarFile(fileName);this.raf = new RandomAccessFile(fileName, "r");try {// Read the MANIFEST and signature files up front and try to// parse them. We never want to accept a JAR File with broken signatures// or manifests, so it's best to throw as early as possible.HashMap<String, byte[]> metaEntries = getMetaEntries();this.manifest = new Manifest(metaEntries.get(JarFile.MANIFEST_NAME), true);this.verifier = new JarVerifier(fileName, manifest, metaEntries);isSigned = verifier.readCertificates() && verifier.isSignedJar(); //完成校验方法} catch (IOException ioe) {nativeClose(this.nativeHandle);throw ioe;}guard.open("close");}

从截图中可以看到,总共执行了两个方法,先看一下readCertificates方法:

synchronized boolean readCertificates() {if (metaEntries.isEmpty()) {return false;}Iterator<String> it = metaEntries.keySet().iterator();while (it.hasNext()) {String key = it.next();if (key.endsWith(".DSA") || key.endsWith(".RSA") || key.endsWith(".EC")) {verifyCertificate(key);it.remove();}}return true;}

从方法中可以看出,这个方法首先判断一下是否有MANIFEST.MF文件,如果没有就返回false,那么isSigned就会被赋值为false(这个变量的赋值会在后续方法中作为校验的参数)。然后会去找证书文件,而在android的项目中,一般多采用的是RSA加密算法,也就是生成的.RSA文件,所以这个方法会把.RSA文件找到,执行verifyCertificate方法:

private void verifyCertificate(String certFile) {// Found Digital Sig, .SF should already have been readString signatureFile = certFile.substring(0, certFile.lastIndexOf('.')) + ".SF";byte[] sfBytes = metaEntries.get(signatureFile);if (sfBytes == null) {return;}byte[] manifestBytes = metaEntries.get(JarFile.MANIFEST_NAME);// Manifest entry is required for any verifications.if (manifestBytes == null) {return;}byte[] sBlockBytes = metaEntries.get(certFile);try {Certificate[] signerCertChain = JarUtils.verifySignature(new ByteArrayInputStream(sfBytes),new ByteArrayInputStream(sBlockBytes)); // 校验执行的位置if (signerCertChain != null) {certificates.put(signatureFile, signerCertChain);}} catch (IOException e) {return;} catch (GeneralSecurityException e) {throw failedVerification(jarName, signatureFile);}...
}

这是方法的一部分,通过.RSA文件的名字找到对应的.SF文件,然后在获取MANIFEST.MF文件和.RSA文件,到这里三个文件就都拿到了。拿到三个文件数据后,会调用JarUtils.verifySignature方法, 这个方法,参数是.SF文件和.RSA文件的内存流对象;这个方法中通过用X509文件解析了.RSA文件,这个方法最终的目的是获取到RSA文件中的证书;然后同.SF文件的数据进行对比,如果不相同会抛出异常:

...
if (md == null && daName != null) {md = MessageDigest.getInstance(daName);}if (md == null) {return null;}byte[] computedDigest = md.digest(sfBytes);if (!Arrays.equals(existingDigest, computedDigest)) {throw new SecurityException("Incorrect MD");}
...

然后回到JarVerifier.java的verifyCertificate方法中,会将之前拿到的签名数据封装到集合中:

...
if (signerCertChain != null) {certificates.put(signatureFile, signerCertChain);}
...

在创建StrictJarFile对象的时候,就完成了对.RSA和.SF文件的校验操作。

2 、校验MANIFEST.MF文件和.SF文件对应的摘要值
在JarVerifier.java中的verifyCertificate方法中,会继续执行校验MANIFEST.MF文件和.SF文件的操作:

private void verifyCertificate(String certFile) {// Found Digital Sig, .SF should already have been read...// Verify manifest hash in .sf fileAttributes attributes = new Attributes();HashMap<String, Attributes> entries = new HashMap<String, Attributes>();try {ManifestReader im = new ManifestReader(sfBytes, attributes);im.readEntries(entries, null);} catch (IOException e) {return;}// Do we actually have any signatures to look at?if (attributes.get(Attributes.Name.SIGNATURE_VERSION) == null) {return;}boolean createdBySigntool = false;String createdBy = attributes.getValue("Created-By");if (createdBy != null) {createdBySigntool = createdBy.indexOf("signtool") != -1;}// Use .SF to verify the mainAttributes of the manifest// If there is no -Digest-Manifest-Main-Attributes entry in .SF// file, such as those created before java 1.5, then we ignore// such verification.if (mainAttributesEnd > 0 && !createdBySigntool) {String digestAttribute = "-Digest-Manifest-Main-Attributes";if (!verify(attributes, digestAttribute, manifestBytes, 0, mainAttributesEnd, false, true)) {throw failedVerification(jarName, signatureFile);}}// Use .SF to verify the whole manifest.String digestAttribute = createdBySigntool ? "-Digest" : "-Digest-Manifest";if (!verify(attributes, digestAttribute, manifestBytes, 0, manifestBytes.length, false, false)) {Iterator<Map.Entry<String, Attributes>> it = entries.entrySet().iterator();while (it.hasNext()) {Map.Entry<String, Attributes> entry = it.next();Manifest.Chunk chunk = manifest.getChunk(entry.getKey());if (chunk == null) {return;}if (!verify(entry.getValue(), "-Digest", manifestBytes,chunk.start, chunk.end, createdBySigntool, false)) {throw invalidDigest(signatureFile, entry.getKey(), jarName);}}}metaEntries.put(signatureFile, null);signatures.put(signatureFile, entries);}

上面代码中,前面一部分基本是校验的准备工作,主要是封装数据,真正的校验操作是上文中的verify方法:

private boolean verify(Attributes attributes, String entry, byte[] data,int start, int end, boolean ignoreSecondEndline, boolean ignorable) {for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) {String algorithm = DIGEST_ALGORITHMS[i];String hash = attributes.getValue(algorithm + entry);if (hash == null) {continue;}MessageDigest md;try {md = MessageDigest.getInstance(algorithm);} catch (NoSuchAlgorithmException e) {continue;}if (ignoreSecondEndline && data[end - 1] == '\n' && data[end - 2] == '\n') {md.update(data, start, end - 1 - start);} else {md.update(data, start, end - start);}byte[] b = md.digest();byte[] hashBytes = hash.getBytes(StandardCharsets.ISO_8859_1);return MessageDigest.isEqual(b, Base64.decode(hashBytes));}return ignorable;}

调用这个方法就完成了MANIFEST.MF文件和.SF文件的对比.

3、校验APK文件摘要值同MANIFEST.MF文件中对应值是否相同
接下来回到PackageParser.java中的collectCertificates方法,创建 StrictJarFile对象后, 会检查一下是否有清单文件(AndroidManifest.xml),如果没有则直接抛出异常。然后遍历查询到META-INF文件

private static void collectCertificates(Package pkg, File apkFile, int flags)throws PackageParserException {...// Always verify manifest, regardless of sourcefinal ZipEntry manifestEntry = jarFile.findEntry(ANDROID_MANIFEST_FILENAME);if (manifestEntry == null) {throw new PackageParserException(INSTALL_PARSE_FAILED_BAD_MANIFEST,"Package " + apkPath + " has no manifest");}final List<ZipEntry> toVerify = new ArrayList<>();toVerify.add(manifestEntry);// If we're parsing an untrusted package, verify all contentsif ((flags & PARSE_IS_SYSTEM) == 0) {final Iterator<ZipEntry> i = jarFile.iterator();while (i.hasNext()) {final ZipEntry entry = i.next();if (entry.isDirectory()) continue;if (entry.getName().startsWith("META-INF/")) continue;if (entry.getName().equals(ANDROID_MANIFEST_FILENAME)) continue;toVerify.add(entry);}}// Verify that entries are signed consistently with the first entry// we encountered. Note that for splits, certificates may have// already been populated during an earlier parse of a base APK.for (ZipEntry entry : toVerify) {final Certificate[][] entryCerts = loadCertificates(jarFile, entry);if (ArrayUtils.isEmpty(entryCerts)) {throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,"Package " + apkPath + " has no certificates at entry "+ entry.getName());}final Signature[] entrySignatures = convertToSignatures(entryCerts);if (pkg.mCertificates == null) {pkg.mCertificates = entryCerts;pkg.mSignatures = entrySignatures;pkg.mSigningKeys = new ArraySet<PublicKey>();for (int i=0; i < entryCerts.length; i++) {pkg.mSigningKeys.add(entryCerts[i][0].getPublicKey());}} else {if (!Signature.areExactMatch(pkg.mSignatures, entrySignatures)) {throw new PackageParserException(INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, "Package " + apkPath+ " has mismatched certificates at entry "+ entry.getName());}}}} catch (GeneralSecurityException e) {throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,"Failed to collect certificates from " + apkPath, e);} catch (IOException | RuntimeException e) {throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,"Failed to collect certificates from " + apkPath, e);} finally {closeQuietly(jarFile);}}

对toVerify集合遍历, 这里面涉及到了最关键的方法:loadCertificates方法。

private static Certificate[][] loadCertificates(StrictJarFile jarFile, ZipEntry entry)throws PackageParserException {InputStream is = null;try {// We must read the stream for the JarEntry to retrieve// its certificates.is = jarFile.getInputStream(entry);readFullyIgnoringContents(is);return jarFile.getCertificateChains(entry);} catch (IOException | RuntimeException e) {throw new PackageParserException(INSTALL_PARSE_FAILED_UNEXPECTED_EXCEPTION,"Failed reading " + entry.getName() + " in " + jarFile, e);} finally {IoUtils.closeQuietly(is);}}

先看一下getInputStream方法:

public InputStream getInputStream(ZipEntry ze) {final InputStream is = getZipInputStream(ze);if (isSigned) {JarVerifier.VerifierEntry entry = verifier.initEntry(ze.getName());if (entry == null) {return is;}return new JarFile.JarFileInputStream(is, ze.getSize(), entry);}return is;}

在这个方法中,显示根据isSigned进行判断(上文中已经说明),然后获取的VerifierEntry对象。在initEntry方法:

VerifierEntry initEntry(String name) {...ArrayList<Certificate[]> certChains = new ArrayList<Certificate[]>();Iterator<Map.Entry<String, HashMap<String, Attributes>>> it = signatures.entrySet().iterator();while (it.hasNext()) {Map.Entry<String, HashMap<String, Attributes>> entry = it.next();HashMap<String, Attributes> hm = entry.getValue();if (hm.get(name) != null) {// Found an entry for entry name in .SF fileString signatureFile = entry.getKey();Certificate[] certChain = certificates.get(signatureFile);if (certChain != null) {certChains.add(certChain);}}}// entry is not signedif (certChains.isEmpty()) {return null;}Certificate[][] certChainsArray = certChains.toArray(new Certificate[certChains.size()][]);for (int i = 0; i < DIGEST_ALGORITHMS.length; i++) {final String algorithm = DIGEST_ALGORITHMS[i];final String hash = attributes.getValue(algorithm + "-Digest");if (hash == null) {continue;}byte[] hashBytes = hash.getBytes(StandardCharsets.ISO_8859_1);try {return new VerifierEntry(name, MessageDigest.getInstance(algorithm), hashBytes,certChainsArray, verifiedEntries);} catch (NoSuchAlgorithmException ignored) {}}return null;}

这个方法的目的是构造VerifierEntry对象,所以这个方法主要进行的就是准备构建这个对象的参数。这些参数分别是要验证文件的名字。第二个是计算摘要的对象。第三个参数是对应文件的摘要值,从代码中可以看到是ISO_8859_1格式的。第四个参数是证书链。最后一个参数是已经验证过的文件列表。
VerifierEntry对象创建成功后,会返回JarFileInputStream流对象。然后回到PackageParser.java的loadCartificates方法中,会继续走到readFullyIgnoringContents方法:

public static long readFullyIgnoringContents(InputStream in) throws IOException {byte[] buffer = sBuffer.getAndSet(null);if (buffer == null) {buffer = new byte[4096];}int n = 0;int count = 0;while ((n = in.read(buffer, 0, buffer.length)) != -1) {count += n;}sBuffer.set(buffer);return count;}

这里面就是正常的读取 read;但是我们从第一点中知道 , 这个 InputStream 对象实际上是 JarInputStream 对象 , 所以这里面 read 执行的方法实际上是在 JarInputStream 中重写的 read 方法。

@Overridepublic int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {if (done) {return -1;}if (count > 0) {int r = super.read(buffer, byteOffset, byteCount);if (r != -1) {int size = r;if (count < size) {size = (int) count;}entry.write(buffer, byteOffset, size);count -= size;} else {count = 0;}if (count == 0) {done = true;entry.verify();  // 校验MANIFEST.MF文件中数据和APK中每个文件的摘要值}return r;} else {done = true;entry.verify();return -1;}}

verify方法:

void verify() {byte[] d = digest.digest();if (!MessageDigest.isEqual(d, Base64.decode(hash))) {throw invalidDigest(JarFile.MANIFEST_NAME, name, name);}verifiedEntries.put(name, certChains);}

OK,通过这些方法的执行,便可以校验了APK中各文件摘要值同MANIFEST.MF中对应的值是否相同 。

最后回到PackageParser.java中的collectCertificates方法,下面这个方法是上文中所有方法的初始位置:

public void collectCertificates(Package pkg, int flags) throws PackageParserException {pkg.mCertificates = null;pkg.mSignatures = null;pkg.mSigningKeys = null;collectCertificates(pkg, new File(pkg.baseCodePath), flags);if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {for (String splitCodePath : pkg.splitCodePaths) {collectCertificates(pkg, new File(splitCodePath), flags);}}}

执行完了重载的collectCertificates方法后,会去判断是否是一次安装,如果不是第一次安装,会再次执行 collectCertificates方法,如果签名不一致,会抛出异常:

private static void collectCertificates(Package pkg, File apkFile, int flags)throws PackageParserException {...for (ZipEntry entry : toVerify) {final Certificate[][] entryCerts = loadCertificates(jarFile, entry);if (ArrayUtils.isEmpty(entryCerts)) {throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,"Package " + apkPath + " has no certificates at entry "+ entry.getName());}final Signature[] entrySignatures = convertToSignatures(entryCerts);if (pkg.mCertificates == null) {pkg.mCertificates = entryCerts;pkg.mSignatures = entrySignatures;pkg.mSigningKeys = new ArraySet<PublicKey>();for (int i=0; i < entryCerts.length; i++) {pkg.mSigningKeys.add(entryCerts[i][0].getPublicKey());}} else {if (!Signature.areExactMatch(pkg.mSignatures, entrySignatures)) {throw new PackageParserException(INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES, "Package " + apkPath+ " has mismatched certificates at entry "+ entry.getName());}}}} catch (GeneralSecurityException e) {throw new PackageParserException(INSTALL_PARSE_FAILED_CERTIFICATE_ENCODING,"Failed to collect certificates from " + apkPath, e);} catch (IOException | RuntimeException e) {throw new PackageParserException(INSTALL_PARSE_FAILED_NO_CERTIFICATES,"Failed to collect certificates from " + apkPath, e);} finally {closeQuietly(jarFile);}}

总结一下,在APK签名的时候,首先会遍历APK所有的文件,生成对应的摘要数据写入到META-INF文件夹的MENIFEST.MF文件中,接下来会将MENIFEST.MF文件的摘要值和它里面每个数据块的值重新做一次摘要算法,然后写入到.SF文件中.最后是通过秘钥库文件对.SF文件进行加密,然后将公钥数据,签名数据等信息写入到.RSA文件中.如上便是android下对APK的签名机制.

而在android中对APK的签名校验则顺序相反,先是解开.RSA文件,提取公钥和签名数据,找到对应的.SF文件进行对比.然后进行MENIFEST.MF和.SF文件的对比,最后则是对apk中各个文件的摘要数据同MANIFEST.MF文件进行校验.不过在最后还会校验在覆盖安装的时候,会再次执行校验流程.

Android签名机制及PMS中校验签名相关推荐

  1. Android签名机制及PMS中校验流程(雷惊风)

    @Android签名机制及PMS中校验流程(雷惊风) 网上看到一篇比较好的关于Android签名的文章,但是文章链接不安全,不知道哪天会不会找不到了,而且需要关注才能查看完整版,所以在这里记录一下,原 ...

  2. Android签名机制:生成keystore、签名、查看签名信息

    2019独角兽企业重金招聘Python工程师标准>>> Android独有的安全机制,除了权限机制外,另外一个就是签名机制了.签名机制主要用在以下两个主要场合起到其作用:升级App和 ...

  3. 【android系统】根据PMS中的屏幕锁WakeLock,做了一个限制儿童观看视频时长的方案

    背景 随着时代的发展.科技的进步,电子产品的拥有日趋低龄化.电子产品的不当使用可能造成很多问题,过度使用容易上瘾,影响孩子的社交生活.据资料显示看电视绝不仅仅只会影响孩子的视力,更严重的会影响到孩子的 ...

  4. 【Android】多渠道打包与签名机制

    [Android]多渠道打包与签名机制 多渠道打包 我们在发布APP时,往往需要生成多个渠道包,以上传到不同的应用市场. 而每个渠道包中,都可以包含各自的渠道信息,当APP和后台交互或进行数据上报时, ...

  5. 一篇文章看明白 Android v1 v2 签名机制

    Android - v1 & v2 签名机制 相关系列 一篇文章看明白 Android 系统启动时都干了什么 一篇文章了解相见恨晚的 Android Binder 进程间通讯机制 一篇文章看明 ...

  6. Android学习笔记——Android 签名机制详解

    Android 签名机制详解 近期由于工作需要在学习 Android 的签名机制,因为没有现成资料,只能通过开发者文档和阅读博客的方式对 Android 签名机制进行大致了解.过程中查阅到的资料相对零 ...

  7. ios keychain 不被清理_iOS签名机制和说明文件【ios企业签名吧】

    IOS签名机制和配置文件.iOS签名机制的作用:保证安装在手机上的应用程序经苹果公式验证和许可.无论是真机调试还是发布App,开发人员都必须经过一些复杂的步骤.以下广州贝壳技术将详细说明. 贝壳科技( ...

  8. IOS逆向学习-签名机制

    签名机制 1. 加密解密 1.0 学习流程图 1.1 学前须知 1.1.1 发送消息流程 1.1.2 如何防止被窃听 1.1.3 如何加密解密 1.1.4 密码的类型 1.1.5 密码的配送问题 1. ...

  9. 【转】Thunderbird中配置签名

    原文网址:https://support.mozilla.org/zh-CN/kb/Thunderbird%E4%B8%AD%E9%85%8D%E7%BD%AE%E7%AD%BE%E5%90%8D & ...

最新文章

  1. 利用mem数组在MM32 MicroPython中实现COMP的功能
  2. java and asp.net
  3. Python技巧:不要在for与while循环后写else块
  4. 不使用sprintf函数使用共用体进行STM32单片机通讯解析
  5. 探索Julia(part4)--数组
  6. erp无线架设服务器,erp数据库架设在云服务器上
  7. daemon守护进程初识
  8. Com uma forca, com uma forca
  9. 凸优化第六章逼近与拟合 6.2最小范数问题
  10. 大写金额用计算机简单些,大写一到十怎么写 数字一到十的大写怎么写(开票用的大写)...
  11. 手把手教你:人脸识别考勤系统
  12. H3C交换机常用命令
  13. 2021最新文本综述:从浅层到深度学习(附PDF下载)
  14. SQL分组排序再取前N条记录
  15. Memcached缓存
  16. ssm:Unable to process Jar entry
  17. 2023校招C++开发oppo笔试
  18. POJ 1364 King 差分约束系统
  19. 分布式定时任务—xxl-job学习(四)——调度中心web页面端api调用源码分析
  20. 从客户端到服务器端,适配微信iOS OpenSDK中的Universal Links

热门文章

  1. 银行本票、汇票、支票的区别是什么
  2. 使用nginx的第三方模块rtmp搭建直播服务器
  3. swift问题集--未完待续
  4. USB(FS、HS、LS)
  5. android marquee,Android TextView Marquee的应用实例详解
  6. 基于MATLAB的人脸识别算法的研究
  7. 如何安装epel的源
  8. 21世纪的四大化学难题
  9. linux内核中IP数据包的输入与输出
  10. c++的HTTP请求返回网页数据