Search code examples
javaandroidxmlcertificatesignature

Android Certificate Signature & App name


I'm using Java (J2SE) to create a desktop application. It has a function that check if two unknown .apk files (for ex app1.apk and app2.apk) are same application or not. My idea is comparing the fingerprint (in file META-INF/CERT.RSA) and names of the two apps.

  • By using keytool (cmd: keytool -printcert -file .\META-INF\CERT.RSA), i can get meta information of signature inside the CERT.RSA file. However I cannot get those information by using java source code.
  • I also try using openssl to get the information, again I cannot have those information inside my source code to compare fingerprint of the two android apps.
  • By using normal Java, I have no idea how to extract the fingerprint in file CERT.RSA.

In addition, I also want to get the android app name which is defined in the file strings.xml. In particular, I read the AndroidManifest.xml first, in tag application I get the value of attribute android:label="@string/app_name". Then i get the value of attribute app_name in file strings.xml. However the file is compiled in file resources.arsc which is an unreadable binary file.

Any solution for this idea or any other ideas for checking if two unknown .apk files are same app? I really appreciate your help.
Thanks


Solution

  • The package name serves as a unique identifier for the application.

    So basically 2 apk which refer to he same app will have same package name.

    Then maybe one of these apk is corrupted somehow eg :

    • signature file is missing-inconsistent
    • public key file is missing-inconsistent
    • computing of digest of files's entries in CERT.SF doesn't match (for one or several files listed)
    • CERT.SF file's digest (at the beginning of the file) doesnt't match with computing of its own digest

    If you want to be sure that one of this app has not been modified by 3rd part, you will have to do all these check above.

    Then to sum up the whole thing of determining if 2 apks are the same official app :

    • package name must be the same
    • public keys must be the same
    • computing of digests of file listed in CERT.SF must match with file entries' digest in the same CERT.SF file
    • computing of digest of file CERT.SF must match with CERT.SF's own digest

    CERT.SF are not necesserally the same if apk refer to the same app with different version (files added or deleted) but public key/private key pair must remained untouched to enable update of this app (unless it will result in "signature conflict" or "existing package already exist" errors)


    Using Java

    Check signature verification

    You can use JarSigner source code for this : http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/sun/security/tools/JarSigner.java#JarSigner.signatureRelated%28java.lang.String%29

    with external jar lib sun-security-tool : http://www.java2s.com/Code/Jar/a/Downloadandroidsunjarsignsupport11jar.htm

    Check public keys are the same

    Compare the extracted public key. Here is the method I made for extracting PKCS7 certificate and public key from it :

    /**
     * Retrieve public key from PKCS7 certificate
     * 
     * @param certPath
     * @return
     * @throws IOException
     * @throws InvalidKeySpecException
     * @throws NoSuchAlgorithmException
     */
    public static String getPublicKey(String certPath) throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
    
        File f = new File(certPath);
        FileInputStream is = new FileInputStream(f);
    
        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
    
        int nRead;
        byte[] data = new byte[16384];
    
        while ((nRead = is.read(data, 0, data.length)) != -1) {
            buffer.write(data, 0, nRead);
        }
    
        buffer.flush();
        PKCS7 test = new PKCS7(buffer.toByteArray());
        X509Certificate[] certs = test.getCertificates();
    
        for (int i = 0; i < certs.length; i++) {
            if (certs[i] != null && certs[i].getPublicKey() != null) {
                return new BASE64Encoder().encode(certs[i].getPublicKey().getEncoded());
            }
        }
        return "";
    }
    

    Read package-name from Android Manifest

    Here you have to parse Android binary XML for extracting these information. with this link, you have a lot of tools you can use (and snippet code) :

    How to parse the AndroidManifest.xml file inside an .apk package

    To get your app-name this is a bit different, this is an Android compiled resource. apktool can decode that kind of file but if you want to do this in java you'll have to decode it yourself. Here is apktool decoder https://github.com/iBotPeaches/Apktool/blob/master/brut.apktool/apktool-lib/src/main/java/brut/androlib/ApkDecoder.java. But if you want the app name it is not necessary in string.xml so this is rather something specific you want to do.


    I have made a Java tool that makes jar verification / public key comparison : https://github.com/bertrandmartel/apk-checker


    Using Bash

    With command line, you can test your output much faster and easier :

    Check signature verification

    jarsigner -verbose -verify <your_apk_file_name>.apk | grep "jar verified"
    

    Check public keys are the same

    unzip -p <your_apk_file_name1>.apk META-INF/CERT.RSA | keytool -printcert
    

    check package-name are the same

    aapt d xmltree <your_apk_file_name1>.apk AndroidManifest.xml | grep "package=" | sed -e "s/.*=//g" | sed -e "s/ .*//g"
    

    A gist for Bash scripts is available at https://gist.github.com/bertrandmartel/374564b950e8a577550b