Complete validity of verify files in Android running
I. summary because the previous project has the dynamic hotfix function, a new dex file will be downloaded from the server during the restoration process to replace the old dex file, therefore, file identity verification is involved. the interface usually sends an MD5 value, but only one MD5 value can be used for integrity verification, and the validity of the file cannot be determined. If attackers simulate the interface to send a correct MD5 value, you can still Replace the file. therefore, the validity verification is performed based on the signature after the MD5 integrity is verified. II. implementation
1. File integrity verification
Here, we will not repeat the MD5 string. since the integrity of the file needs to be verified, it involves obtaining the MD5 Digest of the file. Here, MessageDigest in JDK is used to read the binary stream of the file, update the MD5 Digest of the file stream to obtain the MD5 Digest of the entire file. after obtaining the file MD5, the comparison with the interface is OK. therefore, it is very easy to verify file integrity.
/** * get file md5 * @param file * @return * @throws NoSuchAlgorithmException * @throws IOException */ private static String getFileMD5(File file) throws NoSuchAlgorithmException, IOException { if (!file.isFile()) { return null; } MessageDigest digest; FileInputStream in; byte buffer[] = new byte[1024]; int len; digest = MessageDigest.getInstance(MD5); in = new FileInputStream(file); while ((len = in.read(buffer, 0, 1024)) != -1) { digest.update(buffer, 0, len); } in.close(); BigInteger bigInt = new BigInteger(1, digest.digest()); return bigInt.toString(16); }
2. Validity verification of documents
The validity of a file is much more complex than the integrity verification. Here, the validity is based on the signature, so at least we need to know the signature process briefly, after the signature, what changes have occurred to the file and how to obtain the file signature information.
1) Post-signature output
The process of generating the signature file and signature is not detailed here. It mainly analyzes some situations after the signature. decompress the signature file can be seen after the signature generated a META-INF folder, which is generally three files used to store all the file verification and signature information.
MANIFEST. MF: It can be viewed in plaintext and BASE64 hash is used for all files.
ANDROIDK. SF: It can be viewed in plaintext, and the first three lines of all files are taken with BASE64 hash values.
ANDROIDK. RSA: The first two files only make a hash digest of the file and do not have information such as the signature public key. The RSA file contains the information we need, including the developer information, the developer's public key and the ciphertext encrypted by the CA Based on the digest information of the first two files. RSA files cannot be viewed in plain text. Here, you can use the openssl command to output the file information. openssl pkcs7-inform DER-in ANDROIDK. RSA-noout-print_certs-text. From the data structure of this file, this chapter uses the public key to obtain the public key of the file's own certificate information, then, compare the app's signature public key to determine the validity of the file.
2) obtain the app's own signature
Through the above introduction, we can understand that all files with signatures can obtain an RSA public key based on the RSA algorithm. Here, we verify the validity of this public key by verifying the public key, the signature information of the app can be obtained through PackageInfo. After obtaining the signature information, it is okay to extract the RSA public key part through String Conversion and interception.
/** * get local app rsa public key * @param ctx * @return * @throws IOException * @throws PackageManager.NameNotFoundException * @throws CertificateException */ private static String getLocalSignature(Context ctx) throws IOException, PackageManager.NameNotFoundException, CertificateException { String signCode = null; //get signature info depends on package name PackageInfo packageInfo = ctx.getPackageManager().getPackageInfo( ctx.getPackageName(), PackageManager.GET_SIGNATURES); Signature[] signs = packageInfo.signatures; Signature sign = signs[0]; CertificateFactory certFactory = CertificateFactory .getInstance(X.509); X509Certificate cert = (X509Certificate) certFactory .generateCertificate(new ByteArrayInputStream(sign.toByteArray())); String pubKey = cert.getPublicKey().toString(); String ss = subPublicSignature(pubKey); ss = ss.replace(,, ); ss = ss.toLowerCase(); int aa = ss.indexOf(modulus); int bb = ss.indexOf(publicexponent); signCode = ss.substring(aa + 8, bb); return signCode; }
3) obtain the external file Signature
To obtain the external file signature, you can refer to the Android internal verification apk file process. The PackageParser class in the Android source code will verify the validity of the apk file before installing the apk, unfortunately, this class is marked as hide, so we cannot use it directly. there are only two methods left. One is to use the PackageParser method through reflection, and the other is to verify the implementation of this Part in the source code and then compile it for implementation.
/** * Package archive parsing * * {@hide} */public class PackageParser { //source/frameworks/base/core/java/android/content/pm}
Reflection is not recommended in current use cases. First, reflection is used to reduce efficiency, and second, verification is not dependent on other Android components, but on JarFile in JDK. therefore, we can compare the source code implementation by ourselves. Here we will not analyze the process of using reflection verification. We will directly go to the source code.
The following code snippet is displayed in the collectCertificates method of the PackageParser class. first, load the signed apk, jar, or zip file to JarFile according to the file path (JarFile is inherited from ZipFile ), obtain a file in the file content (this part of the code block is the obtained manifest file), and then obtain the certificate information of the file. as long as you can get the certificate information, it is a small case to get the public key.
public boolean collectCertificates(Package pkg, int flags) { //................. JarFile jarFile = new JarFile(mArchiveSourcePath); //................. JarEntry jarEntry = jarFile.getJarEntry(ANDROID_MANIFEST_FILENAME); //................. certs = loadCertificates(jarFile, jarEntry, readBuffer); pkg.mSignatures = null; //................. }
This loadCertificates method needs to be specifically mentioned, because at first I wrote the implementation after reading the source code. Every time I read this method, I paid attention to comments, the process of reading the file stream is skipped, directly through JarEntry. getCertificates obtains the certificate. as a result, the certificate cannot be obtained for several files with different signatures. After you review the source code, you can find that the JarEntry must be used to read the file stream in the comment to receive the certificate information ...... if you do not die, you will not die. after obtaining the certificate, it will be the same as the steps in the previous 2). Directly get the public key, and then intercept the string to intercept the RSA public key, and finally follow 2) the result comparison in can be used for validity verification.
private static Certificate[] loadCertificates(JarFile jarFile, JarEntry je, byte[] readBuffer) throws IOException { // We must read the stream for the JarEntry to retrieve // its certificates. InputStream is = new BufferedInputStream(jarFile.getInputStream(je)); while (is.read(readBuffer, 0, readBuffer.length) != -1) { // not using } is.close(); return je != null ? je.getCertificates() : null; }