Android簽名知識總結

1.簽名相關文件的簡介

這裏寫圖片描述

MANIFEST.MF

程序遍歷apk包中的所有文件,對非文件夾及未簽名文件的文件,逐個生成SHA1的數字簽名信息,再用Base64進行編碼,最終將這些(摘要)信息存於該文件中,該文件未涉及祕鑰信息的使用。(摘要信息)

CERT.SF

存放MANIFEST.MF通過私鑰及加密算法加密後的信息。(安裝時可通過公鑰解密後再與MANIFEST.MF對比信息一致性)。該文件涉及到簽名時私鑰的使用。

ERT.RSA

CERT.RSA文件中保存了公鑰、所採用的加密算法等信息。(數字證書Certificate)

  • .SF與.RSA文件的命名可修改,但是兩者需要保證一致
  • Android簽名機制不能阻止APK包被修改,但修改後的再簽名無法與原先的簽名保持一致。(擁有私鑰的情況除外)。
  • APK包加密的公鑰就打包在APK包內,且不同的私鑰對應不同的公鑰。換句話,不同的私鑰簽名的APK公鑰也必不相同。所以我們可以根據公鑰的對比,來判斷私鑰是否一致。

2.數字簽名信息及MD5與SHA1簡介

  1. 數字簽名(signature):在安卓簽名過程中,(簽名工具)會根據開發者提供的姓名地址及私鑰等信息生成一串數字簽名(字符串).
  2. MD5 (Message Digest 信息摘要)、SHA1(Secure Hash Algorithm安全哈希算法)爲HASH(哈希)算法或者稱之爲摘要算法(Digest Algorithm),即將無限制長度的字符串轉換成固定長度(MD5是16個字符16*8=128bits,SHA1是20個字節=160bits 20*8bits的長度)。得到的是摘要,輸入是無限制的字符。(常用MD5來進行加密處理)
  3. RSA文件中的簽名信息(例:QQ):
    這裏寫圖片描述
    (這裏的MD5、SHA1信息即數字證書信息(certificate),是通過相應算法計算後得出的。未經修改,則這邊的信息應該是與開發者簽名時所看到的是一致的。因此可以通過MD5或者是SHA1值來比較該apk包是否被惡意篡改)
  4. 補充兩句數字證書和數字簽名的一點區別:
    • The digital signature(數字簽名) for the JAR file that was generated with the signer’s private key
    • The certificate(數字證書) containing the signer’s public key, to be used by anyone wanting to verify the signed JAR file

3.使用指令獲取apk包簽名信息

  1. jarsigner -verify -verbose -certs your_apk_path.apk
  2. unzip -p suspect.apk META-INF/CERT.RSA | keytool -printcert 或 unzip -p suspect.apk META-INF/*.RSA | keytool -printcert
  3. unzip -p suspect.apk META-INF/CERT.RSA | keytool -printcert | grep MD5 或 unzip -p suspect.apk META-INF/*.RSA | keytool -printcert | grep MD5

4.使用代碼 獲取apk包簽名信息(Signature或者Certificate)

有兩種實現方法:  

  1. 可以使用Java自帶的API(主要用到的爲JarFile,JarEntry,Certificate)進行獲取,所以這種
  2. 還有一種方法是使用系統隱藏的API PackageParser,通過反射來使用對應的API.
     (但是由於安卓系統版本過多,並且不同廠商進行的修改很多,依賴反射隱藏API的方法並不能保證兼容性和通用性,因此推薦使用JAVA自帶API進行獲取):
    代碼:
import android.content.pm.Signature ;

import java.io.ByteArrayInputStream ;
import java.io.File ;
import java.io.IOException ;
import java.io.InputStream ;
import java.security.MessageDigest ;
import java.security.NoSuchAlgorithmException ;
import java.security.cert.Certificate ;
import java.security.cert.CertificateFactory ;
import java.security.cert.X509Certificate ;
import java.util.ArrayList ;
import java.util.List ;
import java.util.jar.JarEntry ;
import java.util.jar.JarFile ;

/**
* 獲取Signature串對應的MD5值
* Created by wiky on 2016/1/21.
*/

public class SignatureCheck {
    public static char [] hexChar = { '0', '1' , '2', '3', '4' , '5', '6', '7' ,
            '8' , '9', 'a', 'b' , 'c', 'd', 'e' , 'f'} ;

    public static void main (String[] args) {
        String path = args[0 ];
        File f = new File(path);
        if (!f.exists()) {
            System.out.println( "文件不存在" );
        }
        System.out.println( "文件存在,path=" +path);
        try {
            List<String> signature = getSignaturesFromApk(f);
            int size = signature.size() ;
            for ( int i = 0 ; i < size; i++) {
                System.out.println( "signature = " + signature.get(i));
                System.out.println( "signatureMD5:" + getMD5(signature.get(i))) ;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

//====================================獲取簽名(signature)信息=======================================
    /**
     * 從APK中讀取簽名
     * @param file apk文件
     * @return signatures列表
     * @throws IOException
     */
    private static List<String> getSignaturesFromApk(File file) throws
            IOException {
        List<String> signatures = new ArrayList<>();
        JarFile jarFile = new JarFile(file);
        try {
            JarEntry je = jarFile.getJarEntry("AndroidManifest.xml") ;
            byte[] readBuffer = new byte[8192 ];

            Certificate[] certs = loadCertificates(jarFile , je, readBuffer);
            if (certs != null) {
                for (Certificate c : certs) {
                    String sig = toCharsString(c.getEncoded()) ;

                    //這裏可以獲取更詳細的信息
                    Signature signature = new Signature(c.getEncoded());
                    CertificateFactory certFactory = CertificateFactory.getInstance( "X.509");
                    X509Certificate cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(signature.toByteArray())) ;
                    String pubKey = cert.getPublicKey().toString();
                    String signNumber = cert.getSerialNumber().toString();
                    System.out.println( "signName:" + cert.getSigAlgName());
                    System.out.println( "pubKey:" + pubKey);
                    System.out.println( "pubKeyMD5:" + getMD5(pubKey)) ;
                    System.out.println( "signNumber:" + signNumber);
                    System.out.println( "subjectDN:" + cert.getSubjectDN().toString());

                    signatures.add(sig);
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return signatures;
    }

    /**
     *   * 加載簽名
     *   * @param jarFile
     *   * @param je
     *   * @param readBuffer
     *   * @return
     */
    private static Certificate[] loadCertificates(JarFile jarFile , JarEntry je,
                                                  byte [] readBuffer) {
        try {
            InputStream is = jarFile.getInputStream(je);
            while (is.read(readBuffer , 0, readBuffer.length) != - 1) {
            }
            is.close();
            return je != null ? je.getCertificates() : null;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }


//====================================使用MD5對簽名(signature)進行加密=======================================
    /**
     * 用MD5算法加密字符串
     * @param val 待加密的字符串
     * @return 加密後的字符串
     * @throws NoSuchAlgorithmException
     */
    public static String getMD5(String val) throws NoSuchAlgorithmException {
        MessageDigest md5 = MessageDigest.getInstance("MD5" );//類型可選擇"MD5"、"SHA1"、"SHA-256"等
        md5.update(val.getBytes()) ;
        byte[] m = md5.digest() ;//加密
        return toHexString(m) ;
    }

    /**
     *   * 將簽名轉成轉成可見字符串
     *   * @param sigBytes
     *   * @return
     */
    private static String toCharsString( byte[] sigBytes) {
        final int N = sigBytes.length;
        final int N2 = N * 2;
        char[] text = new char[N2];
        for ( int j = 0 ; j < N; j++) {
            byte v = sigBytes[j];
            int d = (v >> 4) & 0xf ;
            text[j * 2] = (char ) (d >= 10 ? ( 'a' + d - 10 ) : ('0' + d)) ;
            d = v & 0xf;
            text[j * 2 + 1 ] = (char) (d >= 10 ? ('a' + d - 10) : ( '0' + d));
        }
        return new String(text);
    }

    /**
     * 轉換爲十六進制字符串形式
     */
    public static String toHexString( byte[] b) {
        StringBuilder sb = new StringBuilder(b.length * 2);
        for ( int i = 0 ; i < b.length ; i++) {
            sb.append(hexChar[(b[i] & 0xf0) >>> 4 ]);
            sb.append( hexChar[b[i] & 0x0f ]);
        }
        return sb.toString();
    }

}

權限:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

5.使用情景

某些時候需要獲取某個特定的apk(已安裝或者未安裝)的簽名信息,如程序自檢測,可信賴的第三方檢測(應用市場),系統限定安裝,判斷是否爲山寨軟件等

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章