APKSignTools.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. #!/usr/bin/python
  2. # -*- coding: utf-8 -*-
  3. from logging import debug, root
  4. from struct import pack
  5. from Common import Setting, Debug, PathConvert, StringFormat
  6. Setting.SetDefaultencodingUTF8()
  7. import zipfile, sys, os, traceback
  8. import shutil
  9. import platform
  10. import subprocess
  11. import ConfigParser
  12. CUR_PLATFORM_NAME = platform.system()
  13. CUR_PATH = os.path.abspath(sys.path[0])
  14. APK_TOOLS_PATH = os.path.abspath(CUR_PATH + '/../APKTools')
  15. APK_TOOL_JAR_PATH = os.path.abspath(APK_TOOLS_PATH + '/apktool.jar')
  16. APK_SIGNER_JAR_PATH = os.path.abspath(APK_TOOLS_PATH + '/apksigner.jar')
  17. if CUR_PLATFORM_NAME == 'Windows':
  18. ZIP_ALIGN_PATH = os.path.abspath(APK_TOOLS_PATH + '/zipalign.exe')
  19. elif CUR_PLATFORM_NAME == 'Linux':
  20. ZIP_ALIGN_PATH = os.path.abspath(APK_TOOLS_PATH + '/zipalign.exe')
  21. elif CUR_PLATFORM_NAME == 'Darwin':
  22. ZIP_ALIGN_PATH = os.path.abspath(APK_TOOLS_PATH + '/zipalign_mac')
  23. else:
  24. ZIP_ALIGN_PATH = os.path.abspath(APK_TOOLS_PATH + '/zipalign.exe')
  25. VALID_CMD_ARGV = {"-help", "-checkSign", "-checkSignVer",
  26. "-sign", "-signWithIni", "-outPath", "-inPath", "-iniPath",
  27. "-keyStorePath", "-keyAlias", "-keyStorePwd", "-keyAliasPwd",
  28. "-keySignVer"}
  29. def PrintHelp():
  30. Debug.Log('')
  31. Debug.Log('')
  32. Debug.Log('命令参数说明')
  33. Debug.Log('')
  34. Debug.Log('-help 查看帮助')
  35. Debug.Log('')
  36. Debug.Log('')
  37. Debug.Log('-checkSign 查看签名')
  38. Debug.Log(' -inPath 原始apk路径')
  39. Debug.Log('')
  40. Debug.Log('')
  41. Debug.Log('-checkSignVer 查看签名版本')
  42. Debug.Log(' -inPath 原始apk路径')
  43. Debug.Log('')
  44. Debug.Log('')
  45. Debug.Log('-signWithIni 使用证书配置来签名')
  46. Debug.Log(' -inPath 原始apk路径')
  47. Debug.Log(' -outPath 目标apk路径')
  48. Debug.Log(' -iniPath 签名配置')
  49. Debug.Log('')
  50. Debug.Log('')
  51. Debug.Log('-sign 重新签名')
  52. Debug.Log(' -inPath 原始apk路径')
  53. Debug.Log(' -outPath 目标apk路径')
  54. Debug.Log(' -keyStorePath 签名证书路径')
  55. Debug.Log(' -keyAlias 签名证书alias')
  56. Debug.Log(' -keyStorePwd 签名证书密码')
  57. Debug.Log(' -keyAliasPwd 签名证书alias密码')
  58. Debug.Log(' -keySignVer (可选)签名版本, 默认为v1')
  59. Debug.Log('')
  60. Debug.Log('')
  61. Debug.Log('')
  62. def ValidJava():
  63. try:
  64. cmds = ["java", "-version"]
  65. sp = subprocess.Popen(cmds, stdout=subprocess.PIPE,
  66. stderr=subprocess.PIPE)
  67. contents = sp.communicate()
  68. sp.wait()
  69. version = None
  70. for content in contents:
  71. lines = content.split("\n")
  72. for i in range(len(lines)):
  73. line = lines[i].strip()
  74. if line.startswith("java version "):
  75. version = line.replace("java version ", "")
  76. version = version.replace("\"", "")
  77. if not version:
  78. Debug.LogError("无法检测JDK版本 ")
  79. return False
  80. else:
  81. if version.startswith("1.8"):
  82. return True
  83. else:
  84. Debug.LogError("JDK版本不符,需要使用1.8版本 当前版本 " + version)
  85. return False
  86. except Exception as exc:
  87. Debug.LogError(exc)
  88. Debug.Log("")
  89. Debug.LogError("无法找到有效JDK, 请检查环境 !!! ")
  90. return False
  91. return True
  92. def ToolsChmodEXE():
  93. if CUR_PLATFORM_NAME != 'Linux' or CUR_PLATFORM_NAME != 'Darwin':
  94. return
  95. Debug.LogSuccess("赋予%s目录可执行权限开始" % APK_TOOLS_PATH)
  96. import stat
  97. for root, dirs, files in os.walk(APK_TOOLS_PATH):
  98. for file in files:
  99. os.chmod(file, os.stat(file).st_mode | stat.S_IEXEC)
  100. Debug.LogSuccess("赋予%s目录可执行权限成功" % APK_TOOLS_PATH)
  101. def ExecCheckSign(inputPath):
  102. if not os.path.exists(StringFormat.FormatStringPlatform(inputPath)):
  103. Debug.LogError(inputPath + "不存在")
  104. Debug.Log("")
  105. return False
  106. try:
  107. cmds = ["keytool", "-list",
  108. "-printcert",
  109. "-jarfile",
  110. StringFormat.FormatStringPlatform(inputPath)]
  111. res = subprocess.check_output(cmds, shell=False)
  112. res = StringFormat.FormatStdinString(res)
  113. Debug.Log(res)
  114. except Exception as exc:
  115. Debug.LogError(exc)
  116. Debug.Log("")
  117. return False
  118. return True
  119. def ExecCheckSignVer(inputPath):
  120. if not os.path.exists(StringFormat.FormatStringPlatform(inputPath)):
  121. Debug.LogError(inputPath + "不存在")
  122. Debug.Log("")
  123. return False
  124. try:
  125. cmds = ["java", "-jar", APK_SIGNER_JAR_PATH,
  126. "verify",
  127. "--verbose",
  128. StringFormat.FormatStringPlatform(inputPath)]
  129. res = subprocess.check_output(cmds, shell=False)
  130. res = StringFormat.FormatStdinString(res)
  131. Debug.Log(res)
  132. except Exception as exc:
  133. Debug.LogError(exc)
  134. Debug.Log("")
  135. return False
  136. return True
  137. def ExecSignWithIni(inputPath, outPath, iniPath):
  138. if not os.path.exists(StringFormat.FormatStringPlatform(iniPath)):
  139. Debug.LogError(iniPath + "不存在")
  140. Debug.Log("")
  141. return
  142. config = ConfigParser.ConfigParser()
  143. config.read(StringFormat.FormatStringPlatform(iniPath))
  144. keyAliasD = config.get('signinfodefault', 'keyAlias')
  145. keyStorePwdD = config.get('signinfodefault', 'keyStorePwd')
  146. keyAliasPwdD = config.get('signinfodefault', 'keyAliasPwd')
  147. keySignVerD = config.get('signinfodefault', 'keySignVer')
  148. keyStorePathD = config.get('signinfodefault', 'keyStorePath')
  149. if not os.path.exists(StringFormat.FormatStringPlatform(keyStorePathD)):
  150. if '../' in keyStorePathD:
  151. keyStorePathDTemp = os.path.abspath(os.path.dirname(iniPath) + "/" + keyStorePathD)
  152. if os.path.exists(StringFormat.FormatStringPlatform(keyStorePathDTemp)):
  153. keyStorePathD = keyStorePathDTemp
  154. ExecSign(inputPath, outPath,
  155. keyStorePathD, keyAliasD, keyStorePwdD, keyAliasPwdD,
  156. keySignVerD)
  157. def ExecSign(inputPath, outPath,
  158. keyStorePath, keyAlias, keyStorePwd, keyAliasPwd,
  159. keySignVer):
  160. if not os.path.exists(StringFormat.FormatStringPlatform(inputPath)):
  161. Debug.LogError(inputPath + "不存在")
  162. Debug.Log("")
  163. return False
  164. if not outPath or outPath == "":
  165. Debug.LogError(outPath + "目标目录无效")
  166. Debug.Log("")
  167. return False
  168. if not os.path.exists(StringFormat.FormatStringPlatform(keyStorePath)):
  169. Debug.LogError(keyStorePath + "不存在")
  170. Debug.Log("")
  171. return False
  172. outTempPath = outPath + "_temp.apk"
  173. ExecSignInternal(inputPath, outPath,
  174. keyStorePath, keyAlias, keyStorePwd, keyAliasPwd,
  175. keySignVer, outTempPath)
  176. if os.path.exists(StringFormat.FormatStringPlatform(outTempPath)):
  177. os.remove(StringFormat.FormatStringPlatform(outTempPath))
  178. if os.path.exists(StringFormat.FormatStringPlatform(outPath + ".idsig")):
  179. os.remove(StringFormat.FormatStringPlatform(outPath + ".idsig"))
  180. def ExecSignInternal(inputPath, outPath,
  181. keyStorePath, keyAlias, keyStorePwd, keyAliasPwd,
  182. keySignVer, outTempPath):
  183. if keySignVer:
  184. if isinstance(keySignVer, str):
  185. if len(keySignVer) > 1:
  186. if (keySignVer[:1] == 'v' or keySignVer == 'V'):
  187. keySignVer = keySignVer[1:]
  188. keySignVer = int(keySignVer)
  189. else:
  190. keySignVer = 1
  191. try:
  192. #删除当前包的证书
  193. with zipfile.ZipFile(StringFormat.FormatStringPlatform(inputPath)) as inFile:
  194. with zipfile.ZipFile(StringFormat.FormatStringPlatform(outTempPath), 'w') as outFile:
  195. for data in inFile.infolist():
  196. if not data.filename.startswith("META-INF"):
  197. outFile.writestr(data, inFile.read(data.filename))
  198. # zip对齐apk
  199. Debug.Log("zip对齐apk")
  200. Debug.Log("")
  201. cmds = [StringFormat.FormatStringPlatform(ZIP_ALIGN_PATH),
  202. "-p", "-f", "4",
  203. StringFormat.FormatStringPlatform(outTempPath),
  204. StringFormat.FormatStringPlatform(outPath)]
  205. res = subprocess.Popen(cmds, shell=False, stdout=subprocess.PIPE)
  206. res.wait()
  207. if res.returncode != 0:
  208. Debug.LogError("无法zip对齐APK " + outPath)
  209. return False
  210. # 签名apk
  211. Debug.Log("签名apk")
  212. Debug.Log("")
  213. cmds = ["java", "-jar", StringFormat.FormatStringPlatform(APK_SIGNER_JAR_PATH), "sign",
  214. "--ks", StringFormat.FormatStringPlatform(keyStorePath), "--ks-key-alias", keyAlias,
  215. "--ks-pass", "pass:" + keyStorePwd, "--key-pass", "pass:" + keyAliasPwd]
  216. cmds.append('--v4-signing-enabled')
  217. if keySignVer >= 4:
  218. cmds.append('true')
  219. else:
  220. cmds.append('false')
  221. cmds.append('--v3-signing-enabled')
  222. if keySignVer >= 3:
  223. cmds.append('true')
  224. else:
  225. cmds.append('false')
  226. cmds.append('--v2-signing-enabled')
  227. if keySignVer >= 2:
  228. cmds.append('true')
  229. else:
  230. cmds.append('false')
  231. cmds.append('--v1-signing-enabled')
  232. cmds.append('true')
  233. cmds.append(StringFormat.FormatStringPlatform(outPath))
  234. res = subprocess.Popen(cmds, shell=False, stdout=subprocess.PIPE)
  235. res.wait()
  236. if res.returncode != 0:
  237. Debug.LogError("无法签名APK " + outPath)
  238. return False
  239. Debug.LogSuccess("执行成功" + inputPath + " -> " + outPath)
  240. Debug.Log("")
  241. Debug.Log("")
  242. return True
  243. except Exception as exc:
  244. Debug.LogError(exc)
  245. Debug.Log("")
  246. return False
  247. def CMDArgvIsNull(dic, keyName):
  248. if (not (keyName in dic)):
  249. PrintHelp()
  250. Debug.Log('')
  251. Debug.Log('')
  252. Debug.LogError(keyName + ' 不能为空')
  253. Debug.Log('')
  254. Debug.Log('')
  255. return True
  256. return False
  257. def Main():
  258. args = sys.argv[1:]
  259. dic = {}
  260. num = len(args)
  261. for i in range(num):
  262. arg = StringFormat.FormatStdinString(args[i])
  263. if arg.startswith('-'):
  264. if not arg in VALID_CMD_ARGV:
  265. Debug.LogError(arg + " 不是有效的参数")
  266. PrintHelp()
  267. return
  268. if i + 1 >= num:
  269. Debug.LogSuccess(arg)
  270. dic[arg] = ''
  271. else:
  272. nextArg = StringFormat.FormatStdinString(args[i + 1])
  273. if nextArg.startswith('-'):
  274. Debug.LogSuccess(arg)
  275. dic[arg] = ''
  276. else:
  277. Debug.LogSuccess(arg + " " + nextArg)
  278. dic[arg] = nextArg
  279. if len(dic) <= 0:
  280. PrintHelp()
  281. return
  282. if ('-help' in dic):
  283. PrintHelp()
  284. return
  285. if not ValidJava():
  286. return
  287. ToolsChmodEXE()
  288. if (CMDArgvIsNull(dic, '-inPath')):
  289. return
  290. if ('-checkSign' in dic):
  291. ExecCheckSign(dic['-inPath'])
  292. return
  293. if ('-checkSignVer' in dic):
  294. ExecCheckSignVer(dic['-inPath'])
  295. return
  296. if (CMDArgvIsNull(dic, '-outPath')):
  297. return
  298. if ('-signWithIni' in dic):
  299. if (CMDArgvIsNull(dic, '-iniPath')):
  300. return
  301. ExecSignWithIni(dic['-inPath'], dic['-outPath'], dic['-iniPath'])
  302. return
  303. return
  304. if (CMDArgvIsNull(dic, '-keyStorePath')):
  305. return
  306. if (CMDArgvIsNull(dic, '-keyAlias')):
  307. return
  308. if (CMDArgvIsNull(dic, '-keyStorePwd')):
  309. return
  310. if (CMDArgvIsNull(dic, '-keyAliasPwd')):
  311. return
  312. Debug.Log('')
  313. Debug.Log('')
  314. keySignVer = None
  315. if ('-keySignVer' in dic):
  316. keySignVer = dic['-keySignVer']
  317. ExecSign(dic['-inPath'], dic['-outPath'],
  318. dic['-keyStorePath'], dic['-keyAlias'], dic['-keyStorePwd'], dic['-keyAliasPwd'],
  319. keySignVer)
  320. if __name__ == '__main__':
  321. try:
  322. Main()
  323. except Exception as e:
  324. Debug.LogError(e)
  325. Debug.LogError(traceback.format_exc(), True)
  326. sys.exit(1)