国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Android APK文件的簽名V2查找、驗證

這篇具有很好參考價值的文章主要介紹了Android APK文件的簽名V2查找、驗證。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

??先看一下官網(wǎng)對V2簽名的介紹:
??APK 簽名方案 v2 是一種全文件簽名方案,該方案能夠發(fā)現(xiàn)對 APK 的受保護(hù)部分進(jìn)行的所有更改,從而有助于加快驗證速度并增強(qiáng)完整性保證。
??使用 APK 簽名方案 v2 進(jìn)行簽名時,會在 APK 文件中插入一個 APK 簽名分塊,該分塊位于“ZIP 中央目錄”部分之前并緊鄰該部分。在“APK 簽名分塊”內(nèi),v2 簽名和簽名者身份信息會存儲在 APK 簽名方案 v2 分塊中。
apk v2簽名提取 signature verification,android,java

圖 1. 簽名前和簽名后的 APK

??APK 簽名方案 v2 是在 Android 7.0 (Nougat) 中引入的。為了使 APK 可在 Android 6.0 (Marshmallow) 及更低版本的設(shè)備上安裝,應(yīng)先使用 JAR 簽名功能對 APK 進(jìn)行簽名,然后再使用 v2 方案對其進(jìn)行簽名。

APK 簽名分塊
??為了保持與 v1 APK 格式向后兼容,v2 及更高版本的 APK 簽名會存儲在“APK 簽名分塊”內(nèi),該分塊是為了支持 APK 簽名方案 v2 而引入的一個新容器。在 APK 文件中,“APK 簽名分塊”位于“ZIP 中央目錄”(位于文件末尾)之前并緊鄰該部分。
??該分塊包含多個“ID-值”對,所采用的封裝方式有助于更輕松地在 APK 中找到該分塊。APK 的 v2 簽名會存儲為一個“ID-值”對,其中 ID 為 0x7109871a。

格式
??“APK 簽名分塊”的格式如下(所有數(shù)字字段均采用小端字節(jié)序):

  • size of block,以字節(jié)數(shù)(不含此字段)計 (uint64)
  • 帶 uint64 長度前綴的“ID-值”對序列:
    • ID (uint32)
    • value(可變長度:“ID-值”對的長度 - 4 個字節(jié))
  • size of block,以字節(jié)數(shù)計 - 與第一個字段相同 (uint64)
  • magic“APK 簽名分塊 42”(16 個字節(jié))

??在解析 APK 時,首先要通過以下方法找到“ZIP 中央目錄”的起始位置:在文件末尾找到“ZIP 中央目錄結(jié)尾”記錄,然后從該記錄中讀取“中央目錄”的起始偏移量。通過 magic 值,可以快速確定“中央目錄”前方可能是“APK 簽名分塊”。然后,通過 size of block 值,可以高效地找到該分塊在文件中的起始位置。
??在解譯該分塊時,應(yīng)忽略 ID 未知的“ID-值”對。

APK 簽名方案 v2 分塊
??APK 由一個或多個簽名者/身份簽名,每個簽名者/身份均由一個簽名密鑰來表示。該信息會以“APK 簽名方案 v2 分塊”的形式存儲。對于每個簽名者,都會存儲以下信息:

  • (簽名算法、摘要、簽名)元組。摘要會存儲起來,以便將簽名驗證和 APK 內(nèi)容完整性檢查拆開進(jìn)行。
  • 表示簽名者身份的 X.509 證書鏈。
  • 采用鍵值對形式的其他屬性。
    ??對于每位簽名者,都會使用收到的列表中支持的簽名來驗證 APK。簽名算法未知的簽名會被忽略。如果遇到多個支持的簽名,則由每個實現(xiàn)來選擇使用哪個簽名。這樣一來,以后便能夠以向后兼容的方式引入安全系數(shù)更高的簽名方法。建議的方法是驗證安全系數(shù)最高的簽名。

格式
??“APK 簽名方案 v2 分塊”存儲在“APK 簽名分塊”內(nèi),ID 為 0x7109871a。
??“APK 簽名方案 v2 分塊”的格式如下(所有數(shù)字值均采用小端字節(jié)序,所有帶長度前綴的字段均使用 uint32 值表示長度):

  • 帶長度前綴的 signer(帶長度前綴)序列:
    • 帶長度前綴的 signed data:
      • 帶長度前綴的 digests(帶長度前綴)序列:
        • signature algorithm ID (uint32)
        • (帶長度前綴)digest - 請參閱受完整性保護(hù)的內(nèi)容
      • 帶長度前綴的 X.509 certificates 序列:
        • 帶長度前綴的 X.509 certificate(ASN.1 DER 格式)
      • 帶長度前綴的 additional attributes(帶長度前綴)序列:
        • ID (uint32)
        • value(可變長度:附加屬性的長度 - 4 個字節(jié))
    • 帶長度前綴的 signatures(帶長度前綴)序列:
      -signature algorithm ID (uint32)
      -signed data 上帶長度前綴的 signature
    • 帶長度前綴的 public key(SubjectPublicKeyInfo,ASN.1 DER 形式)

??以上就是官網(wǎng)上對V2簽名的介紹,下面跟著源代碼按照上面介紹的,具體的看一下,簽名塊的查找、簽名的驗證過程。

簽名塊的查找

??apk文件其實就是zip文件壓縮包,上面也介紹了查找簽名的步驟。代碼的實現(xiàn)是在ApkSignatureSchemeV2Verifier類的findSignature(RandomAccessFile apk)中,它位于platform\frameworks\base\core\java\android\util\apk目錄下:

    public static SignatureInfo findSignature(RandomAccessFile apk)
            throws IOException, SignatureNotFoundException {
        return ApkSigningBlockUtils.findSignature(apk, APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
    }

??參數(shù)apk就是要查找的apk文件的RandomAccessFile 對象,APK_SIGNATURE_SCHEME_V2_BLOCK_ID就是上面說的 “APK 簽名方案 v2 分塊”存儲在“APK 簽名分塊”內(nèi),值為 0x7109871a。先找到APK 簽名分塊,然后通過APK_SIGNATURE_SCHEME_V2_BLOCK_ID找到APK 簽名方案 v2 分塊。然后再得到具體的值。
??再看下ApkSigningBlockUtils.findSignature()方法:

    static SignatureInfo findSignature(RandomAccessFile apk, int blockId)
            throws IOException, SignatureNotFoundException {
        // Find the ZIP End of Central Directory (EoCD) record.
        Pair<ByteBuffer, Long> eocdAndOffsetInFile = getEocd(apk);
        ByteBuffer eocd = eocdAndOffsetInFile.first;
        long eocdOffset = eocdAndOffsetInFile.second;
        if (ZipUtils.isZip64EndOfCentralDirectoryLocatorPresent(apk, eocdOffset)) {
            throw new SignatureNotFoundException("ZIP64 APK not supported");
        }

        // Find the APK Signing Block. The block immediately precedes the Central Directory.
        long centralDirOffset = getCentralDirOffset(eocd, eocdOffset);
        Pair<ByteBuffer, Long> apkSigningBlockAndOffsetInFile =
                findApkSigningBlock(apk, centralDirOffset);
        ByteBuffer apkSigningBlock = apkSigningBlockAndOffsetInFile.first;
        long apkSigningBlockOffset = apkSigningBlockAndOffsetInFile.second;

        // Find the APK Signature Scheme Block inside the APK Signing Block.
        ByteBuffer apkSignatureSchemeBlock = findApkSignatureSchemeBlock(apkSigningBlock,
                blockId);

        return new SignatureInfo(
                apkSignatureSchemeBlock,
                apkSigningBlockOffset,
                centralDirOffset,
                eocdOffset,
                eocd);
    }

??該方法主要做了一下幾件事:
??一、找到中央目錄尾部的數(shù)據(jù)和在apk文件中的偏移量。
??二、通過中央目錄得到簽名分塊數(shù)據(jù)和其在apk文件中的偏移量。
??三、通過blockId找到v2 分塊的數(shù)據(jù)。
??最后將相關(guān)數(shù)據(jù)封裝到SignatureInfo類中返回結(jié)果。

找到中央目錄尾部的數(shù)據(jù)和在apk文件中的偏移量

??它是由getEocd(RandomAccessFile apk)實現(xiàn)的,而getEocd()是由ZipUtils類的靜態(tài)方法findZipEndOfCentralDirectoryRecord()實現(xiàn),看一下它:

    static Pair<ByteBuffer, Long> findZipEndOfCentralDirectoryRecord(RandomAccessFile zip)
            throws IOException {
        // ZIP End of Central Directory (EOCD) record is located at the very end of the ZIP archive.
        // The record can be identified by its 4-byte signature/magic which is located at the very
        // beginning of the record. A complication is that the record is variable-length because of
        // the comment field.
        // The algorithm for locating the ZIP EOCD record is as follows. We search backwards from
        // end of the buffer for the EOCD record signature. Whenever we find a signature, we check
        // the candidate record's comment length is such that the remainder of the record takes up
        // exactly the remaining bytes in the buffer. The search is bounded because the maximum
        // size of the comment field is 65535 bytes because the field is an unsigned 16-bit number.

        long fileSize = zip.length();
        if (fileSize < ZIP_EOCD_REC_MIN_SIZE) {
            return null;
        }

        // Optimization: 99.99% of APKs have a zero-length comment field in the EoCD record and thus
        // the EoCD record offset is known in advance. Try that offset first to avoid unnecessarily
        // reading more data.
        Pair<ByteBuffer, Long> result = findZipEndOfCentralDirectoryRecord(zip, 0);
        if (result != null) {
            return result;
        }

        // EoCD does not start where we expected it to. Perhaps it contains a non-empty comment
        // field. Expand the search. The maximum size of the comment field in EoCD is 65535 because
        // the comment length field is an unsigned 16-bit number.
        return findZipEndOfCentralDirectoryRecord(zip, UINT16_MAX_VALUE);
    }

??可見它主要是由findZipEndOfCentralDirectoryRecord(RandomAccessFile zip, int maxCommentSize)方法實現(xiàn)的,我們發(fā)現(xiàn)它有可能會調(diào)用兩次,第一次第二個參數(shù)傳遞的是0,如果結(jié)果為null,會將第二個參數(shù)設(shè)置為UINT16_MAX_VALUE繼續(xù)執(zhí)行該方法。

??如果想要明白它是怎么查詢的,還需要了解一下中央目錄尾部的數(shù)據(jù)結(jié)構(gòu)。

名稱 說明
中央目錄尾部標(biāo)識 4字節(jié) 固定值(0x06054b50)
該磁盤編號 2字節(jié)
中央目錄開始位置的磁盤編號 2字節(jié)
該磁盤上的中央目錄中的實體總數(shù) 2字節(jié)
中央目錄中實體總數(shù) 2字節(jié)
中央目錄大小 4字節(jié)
中央目錄開始位置相較于起始磁盤號的偏移量 4字節(jié)
ZIP文件注釋長度 2字節(jié)
ZIP文件注釋 不固定長度的數(shù)據(jù)

??zip壓縮文件的最后部分是中央目錄尾部,它的結(jié)構(gòu)是22字節(jié)固定長度值和不固定長度的注釋值,這個不固定長度最多是2個字節(jié)長度。因為apk文件大概99.99%都是0長度的注釋值,所以剛開始就直接使用0來查找中央目錄尾部,這是一種優(yōu)化策略。從APK的結(jié)尾前22字節(jié)開始讀取數(shù)據(jù),如果得到第一個固定值為0x06054b50,并且找到對應(yīng)的注釋長度為0,則認(rèn)為找到中央目錄尾部。
??如果長度為0,沒查到中央目錄尾部,則認(rèn)為存在注釋數(shù)據(jù),則使用最長的長度UINT16_MAX_VALUE(65536)進(jìn)行查詢。因為注釋前22字節(jié)是固定長度數(shù)據(jù),將APK結(jié)尾前UINT16_MAX_VALUE + 22字節(jié)的數(shù)據(jù)讀取下來,從數(shù)據(jù)的后22字節(jié)開始往前查,如果找到第一個固定值為0x06054b50,然后從數(shù)據(jù)中取到注釋的長度和實際計數(shù)得到的注釋長度一致,認(rèn)為找到中央目錄結(jié)尾。
??總體的查詢思想就是上面介紹的這樣的邏輯。
??這里還要說一下findZipEndOfCentralDirectoryRecord()返回的結(jié)果Pair<ByteBuffer, Long>,Pair對象的第二個就是中央目錄結(jié)尾在APK文件中的位置偏移量。第一個是HeapByteBuffer對象,它是分配在堆上面的。上面我們知道,我們查找中央目錄尾部讀取的數(shù)據(jù),如果存在注釋時,會讀取UINT16_MAX_VALUE + 22字節(jié)的數(shù)據(jù)。但是注釋不見得是UINT16_MAX_VALUE長度,可能小于它。那么它是怎么識別HeapByteBuffer對象中的數(shù)據(jù)從中央目錄尾部標(biāo)識開始的位置的呢,ByteBuffer有一個成員變量offset,就是通過它來標(biāo)識開始位置的。

通過中央目錄得到簽名分塊數(shù)據(jù)和在apk文件中的偏移量

??得到了中央目錄尾部的數(shù)據(jù),就能得到中央目錄的位置和大小。還看中央目錄尾部的數(shù)據(jù)結(jié)構(gòu)內(nèi)容,其中字段 中央目錄開始位置相較于起始磁盤號的偏移量、中央目錄大小 分表標(biāo)識中央目錄的位置和大小。
??getCentralDirOffset(eocd, eocdOffset)就是通過這兩個字段的值得到了中央目錄的偏移量。
??緊接著findApkSigningBlock(apk, centralDirOffset)就通過中央目錄的位置得到了簽名分塊的數(shù)據(jù)和偏移位置。
??我們知道簽名分塊的數(shù)據(jù)是在中央目錄之前,并且通過分塊的數(shù)據(jù)格式知道,在中央目錄之前24字節(jié)處能知道簽名分塊的數(shù)據(jù)大小。這樣我們就能找到簽名分塊的偏移位置。findApkSigningBlock(apk, centralDirOffset)就是按照這個邏輯查找的。

通過blockId找到v2 分塊的數(shù)據(jù)

??v2分塊的數(shù)據(jù)是通過findApkSignatureSchemeBlock(apkSigningBlock, blockId)找到的,這里blockId前面也說了是0x7109871a。

    static ByteBuffer findApkSignatureSchemeBlock(ByteBuffer apkSigningBlock, int blockId)
            throws SignatureNotFoundException {
        checkByteOrderLittleEndian(apkSigningBlock);
        // FORMAT:
        // OFFSET       DATA TYPE  DESCRIPTION
        // * @+0  bytes uint64:    size in bytes (excluding this field)
        // * @+8  bytes pairs
        // * @-24 bytes uint64:    size in bytes (same as the one above)
        // * @-16 bytes uint128:   magic
        ByteBuffer pairs = sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24);

        int entryCount = 0;
        while (pairs.hasRemaining()) {
            entryCount++;
            if (pairs.remaining() < 8) {
                throw new SignatureNotFoundException(
                        "Insufficient data to read size of APK Signing Block entry #" + entryCount);
            }
            long lenLong = pairs.getLong();
            if ((lenLong < 4) || (lenLong > Integer.MAX_VALUE)) {
                throw new SignatureNotFoundException(
                        "APK Signing Block entry #" + entryCount
                                + " size out of range: " + lenLong);
            }
            int len = (int) lenLong;
            int nextEntryPos = pairs.position() + len;
            if (len > pairs.remaining()) {
                throw new SignatureNotFoundException(
                        "APK Signing Block entry #" + entryCount + " size out of range: " + len
                                + ", available: " + pairs.remaining());
            }
            int id = pairs.getInt();
            if (id == blockId) {
                return getByteBuffer(pairs, len - 4);
            }
            pairs.position(nextEntryPos);
        }

        throw new SignatureNotFoundException(
                "No block with ID " + blockId + " in APK Signing Block.");
    }

??因為簽名分塊的第8個字節(jié)開始才是具體的實體數(shù)據(jù),并且到分塊數(shù)據(jù)的倒數(shù)第24個字節(jié)結(jié)束。所以開始就通過sliceFromTo(apkSigningBlock, 8, apkSigningBlock.capacity() - 24)調(diào)整ByteBuffer 的參數(shù)??匆幌?,具體的實現(xiàn):

    static ByteBuffer sliceFromTo(ByteBuffer source, int start, int end) {
        if (start < 0) {
            throw new IllegalArgumentException("start: " + start);
        }
        if (end < start) {
            throw new IllegalArgumentException("end < start: " + end + " < " + start);
        }
        int capacity = source.capacity();
        if (end > source.capacity()) {
            throw new IllegalArgumentException("end > capacity: " + end + " > " + capacity);
        }
        int originalLimit = source.limit();
        int originalPosition = source.position();
        try {
            source.position(0);
            source.limit(end);
            source.position(start);
            ByteBuffer result = source.slice();
            result.order(source.order());
            return result;
        } finally {
            source.position(0);
            source.limit(originalLimit);
            source.position(originalPosition);
        }
    }

??其實就是通過 ByteBuffer對象的slice()方法重新生成一個新的ByteBuffer對象,調(diào)整了一下它的相關(guān)參數(shù)。但是新生成的ByteBuffer對象和原來的ByteBuffer對象使用的是同一個數(shù)組。
??這里先設(shè)置source的limit為分塊數(shù)據(jù)的倒數(shù)第24個字節(jié)位置、position為分塊的第8個字節(jié)位置,然后執(zhí)行了slice()方法,在這ByteBuffer對象實際是HeapByteBuffer對象,所以調(diào)用的是HeapByteBuffer對象的slice()方法。

    public ByteBuffer slice() {
        return new HeapByteBuffer(hb,
                -1,
                0,
                remaining(),
                remaining(),
                position() + offset,
                isReadOnly);
    }

??remaining()是limit-position,所以新生成的ByteBuffer對象的mark為-1,position為0,limit為remaining(),capacity為remaining(),還有offset為position() + offset,position()目前是分塊的第8個字節(jié)位置,offset為0。
??設(shè)置ByteBuffer對象的offset做啥,其實就是為了以后讀取數(shù)據(jù),都要從當(dāng)前位置加上這個偏移值之后位置對應(yīng)的數(shù)據(jù)。
??findApkSignatureSchemeBlock()調(diào)整好ByteBuffer對象之后,接著就要去找v2分塊數(shù)據(jù)了。簽名分塊數(shù)據(jù)不止包含v2分塊數(shù)據(jù),可能還包含其他實體數(shù)據(jù)。這每一個實體的開頭都是這個實體的長度。這樣,我們通過這個長度能很快找到下一個實體。
??所以findApkSignatureSchemeBlock()就使用了一個循環(huán),先得到實體的長度。緊挨著這個長度的數(shù)據(jù)就是ID值,這樣通過循環(huán)實體,然后比較ID值,就能找到v2分塊數(shù)據(jù)。得到v2分塊數(shù)據(jù)是通過getByteBuffer(pairs, len - 4)得到的,其中里面也是通過HeapByteBuffer對象的slice()方法來得到一個新的ByteBuffer對象。

    static ByteBuffer getByteBuffer(ByteBuffer source, int size)
            throws BufferUnderflowException {
        if (size < 0) {
            throw new IllegalArgumentException("size: " + size);
        }
        int originalLimit = source.limit();
        int position = source.position();
        int limit = position + size;
        if ((limit < position) || (limit > originalLimit)) {
            throw new BufferUnderflowException();
        }
        source.limit(limit);
        try {
            ByteBuffer result = source.slice();
            result.order(source.order());
            source.position(limit);
            return result;
        } finally {
            source.limit(originalLimit);
        }
    }

??可見這里新生成的ByteBuffer對象的可用數(shù)據(jù)大小為參數(shù)size。并且slice()會根據(jù)position和offset的和重新設(shè)置ByteBuffer對象的偏移量。生成新的對象之后,注意會把原來的ByteBuffer 對象成員變量position設(shè)置為limit,說明原來的ByteBuffer 對象會跳過新生成的ByteBuffer對象的數(shù)據(jù),將原來的ByteBuffer 對象limit還原。

??這樣就通過ApkSignatureSchemeV2Verifier類的findSignature(RandomAccessFile apk)得到了SignatureInfo對象,它里面包括簽名v2分塊數(shù)據(jù)、簽名分塊的偏移位置、中央目錄的偏移位置、中央目錄尾部的偏移位置、中央目錄尾部的數(shù)據(jù)。

簽名的驗證過程

??驗證過程實現(xiàn)在ApkSignatureSchemeV2Verifier類的verify()中,代碼如下:

    private static VerifiedSigner verify(
            RandomAccessFile apk,
            SignatureInfo signatureInfo,
            boolean doVerifyIntegrity) throws SecurityException, IOException {
        int signerCount = 0;
        Map<Integer, byte[]> contentDigests = new ArrayMap<>();
        List<X509Certificate[]> signerCerts = new ArrayList<>();
        CertificateFactory certFactory;
        try {
            certFactory = CertificateFactory.getInstance("X.509");
        } catch (CertificateException e) {
            throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e);
        }
        ByteBuffer signers;
        try {
            signers = getLengthPrefixedSlice(signatureInfo.signatureBlock);
        } catch (IOException e) {
            throw new SecurityException("Failed to read list of signers", e);
        }
        while (signers.hasRemaining()) {
            signerCount++;
            try {
                ByteBuffer signer = getLengthPrefixedSlice(signers);
                X509Certificate[] certs = verifySigner(signer, contentDigests, certFactory);
                signerCerts.add(certs);
            } catch (IOException | BufferUnderflowException | SecurityException e) {
                throw new SecurityException(
                        "Failed to parse/verify signer #" + signerCount + " block",
                        e);
            }
        }

        if (signerCount < 1) {
            throw new SecurityException("No signers found");
        }

        if (contentDigests.isEmpty()) {
            throw new SecurityException("No content digests found");
        }

        if (doVerifyIntegrity) {
            ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo);
        }
        byte[] verityRootHash = null;
        if (contentDigests.containsKey(CONTENT_DIGEST_VERITY_CHUNKED_SHA256)) {
            byte[] verityDigest = contentDigests.get(CONTENT_DIGEST_VERITY_CHUNKED_SHA256);
            verityRootHash = ApkSigningBlockUtils.parseVerityDigestAndVerifySourceLength(
                    verityDigest, apk.length(), signatureInfo);
        }

        return new VerifiedSigner(
                signerCerts.toArray(new X509Certificate[signerCerts.size()][]),
                verityRootHash, contentDigests);
    }

??V2分塊數(shù)據(jù)的格式參考上面v2分塊格式,signatureInfo.signatureBlock前面說了,它就是v2分塊數(shù)據(jù)。剛開始就是一個int長度,它是后面所有signer的長度。getLengthPrefixedSlice(signatureInfo.signatureBlock)就是通過讀取首個int長度來得到一個新的ByteBuffer對象signers。
??接著開啟一個循環(huán),來讀取處理所有signer。signer就是簽名信息。在這里可以看到,一個APK能允許多個簽名。
??verity主要做了以下幾件事:
??一、調(diào)用verifySigner(signer, contentDigests, certFactory)驗證簽名
??二、根據(jù)參數(shù)doVerifyIntegrity,來決定是否做文件完整性驗證,調(diào)用的是ApkSigningBlockUtils.verifyIntegrity(contentDigests, apk, signatureInfo)方法,它的詳細(xì)在下一篇文章Android APK文件完整性驗證
??三、如果v2分塊中包括CONTENT_DIGEST_VERITY_CHUNKED_SHA256算法,調(diào)用ApkSigningBlockUtils.parseVerityDigestAndVerifySourceLength()得到Merkle樹根摘要。
??最后封裝成VerifiedSigner對象返回。它包含各個簽名者的證書,Merkle樹根摘要,還有每個簽名者中最好的摘要算法。
??下面主要看一下第一步的驗證簽名

驗證簽名

??verifySigner(signer, contentDigests, certFactory)方法有些長,分段來閱讀,第一段:

    private static X509Certificate[] verifySigner(
            ByteBuffer signerBlock,
            Map<Integer, byte[]> contentDigests,
            CertificateFactory certFactory) throws SecurityException, IOException {
        ByteBuffer signedData = getLengthPrefixedSlice(signerBlock);
        ByteBuffer signatures = getLengthPrefixedSlice(signerBlock);
        byte[] publicKeyBytes = readLengthPrefixedByteArray(signerBlock);

        int signatureCount = 0;
        int bestSigAlgorithm = -1;
        byte[] bestSigAlgorithmSignatureBytes = null;
        List<Integer> signaturesSigAlgorithms = new ArrayList<>();
        while (signatures.hasRemaining()) {
            signatureCount++;
            try {
                ByteBuffer signature = getLengthPrefixedSlice(signatures);
                if (signature.remaining() < 8) {
                    throw new SecurityException("Signature record too short");
                }
                int sigAlgorithm = signature.getInt();
                signaturesSigAlgorithms.add(sigAlgorithm);
                if (!isSupportedSignatureAlgorithm(sigAlgorithm)) {
                    continue;
                }
                if ((bestSigAlgorithm == -1)
                        || (compareSignatureAlgorithm(sigAlgorithm, bestSigAlgorithm) > 0)) {
                    bestSigAlgorithm = sigAlgorithm;
                    bestSigAlgorithmSignatureBytes = readLengthPrefixedByteArray(signature);
                }
            } catch (IOException | BufferUnderflowException e) {
                throw new SecurityException(
                        "Failed to parse signature record #" + signatureCount,
                        e);
            }
        }
        if (bestSigAlgorithm == -1) {
            if (signatureCount == 0) {
                throw new SecurityException("No signatures found");
            } else {
                throw new SecurityException("No supported signatures found");
            }
        }

??調(diào)用getLengthPrefixedSlice()分別得到signed data、signatures數(shù)據(jù)。通過readLengthPrefixedByteArray()得到public key數(shù)據(jù)。
??先通過循環(huán),處理signatures數(shù)據(jù)。解析的時候,會將算法放到signaturesSigAlgorithms中。如果存在多個簽名算法,會通過比較選出最好的簽名算法,并且得到signed data通過簽名算法之后得到的簽名bestSigAlgorithmSignatureBytes 。
??看一下支持的算法:

    static boolean isSupportedSignatureAlgorithm(int sigAlgorithm) {
        switch (sigAlgorithm) {
            case SIGNATURE_RSA_PSS_WITH_SHA256:
            case SIGNATURE_RSA_PSS_WITH_SHA512:
            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256:
            case SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512:
            case SIGNATURE_ECDSA_WITH_SHA256:
            case SIGNATURE_ECDSA_WITH_SHA512:
            case SIGNATURE_DSA_WITH_SHA256:
            case SIGNATURE_VERITY_RSA_PKCS1_V1_5_WITH_SHA256:
            case SIGNATURE_VERITY_ECDSA_WITH_SHA256:
            case SIGNATURE_VERITY_DSA_WITH_SHA256:
                return true;
            default:
                return false;
        }
    }

??如果沒有找到最好的簽名算法,會報SecurityException異常。
??verifySigner()第二段代碼:

        String keyAlgorithm = getSignatureAlgorithmJcaKeyAlgorithm(bestSigAlgorithm);
        Pair<String, ? extends AlgorithmParameterSpec> signatureAlgorithmParams =
                getSignatureAlgorithmJcaSignatureAlgorithm(bestSigAlgorithm);
        String jcaSignatureAlgorithm = signatureAlgorithmParams.first;
        AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureAlgorithmParams.second;
        boolean sigVerified;
        try {
            PublicKey publicKey =
                    KeyFactory.getInstance(keyAlgorithm)
                            .generatePublic(new X509EncodedKeySpec(publicKeyBytes));
            Signature sig = Signature.getInstance(jcaSignatureAlgorithm);
            sig.initVerify(publicKey);
            if (jcaSignatureAlgorithmParams != null) {
                sig.setParameter(jcaSignatureAlgorithmParams);
            }
            sig.update(signedData);
            sigVerified = sig.verify(bestSigAlgorithmSignatureBytes);
        } catch (NoSuchAlgorithmException | InvalidKeySpecException | InvalidKeyException
                | InvalidAlgorithmParameterException | SignatureException e) {
            throw new SecurityException(
                    "Failed to verify " + jcaSignatureAlgorithm + " signature", e);
        }
        if (!sigVerified) {
            throw new SecurityException(jcaSignatureAlgorithm + " signature did not verify");
        }

??先得到最好算法對應(yīng)的JCA算法,再得到最好算法對應(yīng)的JCA簽名算法和參數(shù)。之后得到公鑰,然后通過公鑰驗證signed data和簽名bestSigAlgorithmSignatureBytes。如果驗證沒通過,會報SecurityException異常。
??verifySigner()第三段代碼:

        byte[] contentDigest = null;
        signedData.clear();
        ByteBuffer digests = getLengthPrefixedSlice(signedData);
        List<Integer> digestsSigAlgorithms = new ArrayList<>();
        int digestCount = 0;
        while (digests.hasRemaining()) {
            digestCount++;
            try {
                ByteBuffer digest = getLengthPrefixedSlice(digests);
                if (digest.remaining() < 8) {
                    throw new IOException("Record too short");
                }
                int sigAlgorithm = digest.getInt();
                digestsSigAlgorithms.add(sigAlgorithm);
                if (sigAlgorithm == bestSigAlgorithm) {
                    contentDigest = readLengthPrefixedByteArray(digest);
                }
            } catch (IOException | BufferUnderflowException e) {
                throw new IOException("Failed to parse digest record #" + digestCount, e);
            }
        }

        if (!signaturesSigAlgorithms.equals(digestsSigAlgorithms)) {
            throw new SecurityException(
                    "Signature algorithms don't match between digests and signatures records");
        }
        int digestAlgorithm = getSignatureAlgorithmContentDigestAlgorithm(bestSigAlgorithm);
        byte[] previousSignerDigest = contentDigests.put(digestAlgorithm, contentDigest);
        if ((previousSignerDigest != null)
                && (!MessageDigest.isEqual(previousSignerDigest, contentDigest))) {
            throw new SecurityException(
                    getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm)
                    + " contents digest does not match the digest specified by a preceding signer");
        }

??這塊處理的是signedData中的digests序列,其數(shù)據(jù)的結(jié)構(gòu)參考v2分塊格式。得到其中的摘要算法,將摘要算法都放入digestsSigAlgorithms中。并且和前面最好的算法bestSigAlgorithm如果相同,則會將摘要數(shù)據(jù)讀入contentDigest中。
??讀取完畢后,會比較digestsSigAlgorithms和signaturesSigAlgorithms。如果不同,則會報異常。
??接著通過getSignatureAlgorithmContentDigestAlgorithm()方法,將簽名算法轉(zhuǎn)化成對應(yīng)的摘要算法。還將摘要算法和摘要數(shù)據(jù)放入?yún)?shù)contentDigests中。如果之前有簽名已經(jīng)使用了該摘要算法,并且之前的摘要和當(dāng)前的摘要不同,則會拋出SecurityException異常。
??verifySigner()最后一段代碼:

        ByteBuffer certificates = getLengthPrefixedSlice(signedData);
        List<X509Certificate> certs = new ArrayList<>();
        int certificateCount = 0;
        while (certificates.hasRemaining()) {
            certificateCount++;
            byte[] encodedCert = readLengthPrefixedByteArray(certificates);
            X509Certificate certificate;
            try {
                certificate = (X509Certificate)
                        certFactory.generateCertificate(new ByteArrayInputStream(encodedCert));
            } catch (CertificateException e) {
                throw new SecurityException("Failed to decode certificate #" + certificateCount, e);
            }
            certificate = new VerbatimX509Certificate(certificate, encodedCert);
            certs.add(certificate);
        }

        if (certs.isEmpty()) {
            throw new SecurityException("No certificates listed");
        }
        X509Certificate mainCertificate = certs.get(0);
        byte[] certificatePublicKeyBytes = mainCertificate.getPublicKey().getEncoded();
        if (!Arrays.equals(publicKeyBytes, certificatePublicKeyBytes)) {
            throw new SecurityException(
                    "Public key mismatch between certificate and signature record");
        }

        ByteBuffer additionalAttrs = getLengthPrefixedSlice(signedData);
        verifyAdditionalAttributes(additionalAttrs);

        return certs.toArray(new X509Certificate[certs.size()]);
    }

??這時為了處理signedData中的X.509 certificates序列,其數(shù)據(jù)的結(jié)構(gòu)參考v2分塊格式。會將整數(shù)數(shù)據(jù)封裝成VerbatimX509Certificate對象,并且放入certs中。如果不存在證書,會報SecurityException異常。
??并且certs中第一個證書的公鑰和前面讀出來的公鑰數(shù)據(jù)publicKeyBytes不同,也會報SecurityException異常。
??接著會處理signedData中的additional attributes序列,其數(shù)據(jù)的結(jié)構(gòu)參考v2分塊格式。調(diào)用verifyAdditionalAttributes(ByteBuffer attrs)來進(jìn)行處理。
??最后返回的結(jié)果是證書數(shù)組。

總結(jié)

??本篇文章主要根據(jù)官方文檔,再結(jié)合源代碼,重新梳理了一遍V2簽名的查找、驗證過程。
??在這個過程中,讀取出來的數(shù)據(jù),都是使用HeapByteBuffer類實現(xiàn)存儲的,所以需要好好理解一下HeapByteBuffer類的使用,尤其是slice()使用。
??在查找V2簽名塊時,我們需要了解APK文件的結(jié)構(gòu),明白簽名數(shù)據(jù)塊在中央目錄之前存放,并且需要知道簽名塊的數(shù)據(jù)結(jié)構(gòu)。
??在簽名驗證過程中,更需要熟悉v2分塊的數(shù)據(jù)存儲結(jié)構(gòu)形式。通過找到需要簽名的數(shù)據(jù),簽名數(shù)據(jù),還有簽名算法、公鑰。之后,就能使用公鑰驗簽私鑰簽名的數(shù)據(jù)。文章來源地址http://www.zghlxwxcb.cn/news/detail-785217.html

到了這里,關(guān)于Android APK文件的簽名V2查找、驗證的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • Android 對apk進(jìn)行簽名

    Android 對apk進(jìn)行簽名

    生成簽名文件: 1.使用jarsigner(僅限V1簽名): 2.使用apksigner(默認(rèn)V1和V2簽名),Android11以上不包含V2簽名會裝不上 Notice:? ? v1簽名后再對齊,v2要簽名前對齊。 對齊apk: 查看apk是否對齊: 1.查看apk是否簽名(V1) 2.查看apk是否簽名V1、V2、V3、V4 3.查看簽名文件?

    2024年02月16日
    瀏覽(20)
  • Android APK 簽名打包原理分析(二)【Android簽名原理】

    說到簽名,從這個詞來理解,正常個人需要簽名的時候,一般是用來證明這是某個人的特屬認(rèn)證。 大家是否有印象?還記得我們之前在學(xué)習(xí)、總結(jié)網(wǎng)絡(luò)相關(guān)知識的時候,說到過,客戶端和服務(wù)端雖然通信數(shù)據(jù)上,可以采用對稱加密和非對稱加密組合去進(jìn)行數(shù)據(jù)的加密,但是這

    2024年01月18日
    瀏覽(33)
  • android apk 加固后重新簽名

    android apk 加固后重新簽名

    針對于加固平臺在加固的過程中不能配置簽名文件,加固后的apk需要進(jìn)行重新簽名才能安裝,并發(fā)布到應(yīng)用市場。 第一步,用AS對項目進(jìn)行打包,生成簽名的apk文件。 第二步,使用加固平臺,對apk包進(jìn)行加固,加固完成后,得到一個加固后的apk。 第三步,我們可以使用Andr

    2024年02月06日
    瀏覽(26)
  • Android逆向進(jìn)階,APK簽名問題

    APK簽名的原理基于公鑰加密和數(shù)字證書的機(jī)制。在APK簽名過程中,開發(fā)者使用私鑰對應(yīng)用的數(shù)字摘要進(jìn)行加密,生成簽名文件。然后,開發(fā)者將應(yīng)用和簽名文件一同發(fā)布。當(dāng)用戶下載應(yīng)用時,系統(tǒng)會使用開發(fā)者的公鑰對簽名文件進(jìn)行解密,并與應(yīng)用的數(shù)字摘要進(jìn)行比對,以驗

    2024年02月11日
    瀏覽(48)
  • Android 獲取app(apk)簽名

    Android 獲取app(apk)簽名

    以上方法參考微信開放平臺的-獲取安裝到手機(jī)的第三方應(yīng)用簽名的apk包? 源碼 ?

    2024年02月16日
    瀏覽(48)
  • Android Studio APK簽名教程

    Android Studio APK簽名教程 在Android應(yīng)用程序的開發(fā)過程中,簽名APK文件是非常重要的一步。APK簽名可以確保應(yīng)用程序的完整性和可信度,并提供安全保障。本文將介紹如何使用Android Studio進(jìn)行APK簽名,并附帶相應(yīng)的代碼和描述。 生成密鑰庫(Keystore) 首先,我們需要生成一個密

    2024年02月07日
    瀏覽(25)
  • Android Apk簽名算法使用SHA256

    Android apk簽名算法使用SHA256 本文不介紹復(fù)雜的簽名過程,說一下Android簽名算法使用SHA256。 但是SHA1不是相對安全簽名算法,SHA256更加安全一些。 一般大公司才會有這種細(xì)致的安全要求。 如何查看apk簽名是否是SHA1還是SHA256 ··· 1、拿到apk文件,修改文件后綴為.jar 2、解壓文件

    2024年04月08日
    瀏覽(24)
  • Android apk 反編譯后打包(含簽名)

    Android apk 反編譯后打包(含簽名)

    想分析某些app源碼時,遇到煩人彈框,現(xiàn)在想反編譯看看具體實現(xiàn)。 用到的工具: GDA4.06 apk反編譯工具 apktool apk 打包工具 jdk 環(huán)境 一、反編譯分析 將apk反編譯打開 找到入口代碼 彈框代碼如圖 二、解包、打包 使用apktool解包 ps: apktool工具的用法自行百度 -o模式是指定解包后文

    2024年02月09日
    瀏覽(28)
  • Android Studio 打一個正式簽名的Apk

    Android Studio 打一個正式簽名的Apk

    如何打一個帶正式簽名文件的app (給自己的勞動成果冠名) 1. 選擇build - generate signed bundle/apk 2. 這里有兩個選擇, bundle or apk, 我們選擇apk 于是勾選 apk, 并點下一步 ?3.? 來到選擇證書文件的地方, 但是我們這是第一次做, ?還沒有證書文件, 所以選擇新建一個證書 4. 彈出生成證書

    2023年04月13日
    瀏覽(28)
  • android studio 打包簽名apk時報kotlin版本錯誤

    android studio 打包簽名apk時報kotlin版本錯誤

    報錯信息如下: /Users/abbb/Library/Android/sdk/caches/transforms-3/572ca993caa0789f4046529ddf3eacd2/transformed/jetified-BaseRecyclerViewAdapterHelper-4.0.1/jars/classes.jar!/META-INF/com.github.CymChad.brvah.kotlin_module: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.8.0, expected version is 1.6.

    2024年01月25日
    瀏覽(30)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包