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會對其進行校驗,關于校驗機制以及源碼分析,我在下一篇在做講解,到時候會連接進來!