Android V1及V2簽名原理簡(jiǎn)析

Android為了保證系統(tǒng)及應(yīng)用的安全性饶号,在安裝APK的時(shí)候需要校驗(yàn)包的完整性,同時(shí),對(duì)于覆蓋安裝的場(chǎng)景還要校驗(yàn)新舊是否匹配,這兩者都是通過Android簽名機(jī)制來進(jìn)行保證的矩动,本文就簡(jiǎn)單看下Android的簽名與校驗(yàn)原理,分一下幾個(gè)部分分析下:

  • APK簽名是什么
  • APK簽名如何保證APK信息完整性
  • 如何為APK簽名
  • APK簽名怎么校驗(yàn)

Android的APK簽名是什么

簽名是摘要與非對(duì)稱密鑰加密相相結(jié)合的產(chǎn)物释漆,摘要就像內(nèi)容的一個(gè)指紋信息悲没,一旦內(nèi)容被篡改,摘要就會(huì)改變男图,簽名是摘要的加密結(jié)果示姿,摘要改變,簽名也會(huì)失效逊笆。Android APK簽名也是這個(gè)道理栈戳,如果APK簽名跟內(nèi)容對(duì)應(yīng)不起來,Android系統(tǒng)就認(rèn)為APK內(nèi)容被篡改了难裆,從而拒絕安裝子檀,以保證系統(tǒng)的安全性。目前Android有三種簽名V1乃戈、V2(N)命锄、V3(P),本文只看前兩種V1跟V2偏化,對(duì)于V3的輪密先不考慮。先看下只有V1簽名后APK的樣式:

image.png

再看下只有V2簽名的APK包樣式:

image.png

同時(shí)具有V1 V2簽名:

image.png

可以看到镐侯,如果只有V2簽名侦讨,那么APK包內(nèi)容幾乎是沒有改動(dòng)的,META_INF中不會(huì)有新增文件苟翻,按Google官方文檔:在使用v2簽名方案進(jìn)行簽名時(shí)韵卤,會(huì)在APK文件中插入一個(gè)APK簽名分塊,該分塊位于zip中央目錄部分之前并緊鄰該部分崇猫。在APK簽名分塊內(nèi)沈条,簽名和簽名者身份信息會(huì)存儲(chǔ)在APK簽名方案v2分塊中,保證整個(gè)APK文件不可修改诅炉,如下圖:

image.png

而V1簽名是通過META-INF中的三個(gè)文件保證簽名及信息的完整性:

image.png

APK簽名如何保證APK信息完整性

V1簽名是如何保證信息的完整性呢蜡歹?V1簽名主要包含三部分內(nèi)容,如果狹義上說簽名跟公鑰的話涕烧,僅僅在.rsa文件中月而,V1簽名的三個(gè)文件其實(shí)是一套機(jī)制,不能單單拿一個(gè)來說事议纯,

MANIFEST.MF:摘要文件父款,存儲(chǔ)文件名與文件SHA1摘要(Base64格式)鍵值對(duì),格式如下,其主要作用是保證每個(gè)文件的完整性

摘要

如果對(duì)APK中的資源文件進(jìn)行了替換憨攒,那么該資源的摘要必定發(fā)生改變世杀,如果沒有修改MANIFEST.MF中的信息,那么在安裝時(shí)候V1校驗(yàn)就會(huì)失敗肝集,無法安裝瞻坝,不過如果篡改文件的同時(shí),也修改其MANIFEST.MF中的摘要值包晰,那么MANIFEST.MF校驗(yàn)就可以繞過湿镀。

CERT.SF:二次摘要文件,存儲(chǔ)文件名與MANIFEST.MF摘要條目的SHA1摘要(Base64格式)鍵值對(duì)伐憾,格式如下

image.png

CERT.SF個(gè)人覺得有點(diǎn)像冗余勉痴,更像對(duì)文件完整性的二次保證,同繞過MANIFEST.MF一樣树肃,.SF校驗(yàn)也很容易被繞過蒸矛。

CERT.RSA 證書(公鑰)及簽名文件,存儲(chǔ)keystore的公鑰胸嘴、發(fā)行信息雏掠、以及對(duì)CERT.SF文件摘要的簽名信息(利用keystore的私鑰進(jìn)行加密過)

CERT.RSA與CERT.SF是相互對(duì)應(yīng)的,兩者名字前綴必須一致劣像,不知道算不算一個(gè)無聊的標(biāo)準(zhǔn)乡话。看下CERT.RSA文件內(nèi)容:

image.png

CERT.RSA文件里面存儲(chǔ)了證書公鑰耳奕、過期日期绑青、發(fā)行人、加密算法等信息屋群,根據(jù)公鑰及加密算法闸婴,Android系統(tǒng)就能計(jì)算出CERT.SF的摘要信息,其嚴(yán)格的格式如下:

X.509證書格式

從CERT.RSA中芍躏,我們能獲的證書的指紋信息邪乍,在微信分享、第三方SDK申請(qǐng)的時(shí)候經(jīng)常用到对竣,其實(shí)就是公鑰+開發(fā)者信息的一個(gè)簽名:

image.png

除了CERT.RSA文件庇楞,其余兩個(gè)簽名文件其實(shí)跟keystore沒什么關(guān)系,主要是文件自身的摘要及二次摘要柏肪,用不同的keystore進(jìn)行簽名姐刁,生成的MANIFEST.MF與CERT.SF都是一樣的,不同的只有CERT.RSA簽名文件烦味。也就是說前兩者主要保證各個(gè)文件的完整性聂使,CERT.RSA從整體上保證APK的來源及完整性壁拉,不過META_INF中的文件不在校驗(yàn)范圍中,這也是V1的一個(gè)缺點(diǎn)柏靶。V2簽名又是如何保證信息的完整性呢弃理?

V2簽名塊如何保證APK的完整性

前面說過V1簽名中文件的完整性很容易被繞過,可以理解單個(gè)文件完整性校驗(yàn)的意義并不是很大屎蜓,安裝的時(shí)候反而耗時(shí)痘昌,不如采用更加簡(jiǎn)單的便捷的校驗(yàn)方式。V2簽名就不針對(duì)單個(gè)文件校驗(yàn)了炬转,而是針對(duì)APK進(jìn)行校驗(yàn)辆苔,將APK分成1M的塊,對(duì)每個(gè)塊計(jì)算值摘要扼劈,之后針對(duì)所有摘要進(jìn)行摘要驻啤,再利用摘要進(jìn)行簽名。

image.png

也就是說荐吵,V2摘要簽名分兩級(jí)骑冗,第一級(jí)是對(duì)APK文件的1、3 先煎、4 部分進(jìn)行摘要贼涩,第二級(jí)是對(duì)第一級(jí)的摘要集合進(jìn)行摘要,然后利用秘鑰進(jìn)行簽名薯蝎。安裝的時(shí)候遥倦,塊摘要可以并行處理,這樣可以提高校驗(yàn)速度占锯。

簡(jiǎn)單的APK簽名流程(簽名原理)

APK是先摘要谊迄,再簽名,先看下摘要的定義:Message Digest:摘要是對(duì)消息數(shù)據(jù)執(zhí)行一個(gè)單向Hash烟央,從而生成一個(gè)固定長(zhǎng)度的Hash值,這個(gè)值就是消息摘要歪脏,至于常聽到的MD5疑俭、SHA1都是摘要算法的一種。理論上說婿失,摘要一定會(huì)有碰撞钞艇,但只要保證有限長(zhǎng)度內(nèi)碰撞率很低就可以,這樣就能利用摘要來保證消息的完整性豪硅,只要消息被篡改哩照,摘要一定會(huì)發(fā)生改變。但是懒浮,如果消息跟摘要同時(shí)被修改飘弧,那就無從得知了识藤。

而數(shù)字簽名是什么呢(公鑰數(shù)字簽名),利用非對(duì)稱加密技術(shù)次伶,通過私鑰對(duì)摘要進(jìn)行加密痴昧,產(chǎn)生一個(gè)字符串,這個(gè)字符串+公鑰證書就可以看做消息的數(shù)字簽名冠王,如RSA就是常用的非對(duì)稱加密算法赶撰。在沒有私鑰的前提下,非對(duì)稱加密算法能確保別人無法偽造簽名柱彻,因此數(shù)字簽名也是對(duì)發(fā)送者信息真實(shí)性的一個(gè)有效證明豪娜。不過由于Android的keystore證書是自簽名的,沒有第三方權(quán)威機(jī)構(gòu)認(rèn)證哟楷,用戶可以自行生成keystore瘤载,Android簽名方案無法保證APK不被二次簽名。

知道了摘要跟簽名的概念后吓蘑,再來看看Android的簽名文件怎么來的惕虑?如何影響原來APK包?通過sdk中的apksign來對(duì)一個(gè)APK進(jìn)行簽名的命令如下:

 ./apksigner sign  --ks   keystore.jks  --ks-key-alias keystore  --ks-pass pass:XXX  --key-pass pass:XXX  --out output.apk input.apk

其主要實(shí)現(xiàn)在 android/platform/tools/apksig 文件夾中磨镶,主體是ApkSigner.java的sign函數(shù)溃蔫,函數(shù)比較長(zhǎng),分幾步分析

private void sign(
        DataSource inputApk,
        DataSink outputApkOut,
        DataSource outputApkIn)
                throws IOException, ApkFormatException, NoSuchAlgorithmException,
                        InvalidKeyException, SignatureException {
    // Step 1. Find input APK's main ZIP sections
    ApkUtils.ZipSections inputZipSections;
    <!--根據(jù)zip包的結(jié)構(gòu)琳猫,找到APK中包內(nèi)容Object-->
    try {
        inputZipSections = ApkUtils.findZipSections(inputApk);
    ...

先來看這一步伟叛,ApkUtils.findZipSections,這個(gè)函數(shù)主要是解析APK文件脐嫂,獲得ZIP格式的一些簡(jiǎn)單信息统刮,并返回一個(gè)ZipSections,

 public static ZipSections findZipSections(DataSource apk)
            throws IOException, ZipFormatException {
        Pair<ByteBuffer, Long> eocdAndOffsetInFile =
                ZipUtils.findZipEndOfCentralDirectoryRecord(apk);
        ByteBuffer eocdBuf = eocdAndOffsetInFile.getFirst();
        long eocdOffset = eocdAndOffsetInFile.getSecond();
        eocdBuf.order(ByteOrder.LITTLE_ENDIAN);
        long cdStartOffset = ZipUtils.getZipEocdCentralDirectoryOffset(eocdBuf);
        ...
        long cdSizeBytes = ZipUtils.getZipEocdCentralDirectorySizeBytes(eocdBuf);
        long cdEndOffset = cdStartOffset + cdSizeBytes;
        int cdRecordCount = ZipUtils.getZipEocdCentralDirectoryTotalRecordCount(eocdBuf);
        return new ZipSections(
                cdStartOffset,
                cdSizeBytes,
                cdRecordCount,
                eocdOffset,
                eocdBuf);
    }

ZipSections包含了ZIP文件格式的一些信息账千,比如中央目錄信息侥蒙、中央目錄結(jié)尾信息等,對(duì)比到zip文件格式如下:

image.png

獲取到 ZipSections之后匀奏,就可以進(jìn)一步解析APK這個(gè)ZIP包鞭衩,繼續(xù)走后面的簽名流程,

    long inputApkSigningBlockOffset = -1;
    DataSource inputApkSigningBlock = null;
    <!--檢查V2簽名是否存在-->
    try {
        Pair<DataSource, Long> apkSigningBlockAndOffset =
                V2SchemeVerifier.findApkSigningBlock(inputApk, inputZipSections);
        inputApkSigningBlock = apkSigningBlockAndOffset.getFirst();
        inputApkSigningBlockOffset = apkSigningBlockAndOffset.getSecond();
    } catch (V2SchemeVerifier.SignatureNotFoundException e) {
    <!--V2簽名不存在也沒什么問題娃善,非必須-->
}
 <!--獲取V2簽名以外的信息區(qū)域-->
 DataSource inputApkLfhSection =
            inputApk.slice(
                    0,
                    (inputApkSigningBlockOffset != -1)
                            ? inputApkSigningBlockOffset
                            : inputZipSections.getZipCentralDirectoryOffset());

可以看到先進(jìn)行了一個(gè)V2簽名的檢驗(yàn)论衍,這里是用來簽名,為什么先檢驗(yàn)了一次聚磺?第一次簽名的時(shí)候會(huì)直接走這個(gè)異常邏輯分支坯台,重復(fù)簽名的時(shí)候才能獲到取之前的V2簽名,懷疑這里獲取V2簽名的目的應(yīng)該是為了排除V2簽名瘫寝,并獲取V2簽名以外的數(shù)據(jù)塊蜒蕾,因?yàn)楹灻旧聿荒鼙凰闳氲胶灻谐砭妫髸?huì)解析中央目錄區(qū),構(gòu)建一個(gè)DefaultApkSignerEngine用于簽名

      <!--解析中央目錄區(qū)滥搭,目的是為了解析AndroidManifest-->
    // Step 2. Parse the input APK's ZIP Central Directory
    ByteBuffer inputCd = getZipCentralDirectory(inputApk, inputZipSections);
    List<CentralDirectoryRecord> inputCdRecords =
            parseZipCentralDirectory(inputCd, inputZipSections);

    // Step 3. Obtain a signer engine instance
    ApkSignerEngine signerEngine;
    if (mSignerEngine != null) {
        signerEngine = mSignerEngine;
    } else {
        // Construct a signer engine from the provided parameters
        ...
        List<DefaultApkSignerEngine.SignerConfig> engineSignerConfigs =
                new ArrayList<>(mSignerConfigs.size());
        <!--一般就一個(gè)-->
        for (SignerConfig signerConfig : mSignerConfigs) {
            engineSignerConfigs.add(
                    new DefaultApkSignerEngine.SignerConfig.Builder(
                            signerConfig.getName(),
                            signerConfig.getPrivateKey(),
                            signerConfig.getCertificates())
                            .build());
        }
        <!--默認(rèn)V1 V2都啟用-->
        DefaultApkSignerEngine.Builder signerEngineBuilder =
                new DefaultApkSignerEngine.Builder(engineSignerConfigs, minSdkVersion)
                        .setV1SigningEnabled(mV1SigningEnabled)
                        .setV2SigningEnabled(mV2SigningEnabled)
                        .setOtherSignersSignaturesPreserved(mOtherSignersSignaturesPreserved);
        if (mCreatedBy != null) {
            signerEngineBuilder.setCreatedBy(mCreatedBy);
        }
        signerEngine = signerEngineBuilder.build();
    }

先解析中央目錄區(qū)酸纲,獲取AndroidManifest文件,獲取minSdkVersion(影響簽名算法)瑟匆,并構(gòu)建DefaultApkSignerEngine闽坡,默認(rèn)情況下V1 V2簽名都是打開的。

    // Step 4. Provide the signer engine with the input APK's APK Signing Block (if any)
    <!--忽略這一步-->
    if (inputApkSigningBlock != null) {
        signerEngine.inputApkSigningBlock(inputApkSigningBlock);
    }

    // Step 5. Iterate over input APK's entries and output the Local File Header + data of those
    // entries which need to be output. Entries are iterated in the order in which their Local
    // File Header records are stored in the file. This is to achieve better data locality in
    // case Central Directory entries are in the wrong order.
    List<CentralDirectoryRecord> inputCdRecordsSortedByLfhOffset =
            new ArrayList<>(inputCdRecords);
    Collections.sort(
            inputCdRecordsSortedByLfhOffset,
            CentralDirectoryRecord.BY_LOCAL_FILE_HEADER_OFFSET_COMPARATOR);
    int lastModifiedDateForNewEntries = -1;
    int lastModifiedTimeForNewEntries = -1;
    long inputOffset = 0;
    long outputOffset = 0;
    Map<String, CentralDirectoryRecord> outputCdRecordsByName =
            new HashMap<>(inputCdRecords.size());
    ...

    // Step 6. Sort output APK's Central Directory records in the order in which they should
    // appear in the output
    List<CentralDirectoryRecord> outputCdRecords = new ArrayList<>(inputCdRecords.size() + 10);
    for (CentralDirectoryRecord inputCdRecord : inputCdRecords) {
        String entryName = inputCdRecord.getName();
        CentralDirectoryRecord outputCdRecord = outputCdRecordsByName.get(entryName);
        if (outputCdRecord != null) {
            outputCdRecords.add(outputCdRecord);
        }
    }

第五步與第六步的主要工作是:apk的預(yù)處理愁溜,包括目錄的一些排序之類的工作疾嗅,應(yīng)該是為了更高效處理簽名,預(yù)處理結(jié)束后冕象,就開始簽名流程代承,首先做的是V1簽名(默認(rèn)存在,除非主動(dòng)關(guān)閉):

    // Step 7. Generate and output JAR signatures, if necessary. This may output more Local File
    // Header + data entries and add to the list of output Central Directory records.
    ApkSignerEngine.OutputJarSignatureRequest outputJarSignatureRequest =
            signerEngine.outputJarEntries();
    if (outputJarSignatureRequest != null) {
        if (lastModifiedDateForNewEntries == -1) {
            lastModifiedDateForNewEntries = 0x3a21; // Jan 1 2009 (DOS)
            lastModifiedTimeForNewEntries = 0;
        }
        for (ApkSignerEngine.OutputJarSignatureRequest.JarEntry entry :
                outputJarSignatureRequest.getAdditionalJarEntries()) {
            String entryName = entry.getName();
            byte[] uncompressedData = entry.getData();
            ZipUtils.DeflateResult deflateResult =
                    ZipUtils.deflate(ByteBuffer.wrap(uncompressedData));
            byte[] compressedData = deflateResult.output;
            long uncompressedDataCrc32 = deflateResult.inputCrc32;

            ApkSignerEngine.InspectJarEntryRequest inspectEntryRequest =
                    signerEngine.outputJarEntry(entryName);
            if (inspectEntryRequest != null) {
                inspectEntryRequest.getDataSink().consume(
                        uncompressedData, 0, uncompressedData.length);
                inspectEntryRequest.done();
            }

            long localFileHeaderOffset = outputOffset;
            outputOffset +=
                    LocalFileRecord.outputRecordWithDeflateCompressedData(
                            entryName,
                            lastModifiedTimeForNewEntries,
                            lastModifiedDateForNewEntries,
                            compressedData,
                            uncompressedDataCrc32,
                            uncompressedData.length,
                            outputApkOut);


            outputCdRecords.add(
                    CentralDirectoryRecord.createWithDeflateCompressedData(
                            entryName,
                            lastModifiedTimeForNewEntries,
                            lastModifiedDateForNewEntries,
                            uncompressedDataCrc32,
                            compressedData.length,
                            uncompressedData.length,
                            localFileHeaderOffset));
        }
        outputJarSignatureRequest.done();
    }

    // Step 8. Construct output ZIP Central Directory in an in-memory buffer
    long outputCentralDirSizeBytes = 0;
    for (CentralDirectoryRecord record : outputCdRecords) {
        outputCentralDirSizeBytes += record.getSize();
    }
    if (outputCentralDirSizeBytes > Integer.MAX_VALUE) {
        throw new IOException(
                "Output ZIP Central Directory too large: " + outputCentralDirSizeBytes
                        + " bytes");
    }
    ByteBuffer outputCentralDir = ByteBuffer.allocate((int) outputCentralDirSizeBytes);
    for (CentralDirectoryRecord record : outputCdRecords) {
        record.copyTo(outputCentralDir);
    }
    outputCentralDir.flip();
    DataSource outputCentralDirDataSource = new ByteBufferDataSource(outputCentralDir);
    long outputCentralDirStartOffset = outputOffset;
    int outputCentralDirRecordCount = outputCdRecords.size();

    // Step 9. Construct output ZIP End of Central Directory record in an in-memory buffer
    ByteBuffer outputEocd =
            EocdRecord.createWithModifiedCentralDirectoryInfo(
                    inputZipSections.getZipEndOfCentralDirectory(),
                    outputCentralDirRecordCount,
                    outputCentralDirDataSource.size(),
                    outputCentralDirStartOffset);

步驟7渐扮、8论悴、9都可以看做是V1簽名的處理邏輯,主要在V1SchemeSigner中處理墓律,其中包括創(chuàng)建META-INFO文件夾下的一些簽名文件膀估,更新中央目錄、更新中央目錄結(jié)尾等耻讽,流程不復(fù)雜察纯,不在贅述,簡(jiǎn)單流程就是:

image.png

這里特殊提一下重復(fù)簽名的問題:對(duì)一個(gè)已經(jīng)V1簽名的APK再次V1簽名不會(huì)有任何問題针肥,原理就是:再次簽名的時(shí)候饼记,會(huì)排除之前的簽名文件。

  public static boolean isJarEntryDigestNeededInManifest(String entryName) {
        // See https://docs.oracle.com/javase/8/docs/technotes/guides/jar/jar.html#Signed_JAR_File

        // Entries which represent directories sould not be listed in the manifest.
        if (entryName.endsWith("/")) {
            return false;
        }

        // Entries outside of META-INF must be listed in the manifest.
        if (!entryName.startsWith("META-INF/")) {
            return true;
        }
        // Entries in subdirectories of META-INF must be listed in the manifest.
        if (entryName.indexOf('/', "META-INF/".length()) != -1) {
            return true;
        }

        // Ignored file names (case-insensitive) in META-INF directory:
        //   MANIFEST.MF
        //   *.SF
        //   *.RSA
        //   *.DSA
        //   *.EC
        //   SIG-*
        String fileNameLowerCase =
                entryName.substring("META-INF/".length()).toLowerCase(Locale.US);
        if (("manifest.mf".equals(fileNameLowerCase))
                || (fileNameLowerCase.endsWith(".sf"))
                || (fileNameLowerCase.endsWith(".rsa"))
                || (fileNameLowerCase.endsWith(".dsa"))
                || (fileNameLowerCase.endsWith(".ec"))
                || (fileNameLowerCase.startsWith("sig-"))) {
            return false;
        }
        return true;
    }

可以看到目錄慰枕、META-INF文件夾下的文件具则、sf、rsa等結(jié)尾的文件都不會(huì)被V1簽名進(jìn)行處理具帮,所以這里不用擔(dān)心多次簽名的問題乡洼。接下來就是處理V2簽名。

    // Step 10. Generate and output APK Signature Scheme v2 signatures, if necessary. This may
    // insert an APK Signing Block just before the output's ZIP Central Directory
    ApkSignerEngine.OutputApkSigningBlockRequest outputApkSigingBlockRequest =
            signerEngine.outputZipSections(
                    outputApkIn,
                    outputCentralDirDataSource,
                    DataSources.asDataSource(outputEocd));
    if (outputApkSigingBlockRequest != null) {
        byte[] outputApkSigningBlock = outputApkSigingBlockRequest.getApkSigningBlock();
        outputApkOut.consume(outputApkSigningBlock, 0, outputApkSigningBlock.length);
        ZipUtils.setZipEocdCentralDirectoryOffset(
                outputEocd, outputCentralDirStartOffset + outputApkSigningBlock.length);
        outputApkSigingBlockRequest.done();
    }

    // Step 11. Output ZIP Central Directory and ZIP End of Central Directory
    outputCentralDirDataSource.feed(0, outputCentralDirDataSource.size(), outputApkOut);
    outputApkOut.consume(outputEocd);
    signerEngine.outputDone();
}

V2SchemeSigner處理V2簽名匕坯,邏輯比較清晰,直接對(duì)V1簽名過的APK進(jìn)行分塊摘要拔稳,再集合簽名葛峻,V2簽名不會(huì)改變之前V1簽名后的任何信息,簽名后巴比,在中央目錄前添加V2簽名塊术奖,并更新中央目錄結(jié)尾信息礁遵,因?yàn)閂2簽名后,中央目錄的偏移會(huì)再次改變:

image.png

APK簽名怎么校驗(yàn)

簽名校驗(yàn)的過程可以看做簽名的逆向采记,只不過覆蓋安裝可能還要校驗(yàn)公鑰及證書信息一致佣耐,否則覆蓋安裝會(huì)失敗。簽名校驗(yàn)的入口在PackageManagerService的install里唧龄,安裝官方文檔兼砖,7.0以上的手機(jī)優(yōu)先檢測(cè)V2簽名,如果V2簽名不存在既棺,再校驗(yàn)V1簽名讽挟,對(duì)于7.0以下的手機(jī),不存在V2簽名校驗(yàn)機(jī)制丸冕,只會(huì)校驗(yàn)V1耽梅,所以,如果你的App的miniSdkVersion<24(N)胖烛,那么你的簽名方式必須內(nèi)含V1簽名:

簽名校驗(yàn)流程

校驗(yàn)流程就是簽名的逆向眼姐,了解簽名流程即可,本文不求甚解佩番,有興趣自己去分析众旗,只是額外提下覆蓋安裝,覆蓋安裝除了檢驗(yàn)APK自己的完整性以外答捕,還要校驗(yàn)證書是否一致只有證書一致(同一個(gè)keystore簽名)逝钥,才有可能覆蓋升級(jí)。覆蓋安裝同全新安裝相比較多了幾個(gè)校驗(yàn)

  • 包名一致
  • 證書一致
  • versioncode不能降低

這里只關(guān)心證書部分:

    // Verify: if target already has an installer package, it must
    // be signed with the same cert as the caller.
    if (targetPackageSetting.installerPackageName != null) {
        PackageSetting setting = mSettings.mPackages.get(
                targetPackageSetting.installerPackageName);
        // If the currently set package isn't valid, then it's always
        // okay to change it.
        if (setting != null) {
            if (compareSignatures(callerSignature,
                    setting.signatures.mSignatures)
                    != PackageManager.SIGNATURE_MATCH) {
                throw new SecurityException(
                        "Caller does not have same cert as old installer package "
                        + targetPackageSetting.installerPackageName);
            }
        }
    }

V1拱镐、V2簽名下美團(tuán)多渠道打包的切入點(diǎn)

  • V1簽名:META_INFO文件夾下增加文件不會(huì)對(duì)校驗(yàn)有任何影響艘款,則是美團(tuán)V1多渠道打包方案的切入點(diǎn)
  • V2簽名:V2簽名塊中可以添加一些附屬信息,不會(huì)對(duì)簽名又任何影響沃琅,這是V2多渠道打包的切入點(diǎn)哗咆。

總結(jié)

  • V1簽名靠META_INFO文件夾下的簽名文件
  • V2簽名依靠中央目錄前的V2簽名快,ZIP的目錄結(jié)構(gòu)不會(huì)改變益眉,當(dāng)然結(jié)尾偏移要改晌柬。
  • V1 V2簽名可以同時(shí)存在(miniSdkVersion 7.0以下如果沒有V1簽名是不可以的)
  • 多去到打包的切入點(diǎn)原則:附加信息不影響簽名驗(yàn)證

作者:看書的小蝸牛

Android V1及V2簽名簽名原理簡(jiǎn)析

僅供參考,歡迎指正

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末郭脂,一起剝皮案震驚了整個(gè)濱河市年碘,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌展鸡,老刑警劉巖屿衅,帶你破解...
    沈念sama閱讀 222,627評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異莹弊,居然都是意外死亡涤久,警方通過查閱死者的電腦和手機(jī)涡尘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來响迂,“玉大人考抄,你說我怎么就攤上這事≌嵬” “怎么了川梅?”我有些...
    開封第一講書人閱讀 169,346評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)幕与。 經(jīng)常有香客問我挑势,道長(zhǎng),這世上最難降的妖魔是什么啦鸣? 我笑而不...
    開封第一講書人閱讀 60,097評(píng)論 1 300
  • 正文 為了忘掉前任潮饱,我火速辦了婚禮,結(jié)果婚禮上诫给,老公的妹妹穿的比我還像新娘香拉。我一直安慰自己,他們只是感情好中狂,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評(píng)論 6 398
  • 文/花漫 我一把揭開白布凫碌。 她就那樣靜靜地躺著,像睡著了一般胃榕。 火紅的嫁衣襯著肌膚如雪盛险。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,696評(píng)論 1 312
  • 那天勋又,我揣著相機(jī)與錄音苦掘,去河邊找鬼。 笑死楔壤,一個(gè)胖子當(dāng)著我的面吹牛鹤啡,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蹲嚣,決...
    沈念sama閱讀 41,165評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼递瑰,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了隙畜?” 一聲冷哼從身側(cè)響起抖部,我...
    開封第一講書人閱讀 40,108評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎议惰,沒想到半個(gè)月后慎颗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,646評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評(píng)論 3 342
  • 正文 我和宋清朗相戀三年哗总,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倍试。...
    茶點(diǎn)故事閱讀 40,861評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡讯屈,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出县习,到底是詐尸還是另有隱情涮母,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布躁愿,位于F島的核電站叛本,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏彤钟。R本人自食惡果不足惜来候,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望逸雹。 院中可真熱鬧营搅,春花似錦、人聲如沸梆砸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽帖世。三九已至休蟹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間日矫,已是汗流浹背赂弓。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留搬男,地道東北人拣展。 一個(gè)月前我還...
    沈念sama閱讀 49,287評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像缔逛,于是被迫代替她去往敵國(guó)和親备埃。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評(píng)論 2 361