移动知识程序员

Android V2签名机制以及ApkSignerV2签名源码解

2018-06-25  本文已影响375人  黄二狗V

Android 7.0之后,新增了一个签名机制V2签名。

学习Android签名机制之前,需要你了解以下内容:

简要的说明一下:

以上最终目的只是保证内容的完整性以及防止被篡改。签名可以保证完整性,摘要可以保证是否篡改。

V1与V2区别

我们知道APK文件其实就是一个ZIP压缩文件,分为三部分,头文件、中央目录、结尾内容,那么V1和V2有什么区别:

上述内容假设你对V1已经有了解。下面正式进入主题,分析V2签名源码,看一看V2签名过程。

V2签名机制概要

V2签名就是在ZIP的中央目录前并且紧挨着中央目录,增加了一块V2签名块存放签名信息。最终的签名APK,就是四块:头文件区、V2签名块、中央目录、尾部。

V2签名块

整个签名块的格式如下:

在多个“ID-值”对中,APK签名信息的 ID 为 0x7109871a,包含的内容如下:
带长度前缀的 signer

value可能会包含多个 signer,因为Android允许多个签名。
以上信息来自官网,我为了便于读者理解。简要的写了一下重点内容。
总结一下:一个签名块,可以包含多个ID-VALUE,APK的签名信息会存放在 ID 为 0x7109871a的键值对里。他的内容可以包含多个签名者的签名信息,每个签名信息下包含signed datasignaturespublic key,其中,signed data主要存放摘要序列、证书链、额外属性,signatures包含多个签名算法计算出来的签名值,public key表示签名者公钥,用于校验的时候验证签名的。

源码分析

简要的了解了一下V2签名机制,下面我们进入源码分析一下整个过程。
V2签名源码文件:ApkSignerV2.java
地址有墙,准备梯子。没有梯子也没关系,请看下面讲解:

(1)变量的定义
public abstract class ApkSignerV2 {
        //支持的签名算法列表
        public static final int SIGNATURE_RSA_PSS_WITH_SHA256 = 0x0101;
        public static final int SIGNATURE_RSA_PSS_WITH_SHA512 = 0x0102;
        public static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA256 = 0x0103;
        public static final int SIGNATURE_RSA_PKCS1_V1_5_WITH_SHA512 = 0x0104;
        public static final int SIGNATURE_ECDSA_WITH_SHA256 = 0x0201;
        public static final int SIGNATURE_ECDSA_WITH_SHA512 = 0x0202;
        public static final int SIGNATURE_DSA_WITH_SHA256 = 0x0301;
        public static final int SIGNATURE_DSA_WITH_SHA512 = 0x0302;
        public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_NAME = "X-Android-APK-Signed";
        // TODO: Adjust the value when signing scheme finalized.
        public static final String SF_ATTRIBUTE_ANDROID_APK_SIGNED_VALUE = "1234567890";
        private static final int CONTENT_DIGEST_CHUNKED_SHA256 = 0;
        private static final int CONTENT_DIGEST_CHUNKED_SHA512 = 1;
        //计算摘要时的分片大小
        private static final int CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES = 1024 * 1024;
        //魔数
        private static final byte[] APK_SIGNING_BLOCK_MAGIC =
                new byte[] {
                        0x41, 0x50, 0x4b, 0x20, 0x53, 0x69, 0x67, 0x20,
                        0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x20, 0x34, 0x32,
                };
        //签名信息对应的 ID
        private static final int APK_SIGNATURE_SCHEME_V2_BLOCK_ID = 0x7109871a;
        private ApkSignerV2() {}

上面列出了ApkSignerV2中所有定义的变量。可以看到APK_SIGNATURE_SCHEME_V2_BLOCK_ID 为固定值0x7109871a,以及一些支持的算法ID等。

(2)签名代码

签名方法为ApkSignerV2中sign函数,签名操作以及最终生成的APK都是由这个函数完成。函数比较长,主要分为 读APK、摘要、签名生成V2签名块,输出APK,我们主要看摘要、签名生成V2签名块这两部分,其余两部分都是对ZIP的操作,解析APK就是读取ZIP,输出APK就是写ZIP。

        public static ByteBuffer[] sign(
                ByteBuffer inputApk,
                List<SignerConfig> signerConfigs)
                throws ApkParseException, InvalidKeyException, SignatureException {
          
             //这里忽略读解析未签名的APK 代码。。。。

            //获取签名算法序列
            Set<Integer> contentDigestAlgorithms = new HashSet<>();
            for (SignerConfig signerConfig : signerConfigs) {
                for (int signatureAlgorithm : signerConfig.signatureAlgorithms) {
                    contentDigestAlgorithms.add(
                            getSignatureAlgorithmContentDigestAlgorithm(signatureAlgorithm));
                }
            }
            Map<Integer, byte[]> contentDigests; // digest algorithm ID -> digest
            try {
                //对APK内容进行摘要
                contentDigests =computeContentDigests(contentDigestAlgorithms,
                                new ByteBuffer[] {beforeCentralDir, centralDir, eocd});
            } catch (DigestException e) {
                throw new SignatureException("Failed to compute digests of APK", e);
            }

            // 对APK的摘要进行数字签名,并生产V2签名块
            ByteBuffer apkSigningBlock = ByteBuffer.wrap(generateApkSigningBlock(signerConfigs, contentDigests));

           //更新中央目录的偏移量,因为要添加一个V2块到原始的ZIP文件,中央目录偏移量也要改变
            centralDirOffset += apkSigningBlock.remaining();
            eocd.clear();
            ZipUtils.setZipEocdCentralDirectoryOffset(eocd, centralDirOffset);
            originalInputApk.position(originalInputApk.limit());
            beforeCentralDir.clear();
            centralDir.clear();
            eocd.clear();
            // 将V2签名块,放置在中央目录之前
            return new ByteBuffer[] {
                    beforeCentralDir,
                    apkSigningBlock,
                    centralDir,
                    eocd,
            };
        }

上述代码。就是整个V2签名过程,我已经给注释好了。
在sign方法中,有一个参数List<SignerConfig> signerConfigs,这个是什么呢,我们看一下SignerConfig这个类。

/**
* Signer configuration.
 */
public static final class SignerConfig {
            /** Private key. */
            public PrivateKey privateKey;
            /**
             * Certificates, with the first certificate containing the public key corresponding to
             * {@link #privateKey}.
             */
            public List<X509Certificate> certificates;
            /**
             * List of signature algorithms with which to sign (see {@code SIGNATURE_...} constants).
             */
            public List<Integer> signatureAlgorithms;
        }

看官方注释,我们知道这个类表示签名者信息,包含私钥、证书序列、以及签名算法序列,而签名的时候传入的是一个List,说明可能会存在多个签名者signer进行签名,这也就对应着上面阐述到的签名块中包含多个signer签名信息。

(3)摘要计算

首先,说一下APK摘要计算规则,对于每个摘要算法,计算结果如下:

总之,就是把APK按照1M大小分割,分别计算这些分段的摘要,最后把这些分段的摘要在进行计算得到最终的摘要。
在(2)中我们可知computeContentDigests()函数为计算APK摘要,我们进入方法内部,看其实现过程,主要跟着我的注释部分看:

        //参数digestAlgorithms是签名算法列表,contents则为文件内容块、中央目录、EOCD
        private static Map<Integer, byte[]> computeContentDigests(Set<Integer> digestAlgorithms,
                ByteBuffer[] contents) throws DigestException {

          // 按照1M大小 计算分段的数量
         //常量CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES = 1024*1024
            int chunkCount = 0;
            for (ByteBuffer input : contents) {
                chunkCount += getChunkCount(input.remaining(),CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
            }
          //开始计算分段摘要
          //digestsOfChunks记录分段摘要的数据,Integer对应签名算法,byte[]代表摘要
            final Map<Integer, byte[]> digestsOfChunks = new HashMap<>(digestAlgorithms.size());
            for (int digestAlgorithm : digestAlgorithms) {
                //摘要后的结果大小
                int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
                //每个分片摘要结果长度的和
                byte[] concatenationOfChunkCountAndChunkDigests = new byte[5 + chunkCount * digestOutputSizeBytes];
                //每个分片摘要以0x5a连接
                concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
                setUnsignedInt32LittleEngian(chunkCount, concatenationOfChunkCountAndChunkDigests, 1);
                digestsOfChunks.put(digestAlgorithm, concatenationOfChunkCountAndChunkDigests);
            }
            int chunkIndex = 0;
            byte[] chunkContentPrefix = new byte[5];
            chunkContentPrefix[0] = (byte) 0xa5;
            // 块的摘要可以并行计算。
            for (ByteBuffer input : contents) {
                while (input.hasRemaining()) {
                    int chunkSize = Math.min(input.remaining(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
                    //获取1个分片的内容
                    final ByteBuffer chunk = getByteBuffer(input, chunkSize);

                    for (int digestAlgorithm : digestAlgorithms) {

                        String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);

                        MessageDigest md;
                        try {
                            md = MessageDigest.getInstance(jcaAlgorithmName);
                        } catch (NoSuchAlgorithmException e) {
                            throw new DigestException(
                                    jcaAlgorithmName + " MessageDigest not supported", e);
                        }
                        // Reset position to 0 and limit to capacity. Position would've been modified
                        // by the preceding iteration of this loop. NOTE: Contrary to the method name,
                        // this does not modify the contents of the chunk.
                        chunk.clear();

                        setUnsignedInt32LittleEngian(chunk.remaining(), chunkContentPrefix, 1);

                        md.update(chunkContentPrefix);
                        md.update(chunk);
                        byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks.get(digestAlgorithm);

                        int expectedDigestSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);

                        int actualDigestSizeBytes = md.digest(
                                concatenationOfChunkCountAndChunkDigests,
                                        5 + chunkIndex * expectedDigestSizeBytes,
                                        expectedDigestSizeBytes);
                        if (actualDigestSizeBytes != expectedDigestSizeBytes) {
                            throw new DigestException(
                                    "Unexpected output size of " + md.getAlgorithm()
                                            + " digest: " + actualDigestSizeBytes);
                        }
                    }
                    chunkIndex++;
                }
            }
            Map<Integer, byte[]> result = new HashMap<>(digestAlgorithms.size());
            for (Map.Entry<Integer, byte[]> entry : digestsOfChunks.entrySet()) {
                int digestAlgorithm = entry.getKey();
                byte[] concatenationOfChunkCountAndChunkDigests = entry.getValue();
                String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
                MessageDigest md;
                try {
                    md = MessageDigest.getInstance(jcaAlgorithmName);
                } catch (NoSuchAlgorithmException e) {
                    throw new DigestException(jcaAlgorithmName + " MessageDigest not supported", e);
                }
                result.put(digestAlgorithm, md.digest(concatenationOfChunkCountAndChunkDigests));
            }
            return result;
        }

我们来 看一下分段摘要代码:

int chunkIndex = 0;
            byte[] chunkContentPrefix = new byte[5];
            //分段以0xa5开始
            chunkContentPrefix[0] = (byte) 0xa5;

            // Optimization opportunity: digests of chunks can be computed in parallel.
            for (ByteBuffer input : contents) {
                while (input.hasRemaining()) {
                    int chunkSize = Math.min(input.remaining(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
                    //获取1个分片的内容
                    final ByteBuffer chunk = getByteBuffer(input, chunkSize);

                   // 该循环对一个分段进行摘要计算(所有的签名算法都算一遍)
                    for (int digestAlgorithm : digestAlgorithms) {
                        String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
                        MessageDigest md;
                        try {
                            md = MessageDigest.getInstance(jcaAlgorithmName);
                        } catch (NoSuchAlgorithmException e) {
                            throw new DigestException(
                                    jcaAlgorithmName + " MessageDigest not supported", e);
                        }
                        chunk.clear();
                        setUnsignedInt32LittleEngian(chunk.remaining(), chunkContentPrefix, 1);

                        //设置要被计算的数据
                        md.update(chunkContentPrefix);
                        md.update(chunk);
                        //用当前算法下的这个字节数组来接收计算的分段摘要
                        byte[] concatenationOfChunkCountAndChunkDigests = digestsOfChunks.get(digestAlgorithm);
                        int expectedDigestSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
                        //执行计算
                        int actualDigestSizeBytes = md.digest(
                                concatenationOfChunkCountAndChunkDigests,
                                        5 + chunkIndex * expectedDigestSizeBytes,
                                        expectedDigestSizeBytes);
                        if (actualDigestSizeBytes != expectedDigestSizeBytes) {
                            throw new DigestException(
                                    "Unexpected output size of " + md.getAlgorithm()
                                            + " digest: " + actualDigestSizeBytes);
                        }
                    }
                    chunkIndex++;
                }
            }

上面md.digest()方法就是最终计算一个分段摘要的方法,0xa5作为数据的开始,用concatenationOfChunkCountAndChunkDigests变量来接收的,而它的值是从digestsOfChunks根据签名算法ID获取来的一个byte[],所以digestsOfChunks集合是用来接收分段摘要后的数据的。for循环是做并行计算,直接把一个分段用所有的算法ID做了一遍摘要,分别存在digestsOfChunks对应的算法ID对应的Value中了。于是我们在看一下digestsOfChunks如何定义的:

          //计算分段的全部数量
            int chunkCount = 0;
            for (ByteBuffer input : contents) {
                chunkCount += getChunkCount(input.remaining(), CONTENT_DIGESTED_CHUNK_MAX_SIZE_BYTES);
            }
            
            //digestsOfChunks记录分段摘要数据集合,Integer对应签名算法,byte[]代表摘要
            final Map<Integer, byte[]> digestsOfChunks = new HashMap<>(digestAlgorithms.size());
            for (int digestAlgorithm : digestAlgorithms) {
                //当前算法下计算的摘要结果大小
                int digestOutputSizeBytes = getContentDigestAlgorithmOutputSizeBytes(digestAlgorithm);
                //所有分段摘要结果长度的和 +5
                byte[] concatenationOfChunkCountAndChunkDigests = new byte[5 + chunkCount * digestOutputSizeBytes];
                //最终的摘要以0x5a开始
                concatenationOfChunkCountAndChunkDigests[0] = 0x5a;
                setUnsignedInt32LittleEngian(chunkCount, concatenationOfChunkCountAndChunkDigests, 1);
                digestsOfChunks.put(digestAlgorithm, concatenationOfChunkCountAndChunkDigests);
            }

我们看到,循环所有签名算法ID,循环内部 计算了当前签名算法计算结果的长度,乘以分片的总数量,就是当前算法下,所有分段进行计算后合并在一起的长度,而且以0x5a开始,用于接收分段摘要数据,最终,put进digestsOfChunks集合。所以,digestsOfChunks里记录的都是每个算法下对应的分段摘要数据。那么根据摘要规则,最终的APK摘要数据,肯定是通过digestsOfChunks来计算,继续往下看computeContentDigests()方法中:

Map<Integer, byte[]> result = new HashMap<>(digestAlgorithms.size());
            for (Map.Entry<Integer, byte[]> entry : digestsOfChunks.entrySet()) {
                int digestAlgorithm = entry.getKey();
                byte[] concatenationOfChunkCountAndChunkDigests = entry.getValue();
                String jcaAlgorithmName = getContentDigestAlgorithmJcaDigestAlgorithm(digestAlgorithm);
                MessageDigest md;
                try {
                    md = MessageDigest.getInstance(jcaAlgorithmName);
                } catch (NoSuchAlgorithmException e) {
                    throw new DigestException(jcaAlgorithmName + " MessageDigest not supported", e);
                }
                result.put(digestAlgorithm, md.digest(concatenationOfChunkCountAndChunkDigests));
            }
            return result;

如上,最终遍历digestsOfChunks来得到每个算法下的,最终摘要的集合。这个摘要集合就是要记录在V2签名块里的。这里,很多地方都在遍历List<Integer> signatureAlgorithms,这个就是收支持的签名算法集合,可以为一个或者多个,对应的也就是生成一个摘要或者多个摘要来保护APK的内容,同样的,也会对应生成一个或者多个签名。
到这里,整个摘要计算就结束了。返回的result就是APK的摘要序列了。下面我们看签名。

(4)签名计算

上面已经计算得到了APK的摘要,下面就是对摘要进行签名了,我们在进入sign()函数中,找到如下代码,这里就是签名,并生成V2签名块。

            // Sign the digests and wrap the signatures and signer info into an APK Signing Block.
            ByteBuffer apkSigningBlock = ByteBuffer.wrap(generateApkSigningBlock(signerConfigs, contentDigests));

我们重点看generateApkSigningBlock()函数。

 private static byte[] generateApkSigningBlock(List<SignerConfig> signerConfigs,
             Map<Integer, byte[]> contentDigests)throws InvalidKeyException, SignatureException {
   byte[] apkSignatureSchemeV2Block = generateApkSignatureSchemeV2Block(signerConfigs, contentDigests);
   return generateApkSigningBlock(apkSignatureSchemeV2Block);
 }

方法内部只有两行代码,一行就是计算签名内容,一行就是生成签名块。首先看generateApkSignatureSchemeV2Block()函数。

 private static byte[] generateApkSignatureSchemeV2Block(
                List<SignerConfig> signerConfigs,
                Map<Integer, byte[]> contentDigests) throws InvalidKeyException, SignatureException {
            List<byte[]>= new ArrayList<>(signerConfigs.size());
            int signerNumber = 0;
            for (SignerConfig signerConfig : signerConfigs) {
                signerNumber++;
                byte[] signerBlock;
                try {
                    signerBlock = generateSignerBlock(signerConfig, contentDigests);
                } catch (InvalidKeyException e) {
                    throw new InvalidKeyException("Signer #" + signerNumber + " failed", e);
                } catch (SignatureException e) {
                    throw new SignatureException("Signer #" + signerNumber + " failed", e);
                }
                signerBlocks.add(signerBlock);
            }
            return encodeAsSequenceOfLengthPrefixedElements(
                    new byte[][] {
                            encodeAsSequenceOfLengthPrefixedElements(signerBlocks),
                    });
        }

看代码,根据signerConfigs的个数,也就是签名者个数,来生成相应的signer信息块列表,最终返回一个V2信息块,其中generateSignerBlock()方法,主要生成V2信息块的,我们进入其内部看,代码较长,请跟着注释阅读:

private static byte[] generateSignerBlock(
                SignerConfig signerConfig,
                Map<Integer, byte[]> contentDigests) throws InvalidKeyException, SignatureException {

            //获取公钥
            PublicKey publicKey = signerConfig.certificates.get(0).getPublicKey();
            byte[] encodedPublicKey = encodePublicKey(publicKey);

            //初始化signedData ,添加证书,添加摘要等
            V2SignatureSchemeBlock.SignedData signedData = new V2SignatureSchemeBlock.SignedData();
            try {
                signedData.certificates = encodeCertificates(signerConfig.certificates);
            } catch (CertificateEncodingException e) {
                throw new SignatureException("Failed to encode certificates", e);
            }
            List<Pair<Integer, byte[]>> digests = new ArrayList<>(signerConfig.signatureAlgorithms.size());
            for (int signatureAlgorithm : signerConfig.signatureAlgorithms) {
                int contentDigestAlgorithm =
                        getSignatureAlgorithmContentDigestAlgorithm(signatureAlgorithm);
                byte[] contentDigest = contentDigests.get(contentDigestAlgorithm);
                if (contentDigest == null) {
                    throw new RuntimeException(
                            getContentDigestAlgorithmJcaDigestAlgorithm(contentDigestAlgorithm)
                                    + " content digest for "
                                    + getSignatureAlgorithmJcaSignatureAlgorithm(signatureAlgorithm)
                                    + " not computed");
                }
                digests.add(Pair.create(signatureAlgorithm, contentDigest));
            }

            //将摘要序列digests添加到signedData
            signedData.digests = digests;

              //初始化singer,它就是一个签名信息数据
            V2SignatureSchemeBlock.Signer signer = new V2SignatureSchemeBlock.Signer();

            //将signer的signedData赋值
            signer.signedData = encodeAsSequenceOfLengthPrefixedElements(new byte[][] {
                    encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(signedData.digests),
                    encodeAsSequenceOfLengthPrefixedElements(signedData.certificates),
                    // additional attributes
                    new byte[0],
            });
          ////将signer的publicKey 赋值
            signer.publicKey = encodedPublicKey;
            //下面代码,是对每个签名算法下的摘要进行签名,然后设置进signer.signatures
            signer.signatures = new ArrayList<>();
            for (int signatureAlgorithm : signerConfig.signatureAlgorithms) {

                Pair<String, ? extends AlgorithmParameterSpec> signatureParams =
                        getSignatureAlgorithmJcaSignatureAlgorithm(signature
                                Algorithm);
                String jcaSignatureAlgorithm = signatureParams.getFirst();

                AlgorithmParameterSpec jcaSignatureAlgorithmParams = signatureParams.getSecond();
                byte[] signatureBytes;
                try {
                     //对signer中的 signedData进行签名
                    Signature signature = Signature.getInstance(jcaSignatureAlgorithm);
                    signature.initSign(signerConfig.privateKey);
                    if (jcaSignatureAlgorithmParams != null) {
                        signature.setParameter(jcaSignatureAlgorithmParams);
                    }
                    signature.update(signer.signedData);
                    signatureBytes = signature.sign();
                } catch (InvalidKeyException e) {
                    throw new InvalidKeyException("Failed sign using " + jcaSignatureAlgorithm, e);
                } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
                        | SignatureException e) {
                    throw new SignatureException("Failed sign using " + jcaSignatureAlgorithm, e);
                }
                try {
                   //用公钥对签名做一下验证,然后最终赋值给signer.signatures
                    Signature signature = Signature.getInstance(jcaSignatureAlgorithm);
                    signature.initVerify(publicKey);
                    if (jcaSignatureAlgorithmParams != null) {
                        signature.setParameter(jcaSignatureAlgorithmParams);
                    }
                    signature.update(signer.signedData);
                    if (!signature.verify(signatureBytes)) {
                        throw new SignatureException("Signature did not verify");
                    }
                } catch (InvalidKeyException e) {
                    throw new InvalidKeyException("Failed to verify generated " + jcaSignatureAlgorithm
                            + " signature using public key from certificate", e);
                } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
                        | SignatureException e) {
                    throw new SignatureException("Failed to verify generated " + jcaSignatureAlgorithm
                            + " signature using public key from certificate", e);
                }
                signer.signatures.add(Pair.create(signatureAlgorithm, signatureBytes));
            }
            // 最终一个signer block的格式为:
            // * signed data,包含摘要/数字证书/额外属性等
            // *  signatures:
            // * public key (X.509 SubjectPublicKeyInfo, ASN.1 DER encoded)
            return encodeAsSequenceOfLengthPrefixedElements(
                    new byte[][] {
                            signer.signedData,
                            encodeAsSequenceOfLengthPrefixedPairsOfIntAndLengthPrefixedBytes(
                                    signer.signatures),
                            signer.publicKey,
                    });
        }

综上,先实例化了一个signedData,然后实例化了一个signer,分别对其赋值,计算签名等等,最终返回去。

我们再回到generateApkSigningBlock函数:

 private static byte[] generateApkSigningBlock(List<SignerConfig> signerConfigs,
           Map<Integer, byte[]> contentDigests) throws InvalidKeyException, SignatureException {
            byte[] apkSignatureSchemeV2Block = generateApkSignatureSchemeV2Block(signerConfigs, contentDigests);
            return generateApkSigningBlock(apkSignatureSchemeV2Block);
        }

第一步中的V2签名信息已经得到了,下面就是生成一个即将插入ZIP的V2签名块了,我们看generateApkSigningBlock方法:

private static byte[] generateApkSigningBlock(byte[] apkSignatureSchemeV2Block) {
            // FORMAT:
            // uint64:  size (excluding this field)
            // repeated ID-value pairs:
            //     uint64:           size (excluding this field)
            //     uint32:           ID
            //     (size - 4) bytes: value
            // uint64:  size (same as the one above)
            // uint128: magic
            int resultSize =
                    8 // size
                            + 8 + 4 + apkSignatureSchemeV2Block.length // v2Block as ID-value pair
                            + 8 // size
                            + 16 // magic
                    ;
            ByteBuffer result = ByteBuffer.allocate(resultSize);
            result.order(ByteOrder.LITTLE_ENDIAN);
            long blockSizeFieldValue = resultSize - 8;
            result.putLong(blockSizeFieldValue);
            long pairSizeFieldValue = 4 + apkSignatureSchemeV2Block.length;
            result.putLong(pairSizeFieldValue);
            result.putInt(APK_SIGNATURE_SCHEME_V2_BLOCK_ID);
            result.put(apkSignatureSchemeV2Block);
            result.putLong(blockSizeFieldValue);
            result.put(APK_SIGNING_BLOCK_MAGIC);
            return result.array();
        }

如上代码,最终生成一个V2签名块的格式返回。格式如下:
块长度、ID-Value、块长度、魔数。
其中签名信息的ID为0x7109871a,value值为apkSignatureSchemeV2Block里包含的数据。
到此,已经得到了一个签名块的内容,剩下的就是将这个签名块,插入到ZIP,回看sign函数:

// Sign the digests and wrap the signatures and signer info into an APK Signing Block.
            ByteBuffer apkSigningBlock = ByteBuffer.wrap(generateApkSigningBlock(signerConfigs, contentDigests));
            // Update Central Directory Offset in End of Central Directory Record. Central Directory
            // follows the APK Signing Block and thus is shifted by the size of the APK Signing Block.
            centralDirOffset += apkSigningBlock.remaining();
            eocd.clear();
            ZipUtils.setZipEocdCentralDirectoryOffset(eocd, centralDirOffset);
            // Follow the Java NIO pattern for ByteBuffer whose contents have been consumed.
            originalInputApk.position(originalInputApk.limit());
            // Reset positions (to 0) and limits (to capacity) in the ByteBuffers below to follow the
            // Java NIO pattern for ByteBuffers which are ready for their contents to be read by caller.
            // Contrary to the name, this does not clear the contents of these ByteBuffer.
            beforeCentralDir.clear();
            centralDir.clear();
            eocd.clear();
            // Insert APK Signing Block immediately before the ZIP Central Directory.
            return new ByteBuffer[] {
                    beforeCentralDir,
                    apkSigningBlock,
                    centralDir,
                    eocd,
            };

得到apkSigningBlock后,做了一些对中央目录偏移量的重新设置,最终返回一个签名的APK;

到此,整个APK签名源码中的整个流程也就分析完了,知道了最终生成的签名块格式是什么样的,在APK进行安装的时候,Android会对其进行校验,关于校验机制以及源码分析,我在下一篇在做讲解,到时候会连接进来!

上一篇 下一篇

猜你喜欢

热点阅读