美文网首页移动知识程序员
Android V2签名机制以及ApkSignerV2签名源码解

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

作者: 黄二狗V | 来源:发表于2018-06-25 17:58 被阅读375次

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

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

    • 数据摘要
    • 数字签名
    • 数字证书

    简要的说明一下:

    • 数据摘要就是对一段数据进行散列算法计算得出的一段密文数据,过程不可逆,也就是不可解密。
    • 数字签名,就是用密钥对对数据加密或者签名,私钥加密的称为签名,使用公钥才能验证,公钥加密的就称为加密,使用私钥才能解密。
    • 数字证书,是为了保护签名者公钥的有效性,应当由权威的可信机构CA颁发。

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

    V1与V2区别

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

    • V1签名只会检验第一部分的所有压缩文件,而不理会后两部分内容,缺少对APK的完整性校验,V2签名是针对整个APK进行校验(不包含签名块本身)。
    • V1中的数据摘要是基于原始未压缩文件计算的。因此在校验时,需要先解压出原始文件,这无疑是耗时的,而V2是对APK本身进行数据摘要计算,不存在解压APK的操作。

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

    V2签名机制概要

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

    V2签名块

    整个签名块的格式如下:

    • size of block,以字节数(不含此字段)计 (uint64)
    • 带 uint64 长度前缀的“ID-值”对序列:
    • size of block,以字节数计 - 与第一个字段相同 (uint64)
    • magic“APK 签名分块 42”(16 个字节)

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

    • 带长度前缀的 signed data,包含digests序列,X.509 certificates 序列,additional attributes序列
    • 带长度前缀的 signatures(带长度前缀)序列
    • 带长度前缀的 public key(SubjectPublicKeyInfo,ASN.1 DER 形式)

    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中文件内容块、中央目录、EOCD按照1MB大小分割成一些小块。
    • 计算每个小块的数据摘要,数据内容是0xa5 + 块字节长度 + 块内容。
    • 计算整体的数据摘要,数据内容是0x5a + 数据块的数量 + 每个数据块的摘要内容

    总之,就是把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会对其进行校验,关于校验机制以及源码分析,我在下一篇在做讲解,到时候会连接进来!

    相关文章

      网友评论

        本文标题:Android V2签名机制以及ApkSignerV2签名源码解

        本文链接:https://www.haomeiwen.com/subject/gsjyyftx.html