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.509certificates
序列,additional attributes
序列 - 带长度前缀的
signatures
(带长度前缀)序列 - 带长度前缀的
public key
(SubjectPublicKeyInfo,ASN.1 DER 形式)
value可能会包含多个 signer
,因为Android允许多个签名。
以上信息来自官网,我为了便于读者理解。简要的写了一下重点内容。
总结一下:一个签名块,可以包含多个ID-VALUE,APK的签名信息会存放在 ID 为 0x7109871a
的键值对里。他的内容可以包含多个签名者的签名信息,每个签名信息下包含signed data
、signatures
、public 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会对其进行校验,关于校验机制以及源码分析,我在下一篇在做讲解,到时候会连接进来!