文章目錄
  1. 1. Android Sign Study
  2. 2. Introduction
    1. 2.1. When
    2. 2.2. What
    3. 2.3. Why
    4. 2.4. Conceptions
      1. 2.4.1. 数据摘要(Message Digest)
      2. 2.4.2. 数字签名(Signature)
      3. 2.4.3. CA机构(Certification Authority)
      4. 2.4.4. 根证书
      5. 2.4.5. 数字证书(Certification)
      6. 2.4.6. keystores
    5. 2.5. How
      1. 2.5.1. apktool
      2. 2.5.2. zipalign
      3. 2.5.3. keytool
      4. 2.5.4. apksigner
    6. 2.6. Deep
  3. 3. Android Stuido and Gradle
  4. 4. APK重签实战
  5. 5. Unity实战
  6. 6. Reference
    1. 6.1. Conception Part
    2. 6.2. Knowledge Part
    3. 6.3. Other Part

Android Sign Study

这一章主要是学习Android签包相关的概念和方法。
通过采用原始签包工具(zipalign & apksigner & keytool),使用命令行的形式对于已经经过Unity打包签名的包进行重签来学习理解相关概念。

Introduction

When

Android requires that all APKs be digitally signed with a certificate before they can be installed.
Android要求所有的APK都必须通过证书签名否则不能被安装到设备上。

What

When you sign an APK, the signing tool attaches the public-key certificate to the APK. The public-key certificate serves as as a “fingerprint” that uniquely associates the APK to you and your corresponding private key.
签名可以唯一标示我们自己的APK。每个签名的APK都会有对应的公钥证书,而这个公钥证书会与我们的private key唯一对应,我们只需保证private key不泄露,就能保证我们签名的程序的唯一性。(公钥证书和私钥是由有公信力的机构分发的确保了公钥的正确性,那么与之唯一对应的私钥就保证了APK的唯一性。详情见后面)

Why

那么我们为什么需要签名了?
以下引用至Android APK签名原理及方法:

  1. 应用程序升级
    如果想无缝升级一个应用,Android系统要求应用程序的新版本与老版本具有相同的签名与包名。若包名相同而签名不同,系统会拒绝安装新版应用。

  2. 应用程序模块化
    Android系统可以允许同一个证书签名的多个应用程序在一个进程里运行,系统实际把他们作为一个单个的应用程序。此时就可以把我们的应用程序以模块的方式进行部署,而用户可以独立的升级其中的一个模块。

  3. 代码或数据共享
    Android提供了基于签名的权限机制,一个应用程序可以为另一个以相同证书签名的应用程序公开自己的功能与数据,同时其它具有不同签名的应用程序不可访问相应的功能与数据。

  4. 应用程序的可认定性
    签名信息中包含有开发者信息,在一定程度上可以防止应用被伪造。例如网易云加密对Android APK加壳保护中使用的“校验签名(防二次打包)”功能就是利用了这一点。

Conceptions

数据摘要(Message Digest)

以下内容引用至Android签名机制之—-签名过程详解
这个知识点很好理解,百度百科即可,其实他也是一种算法,就是对一个数据源进行一个算法之后得到一个摘要,也叫作数据指纹,不同的数据源,数据指纹肯定不一样,就和人一样。

消息摘要算法(Message Digest Algorithm)是一种能产生特殊输出格式的算法,其原理是根据一定的运算规则对原始数据进行某种形式的信息提取,被提取出的信息就被称作原始数据的消息摘要。
著名的摘要算法有RSA公司的MD5算法和SHA-1算法及其大量的变体。
消息摘要的主要特点有:
1)无论输入的消息有多长,计算出来的消息摘要的长度总是固定的。例如应用MD5算法摘要的消息有128个比特位,用SHA-1算法摘要的消息最终有160比特位的输出。
2)一般来说(不考虑碰撞的情况下),只要输入的原始数据不同,对其进行摘要以后产生的消息摘要也必不相同,即使原始数据稍有改变,输出的消息摘要便完全不同。但是,相同的输入必会产生相同的输出。
3)具有不可逆性,即只能进行正向的信息摘要,而无法从摘要中恢复出任何的原始消息。

可以看出数据摘要确保了数据的完整性。

数字签名(Signature)

以下内容引用至APK签名原理解析:
数字签名是非对称密钥加密技术与数字摘要技术的应用。将信息摘要用发送者的私钥加密,与原文一起传送给接收者。接收者只有用发送者的公钥才能解密被加密的信息摘要,然后接收者用相同的Hash函数对收到的原文产生一个信息摘要,与解密的信息摘要做比对。如果相同,则说明收到的信息是完整的,在传输过程中没有被修改;不同则说明信息被修改过,因此数字签名能保证信息的完整性。并且由于只有发送者才有加密摘要的私钥,所以我们可以确定信息一定是发送者发送的(发送者的身份认证)。

可以看出数字签名确保了数据的正确性(数据来源可靠性),但需要确保公钥的安全传输问题。

CA机构(Certification Authority)

以下内容引用至APK签名原理解析:
受信任的第三方,承担公钥体系中公钥的合法性检验的责任

根证书

以下内容引用至APK签名原理解析:
是未被签名的公钥证书或自签名的证书;是CA认证中心给自己颁发的证书,是信任链的起始点。安装根证书意味着对这个CA认证中心的信任。用户在使用自己的数字证书之前必须先下载根证书。

数字证书(Certification)

以下内容引用至Android签名机制之—-签名过程详解:
所谓数字证书,一般包含以下一些内容:

  1. 证书的发布机构(Issuer)
  2. 证书的有效期(Validity)
  3. 消息发送方的公钥
  4. 证书所有者(Subject)
  5. 数字签名所使用的算法
  6. 数字签名
    可以看出,数字证书其实也用到了数字签名技术。只不过要签名的内容是消息发送方的公钥,以及一些其它信息。但与普通数字签名不同的是,数字证书中签名者不是随随便便一个普通的机构,而是要有一定公信力的机构。这就好像你的大学毕业证书上签名的一般都是德高望重的校长一样。一般来说,这些有公信力机构的根证书已经在设备出厂前预先安装到了你的设备上了。所以,数字证书可以保证数字证书里的公钥确实是这个证书的所有者的,或者证书可以用来确认对方的身份。数字证书主要是用来解决公钥的安全发放问题。
    CertificationVerificationFlowChat

可以看出通过获得权威的机构获取到唯一的数字证书,这样一来解决了前面数字签名提到的公钥安全传输问题(数字证书能够确保公钥的正确性)

keystores

因为没有正确签名的应用,Android系统不会安装或者运行。所以我们通过有公信力机构得到公钥证书以及与之唯一对应的私钥,我们就能验证特定应用是否是特定私钥签名的应用()
接下来我们了解下Android签名过程中一个重要概念:
keystore存储着公信力机构分发的私钥和公钥证书。
通过使用keystore签名APK,我们能够确保APK的唯一性。

How

接下来通过使用相关签名工具来重签一个已经签过名的APK来实战学习理解签名过程。

apktool

apktool是一个帮助我们编码和反编码APK的一个工具。
这里我有一个已经签过名的APK_Signed_Aligned.apk为例进行重签:
第一步:
准备好APK和apktool:
apktoolprepare
第二步:
我们需要反编码APK:
apktooldecode
第三步:
替换APK_Signed_Aligned里的部分内容(比如替换Log之类的)然后进入刚才反编码的文件夹进行APK重编码(这时我们会得到一个重编码后APK_Unsigned_Unaligned.apk):
apktoolbuild
Note:
In order to run a rebuilt application. You must resign the application. Android documentation can help with this.
此时的APK_Unsigned_Unaligned.apk是处于未签名也未进行压缩数据对齐处理(后续会讲到)。

zipalign

zipalign is an archive alignment tool that provides important optimization to Android application (APK) files. The purpose is to ensure that all uncompressed data starts with a particular alignment relative to the start of the file.
zipalign是一个对于APK压缩数据进行对齐处理的工具。
第四步:
对APK_Unsigned_Unaligned.apk进行压缩数据对齐处理得到压缩数据对齐的APK_Unsigned_Aligned.apk:
zipalignalignment

Note:
zipalign位于Android SDK的build tools目录下。

keytool

Manages a keystore (database) of cryptographic keys, X.509 certificate chains, and trusted certificates.
keytool这里可以理解成有公信力的机构(工具)帮助我们生成唯一的公共证书和与之对应的私钥。

第五步:
在对我们进行压缩数据对齐后的APK_Unsigned_Aligned.apk进行重签之前,我们需要生成自己的keystore用于签名认证我们的APK。
使用keytool生成我们需要的公共证书和与之对应的私钥:
keytoolgenerakeystore
上面要填的信息就是之前在数字证书里提到的生成公共秘钥和私钥需要的信息。
最后得到我们的TonyTangKeyStore.jks(签名需要的keystore,公共秘钥和私钥都在里面)
keystore
接下来我们通过keytool查看我们刚生成的keystore信息(因为不是在同一台电脑上测试学习KeyStore,所以路径并不一致):
KeyStoreInfo
可以看到我们的Keystore是由Sun公司提供的,采用SHA1(前面提到的数字摘要算法之一e.g. MD5 or SHA1)算法计算私钥的数字摘要。

Note:
keytool位于JDK的bin目录下。
上面输入的Keystore密码后续签包时会用到。
上面生成keystore默认只有90天有效期,在开发过程中我们要保证keystore有效期远大于软件生命周期,不然期限到了就无法在继续使用同一个keystore,也就不能保证APK的唯一性了。
keytool貌似有个参数-validity是决定有效期的,这里就没有尝试了,详情参考keytool官网。

apksigner

第六步:
得到了keystore,那么我们就可以进行签名工作了。
使用apksigner进行签包:
apksignersigne
通过apksigner验证特定APK是否签名:
apksignerverify
最终我们得到了我们重新重签后的APK_Signed_Aligned.apk(与我们使用的keystore里的私钥唯一对应)

Note:
The apksigner tool, available in revision 24.0.3 and higher of the Android SDK Build Tools
apksigner工具在AndroidSDK 24.0.3的Build Tool才可用。
其他签名工具还有jarsigner和signapk。

Deep

深入学习理解签包里的相关文件:
这里主要参考别人的学习理解Android签名机制之—-签名过程详解
通过上文我们可以看出签名后验证APK的相关文件主要有以下三个文件:

  1. MANIFEST.MF
  2. CERT.SF
  3. CERT.RSA

打开MANIFEST.MF(以下只复制了很少一部分做验证使用):

1
2
3
4
5
6
7
8
Manifest-Version: 1.0
Created-By: 1.0 (Android)

Name: assets/bin/Data/Managed/System.Core.dll
SHA1-Digest: cBOiG0b7uqe/+kFj8qkmBvP2/zw=

Name: AndroidManifest.xml
SHA1-Digest: JYiwTP2AVjbfgZVkMuuhe/bPrf0=

可以看出MANIFEST.MF文件记录了APK里文件内容的SHA1-Digest(数字摘要),这个数字摘要通过上面博主找到的源码来看是通过对文件内容做一次SHA1算法后然后通过Base64 Encode得到的。

下面我们直接通过HashTable和Base64网站验证一下(这里以AndroidManifest.xml为例):
AndroidManifestHashValue
因为我们使用的是SHA1数字摘要算法,所以这里用AndroidManifest.xml的SHA1值ECFB805F9645FB3CDB7B29DB4529B6FD7545261D进行反推。
通过Base64 Encode后:
SHA1AfterBase64
把Base64 Encode后的值和我们在MANIFEST.MF里AndroidManifest.xml的SHA1-Digest进行对比,就会发现是一致的。

这里不再对三个文件做深入研究,上面那位博主已经做了很清晰的解释,详情参见Android签名机制之—-签名过程详解
这里只记录下三个文件之间的关系以及在APK简明里起的作用做个总结:

  1. MANIFEST.MF(对APK里的部分文件通过SHA1和Base64 Encode后记录下对应的数字摘要,用于验证APK文件的完整性)
  2. CERT.SF(对MANIFEST.MF文件进行一次SHA1和Basee64 Encode进行记录,并针对MANIFEST.MF里每一块进行SHA1和Base64 Encode并记录,用于验证MANIFEST.MF的完整性)
  3. CERT.RSA(这里会包含我们通过keystore生成的私钥计算出的签名以及公钥信息等数字证书信息,用于验证APK的唯一性也就是我们一直强调的签名来源)

那么上面三个文件就能确保APK的唯一性和安全性了吗?
一下来自Android签名机制之—-签名过程详解的总结:
首先,如果你改变了apk包中的任何文件,那么在apk安装校验时,改变后的文件摘要信息与MANIFEST.MF的检验信息不同,于是验证失败,程序就不能成功安装。
其次,如果你对更改的过的文件相应的算出新的摘要值,然后更改MANIFEST.MF文件里面对应的属性值,那么必定与CERT.SF文件中算出的摘要值不一样,照样验证失败。
最后,如果你还不死心,继续计算MANIFEST.MF的摘要值,相应的更改CERT.SF里面的值,那么数字签名值必定与CERT.RSA文件中记录的不一样,还是失败。
那么能不能继续伪造数字签名呢?不可能,因为没有数字证书对应的私钥。
所以,如果要重新打包后的应用程序能再Android设备上安装,必须对其进行重签名。

从上面的分析可以得出,只要修改了Apk中的任何内容,就必须重新签名,不然会提示安装失败,当然这里不会分析,后面一篇文章会注重分析为何会提示安装失败。

从上面的总结可以看出,Keystore的Private Key是作为我们APK签名安全的最后一道防线,确保数字签名不会被伪造。

Android Stuido and Gradle

在开始实战APK重签之前,这里需要先学习了解Android Studio和Gradle,这一步可以帮助我们把java代码打包成jar来使用。
详情参考Android Studio

APK重签实战

通过上面的学习,我们知道了APK的重签需要经过一下步骤:

  1. 反解APK
  2. 修改内容
  3. 重新打包APK
  4. APK数据对齐
  5. 重新签名APK

接下来我们以实现以下几个功能作为实战重签APK的学习目标:

  1. 修改AndroidManifest.xml内容(通过修改versioncode打印检验)
  2. 替换APK里的资源(替换streamingasset路径下的一个文本文件内容并打印检验)
  3. 替换签名重签APK(通过apksigner验证apk重签后的签名)

我将会结合前面提到的工具利用Python完成这一次自动化重签功能学习:
直接上学习代码:
resignAPK.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
#!/usr/bin/python
# -*- coding: utf-8 -*-
# 指定文件包含非anscii字符

import os
import sys
import string
import shutil
import re
import argparse
import time

## Decompile APK(反编译APK)
def DecompileAPK():
print("DecompileAPK()")
apttooldecompilecmd = "{}/apktool.bat -f d {}.apk".format(apktoolDir, apkName)
print("------excute cmd :{}".format(apttooldecompilecmd))
os.system(apttooldecompilecmd)

## Modify AndroidManifest File(修改AndroidMainifest.xml)
def ModifyAndroidManifest():
print("ModifyAndroidManifest()")
lines = []
androidManifestFile = open(decompileAndroidManifestPath, mode='r')
for line in androidManifestFile:
line = ReplaceVersionCode(line, newVersionCode)
lines.append(line)
androidManifestFile.close()

androidManifestFile = open(decompileAndroidManifestPath, mode='w')
for line in lines:
androidManifestFile.write(line)
androidManifestFile.close()

## Template Replace(模板替换AndroidManifest.xml里的值)
def CheckTemplateReplace(line, qct, npn):
# Python 2.X string.find
#result = string.find(line, qct)
# Python 3.X str.find
result = line.find(qct)
if result != -1:
line = line.replace(qct, npn)
return line

## Modify Version Code Value(修改AndroidManifest.xml里的version code值)
## 实际使用过程发现apktool反解APK后AndroidManifest.xmnl里没有对应versioncode值
## 但以下代码如果存在versioncode="*"的话是能够替换成功的
def ReplaceVersionCode(line, newverioncode):
# Python 2.X string.find
#result = string.find(line, versionCodeTemplate)
# Python 3.X str.find
result = line.find(versionCodeTemplate)
if result != -1:
line = re.sub('(versionCode="[0-9]*")', "versioncode=\"{}\"".format(newverioncode), line, 1)
return line

## Copy new resource to APK(复制新资源到包内)
def CopyNewResourceToAPK():
print("CopyNewResourceToAPK()")
shutil.copy(newResourceName, "{}".format(replaceResourceDestinationPath))

## Recompile APK(重新打包APK)
def RecompileAPK():
print("RecompileAPK()")

print("------os.chdir(./{})".format(apkName))

os.chdir("./{}".format(apkName))

retval = os.getcwd()

print("------Current working directory {}".format(retval))

apttoolrecompilecmd = "{}/apktool.bat b -o ../{}.apk".format(apktoolDir, apkUnsignedUnaglinedName)

print("------excute cmd :{}".format(apttoolrecompilecmd))

os.system(apttoolrecompilecmd)

print("------os.chdir(./..)")

os.chdir("./..")

retval = os.getcwd()

print("Current working directory {}".format(retval))

## Aligned APK(APK 4K对齐)
def AlignedAPK():
print("AlignedAPK()")

zalignedcmd = "{}\\build-tools\\24.0.3\\zipalign.exe -f 4 {}.apk {}.apk".format(androidSDKDir, apkUnsignedUnaglinedName, apkUnsignedAglinedName)

print("------zalignedcmd :{}".format(zalignedcmd))

os.system(zalignedcmd)

## Resigned APK(重签APK)
def ResignedAPK():
print("ResignedAPK()")

resignedcmd = "{}\\build-tools\\24.0.3\\apksigner.bat sign --ks {} --ks-pass pass:{} --out {}.apk {}.apk".format(androidSDKDir, keystoreFilename, keystorePassword, apkResignedName, apkUnsignedAglinedName)

print("------resignedcmd :{}".format(resignedcmd))

os.system(resignedcmd)

## Repackage process
# argparse解析参数输入,需要先定义一个ArgumentParser对象
# description - -h的时候显示在最前面的信息
parser = argparse.ArgumentParser(description='Study argparse module.')

# add_argument - 定义如何解析一个参数
# 第一个参数代表参数名
# type - 指定参数数据类型
# help - -h时解释该参数的用意
# dest - 表示该参数最终在parser.parse_args()返回对象里的名字
# default - 表示该参数的默认值
# required - 表示该参数是不是必须填的参数
parser.add_argument('--APKName', type=str,
help='APK名字', dest='APKName',
required=True)

parser.add_argument('--VersionCode', type=int,
help='新的VersionCode', dest='NewVersionCode',
required=True)

parser.add_argument('--ResourceName', type=str,
help='新资源名字', dest='NewResourceName',
default='default.aba')

parser.add_argument('--ResourcePath', type=str,
help='新资源包内目录', dest='NewResourcePath',
default='./assets/')

# 解析输入的参数
args = parser.parse_args()

# 打印参数的详细信息,通过访问parser.parse_args()的对象去访问
print(args)
apkName = args.APKName
print("apkName : {0}".format(apkName))
newVersionCode = args.NewVersionCode
print("newVersionCode : {0}".format(newVersionCode))
newResourceName = args.NewResourceName
print("newResourceName : {0}".format(newResourceName))
newResourcePath = args.NewResourcePath
print("newResourcePath : {0}".format(newResourcePath))

currentFolder = os.getcwd()
print("currentFolder : {0}".format(currentFolder))

apktoolDir = currentFolder
print("apktoolDir : {0}".format(apktoolDir))

androidSDKDir = os.getenv("AndroidSDK")
print("androidSDKDir : {}".format(androidSDKDir))
if androidSDKDir == None:
print("androidSDKDir == None,请配置ANdroidSDK环境变量路径")
time.sleep(150)
exit()
else:
print("androidSDKDir != None")

# version code替换原模板
versionCodeTemplate = "versionCode="
print("versionCodeTemplate = {}".format(versionCodeTemplate))

# 需要替换的资源目录
replaceResourcePath = "./{}/{}".format(apkName, newResourcePath)

print("replaceResourcePath = {}".format(replaceResourcePath))

keystoreFilename = "TonyTangNewKeyStore.jks"

print("keystoreFilename = {}".format(keystoreFilename))

keystorePassword = "th13568582998"

print("keystorePassword = {}".format(keystorePassword))

decompileFolderPath = "{}".format(apkName)

print("decompileFolderPath = {}".format(decompileFolderPath))

decompileAndroidManifestPath = "./{}/AndroidManifest.xml".format(decompileFolderPath)

print("decompileAndroidManifestPath = {}".format(decompileAndroidManifestPath))

replaceResourceDestinationPath = "{}{}".format(replaceResourcePath, newResourceName)

print("replaceResourceDestinationPath = {}".format(replaceResourceDestinationPath))

apkUnsignedUnaglinedName = "{}_Unsinged_Unaligned".format(apkName)

print("apkUnsignedUnaglinedName = {}".format(apkUnsignedUnaglinedName))

apkUnsignedAglinedName = "{}_Unsigned_Aligned".format(apkName)

print("apkUnsignedAglinedName = {}".format(apkUnsignedAglinedName))

apkResignedName = "{}_Signed_Aligned".format(apkName)

print("apkResignedName = {}".format(apkResignedName))

print("{}.apk resigned start".format(apkName))

# 反解APK
DecompileAPK()

# 修改AndroidMainifest.xml里面的内容
ModifyAndroidManifest()

# 复制资源到指定目录
CopyNewResourceToAPK()

# 重新打包APK
RecompileAPK()

# APK数据对齐
AlignedAPK()

# 重新签名APK
ResignedAPK()

# 暂停不关闭命令行窗口(仅限Windows)
os.system('pause')

上面的注释都比较详细了,就不再细说每一步了。
这里直接来看下运行这个resignAPK.py的环境要求以及目录结构:
AndroidResignAPKFolderStructure
以下是使用argparse作为参数解析库的帮助查看:
AndroidResignAPKArgParse
以下是正式使用重签resignAPK.py用法:
AndroidResignAPKCommandsUsing
最后输出了一个叫apkName_Signed_Aligned的APK使我们最终重签后的APK:
AndroidResignFinalAPK
ABTestFile.text文件是位于StreamingAsset目录下的一个文件,原文是test,我覆盖后内容是test2。
配合测试访问代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/*
* Description: ResourceManager.cs
* Author: TONYTANG
* Create Date: 2018/08/12
*/

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// ResourceManager.cs
/// 资源加载管理单例类
/// </summary>
public class ResourceManager : SingletonTemplate<ResourceManager> {

/// <summary>
/// AB资源路径
/// </summary>
public string ABPath
{
get;
private set;
}
private string mABPath = Application.streamingAssetsPath;

/// <summary>
/// 测试资源文件名
/// </summary>
private string mTestResourceFileName = "/ABTestFile.text";

/// <summary>
/// 加载AB测试资源
/// </summary>
public void loadABTestResource()
{
Debug.Log("loadABTestResource()");
CoroutineManager.Singleton.startCoroutine(loadTestResourceCoroutine());
}

/// <summary>
/// 加载临时资源携程
/// </summary>
/// <returns></returns>
IEnumerator loadTestResourceCoroutine()
{
Debug.Log("loadTestResourceCoroutine()");
var testresourcefullpath = mABPath + mTestResourceFileName;
#if UNITY_ANDROID && !UNITY_EDITOR
UnityEngine.Networking.UnityWebRequest www = UnityEngine.Networking.UnityWebRequest.Get(testresourcefullpath);
yield return www.SendWebRequest();
Debug.Log(string.Format("ABTestFile Content : {0}", www.downloadHandler.text));
#else
var testresource = System.IO.File.ReadAllText(testresourcefullpath);
Debug.Log(string.Format("ABTestFile Content : {0}", testresource));
yield return null;
#endif
}
}

AndroidResignAndReplaceResourceDynamically
至此我们成功完成了通过python编写了自动化重签APK,动态替换APK部分资源,动态修改AndroidManifest.xml内容的功能。

Unity实战

待续……

Reference

Conception Part

keytool
zipalign
apksigner
Apktool
Android Studio
Gradle
Gradle Wiki
如何通俗地理解 Gradle?
Build Script Basics
Authoring Tasks

Knowledge Part

Sign Your App
Android APK签名原理及方法
Android签名机制之—-签名过程详解
APK签名原理解析
JNI和NDK交叉编译进阶学习
NDK&JNI&Java&Android初步学习总结归纳
Android DSL Android Plugin DSL Reference
Gradle - Building Android Apps

Other Part

HashTable
Base64Website

文章目錄
  1. 1. Android Sign Study
  2. 2. Introduction
    1. 2.1. When
    2. 2.2. What
    3. 2.3. Why
    4. 2.4. Conceptions
      1. 2.4.1. 数据摘要(Message Digest)
      2. 2.4.2. 数字签名(Signature)
      3. 2.4.3. CA机构(Certification Authority)
      4. 2.4.4. 根证书
      5. 2.4.5. 数字证书(Certification)
      6. 2.4.6. keystores
    5. 2.5. How
      1. 2.5.1. apktool
      2. 2.5.2. zipalign
      3. 2.5.3. keytool
      4. 2.5.4. apksigner
    6. 2.6. Deep
  3. 3. Android Stuido and Gradle
  4. 4. APK重签实战
  5. 5. Unity实战
  6. 6. Reference
    1. 6.1. Conception Part
    2. 6.2. Knowledge Part
    3. 6.3. Other Part