#!/usr/bin/python # -*- coding: utf-8 -*- from logging import debug, root from struct import pack from Common import Setting, Debug, PathConvert, StringFormat Setting.SetDefaultencodingUTF8() import zipfile, sys, os, traceback import shutil import platform import subprocess import ConfigParser CUR_PLATFORM_NAME = platform.system() CUR_PATH = os.path.abspath(sys.path[0]) APK_TOOLS_PATH = os.path.abspath(CUR_PATH + '/../APKTools') APK_TOOL_JAR_PATH = os.path.abspath(APK_TOOLS_PATH + '/apktool.jar') APK_SIGNER_JAR_PATH = os.path.abspath(APK_TOOLS_PATH + '/apksigner.jar') if CUR_PLATFORM_NAME == 'Windows': ZIP_ALIGN_PATH = os.path.abspath(APK_TOOLS_PATH + '/zipalign.exe') elif CUR_PLATFORM_NAME == 'Linux': ZIP_ALIGN_PATH = os.path.abspath(APK_TOOLS_PATH + '/zipalign.exe') elif CUR_PLATFORM_NAME == 'Darwin': ZIP_ALIGN_PATH = os.path.abspath(APK_TOOLS_PATH + '/zipalign_mac') else: ZIP_ALIGN_PATH = os.path.abspath(APK_TOOLS_PATH + '/zipalign.exe') VALID_CMD_ARGV = {"-help", "-checkSign", "-checkSignVer", "-sign", "-signWithIni", "-outPath", "-inPath", "-iniPath", "-keyStorePath", "-keyAlias", "-keyStorePwd", "-keyAliasPwd", "-keySignVer"} def PrintHelp(): Debug.Log('') Debug.Log('') Debug.Log('命令参数说明') Debug.Log('') Debug.Log('-help 查看帮助') Debug.Log('') Debug.Log('') Debug.Log('-checkSign 查看签名') Debug.Log(' -inPath 原始apk路径') Debug.Log('') Debug.Log('') Debug.Log('-checkSignVer 查看签名版本') Debug.Log(' -inPath 原始apk路径') Debug.Log('') Debug.Log('') Debug.Log('-signWithIni 使用证书配置来签名') Debug.Log(' -inPath 原始apk路径') Debug.Log(' -outPath 目标apk路径') Debug.Log(' -iniPath 签名配置') Debug.Log('') Debug.Log('') Debug.Log('-sign 重新签名') Debug.Log(' -inPath 原始apk路径') Debug.Log(' -outPath 目标apk路径') Debug.Log(' -keyStorePath 签名证书路径') Debug.Log(' -keyAlias 签名证书alias') Debug.Log(' -keyStorePwd 签名证书密码') Debug.Log(' -keyAliasPwd 签名证书alias密码') Debug.Log(' -keySignVer (可选)签名版本, 默认为v1') Debug.Log('') Debug.Log('') Debug.Log('') def ValidJava(): try: cmds = ["java", "-version"] sp = subprocess.Popen(cmds, stdout=subprocess.PIPE, stderr=subprocess.PIPE) contents = sp.communicate() sp.wait() version = None for content in contents: lines = content.split("\n") for i in range(len(lines)): line = lines[i].strip() if line.startswith("java version "): version = line.replace("java version ", "") version = version.replace("\"", "") if not version: Debug.LogError("无法检测JDK版本 ") return False else: if version.startswith("1.8"): return True else: Debug.LogError("JDK版本不符,需要使用1.8版本 当前版本 " + version) return False except Exception as exc: Debug.LogError(exc) Debug.Log("") Debug.LogError("无法找到有效JDK, 请检查环境 !!! ") return False return True def ToolsChmodEXE(): if CUR_PLATFORM_NAME != 'Linux' or CUR_PLATFORM_NAME != 'Darwin': return Debug.LogSuccess("赋予%s目录可执行权限开始" % APK_TOOLS_PATH) import stat for root, dirs, files in os.walk(APK_TOOLS_PATH): for file in files: os.chmod(file, os.stat(file).st_mode | stat.S_IEXEC) Debug.LogSuccess("赋予%s目录可执行权限成功" % APK_TOOLS_PATH) def ExecCheckSign(inputPath): if not os.path.exists(StringFormat.FormatStringPlatform(inputPath)): Debug.LogError(inputPath + "不存在") Debug.Log("") return False try: cmds = ["keytool", "-list", "-printcert", "-jarfile", StringFormat.FormatStringPlatform(inputPath)] res = subprocess.check_output(cmds, shell=False) res = StringFormat.FormatStdinString(res) Debug.Log(res) except Exception as exc: Debug.LogError(exc) Debug.Log("") return False return True def ExecCheckSignVer(inputPath): if not os.path.exists(StringFormat.FormatStringPlatform(inputPath)): Debug.LogError(inputPath + "不存在") Debug.Log("") return False try: cmds = ["java", "-jar", APK_SIGNER_JAR_PATH, "verify", "--verbose", StringFormat.FormatStringPlatform(inputPath)] res = subprocess.check_output(cmds, shell=False) res = StringFormat.FormatStdinString(res) Debug.Log(res) except Exception as exc: Debug.LogError(exc) Debug.Log("") return False return True def ExecSignWithIni(inputPath, outPath, iniPath): if not os.path.exists(StringFormat.FormatStringPlatform(iniPath)): Debug.LogError(iniPath + "不存在") Debug.Log("") return config = ConfigParser.ConfigParser() config.read(StringFormat.FormatStringPlatform(iniPath)) keyAliasD = config.get('signinfodefault', 'keyAlias') keyStorePwdD = config.get('signinfodefault', 'keyStorePwd') keyAliasPwdD = config.get('signinfodefault', 'keyAliasPwd') keySignVerD = config.get('signinfodefault', 'keySignVer') keyStorePathD = config.get('signinfodefault', 'keyStorePath') if not os.path.exists(StringFormat.FormatStringPlatform(keyStorePathD)): if '../' in keyStorePathD: keyStorePathDTemp = os.path.abspath(os.path.dirname(iniPath) + "/" + keyStorePathD) if os.path.exists(StringFormat.FormatStringPlatform(keyStorePathDTemp)): keyStorePathD = keyStorePathDTemp ExecSign(inputPath, outPath, keyStorePathD, keyAliasD, keyStorePwdD, keyAliasPwdD, keySignVerD) def ExecSign(inputPath, outPath, keyStorePath, keyAlias, keyStorePwd, keyAliasPwd, keySignVer): if not os.path.exists(StringFormat.FormatStringPlatform(inputPath)): Debug.LogError(inputPath + "不存在") Debug.Log("") return False if not outPath or outPath == "": Debug.LogError(outPath + "目标目录无效") Debug.Log("") return False if not os.path.exists(StringFormat.FormatStringPlatform(keyStorePath)): Debug.LogError(keyStorePath + "不存在") Debug.Log("") return False outTempPath = outPath + "_temp.apk" ExecSignInternal(inputPath, outPath, keyStorePath, keyAlias, keyStorePwd, keyAliasPwd, keySignVer, outTempPath) if os.path.exists(StringFormat.FormatStringPlatform(outTempPath)): os.remove(StringFormat.FormatStringPlatform(outTempPath)) if os.path.exists(StringFormat.FormatStringPlatform(outPath + ".idsig")): os.remove(StringFormat.FormatStringPlatform(outPath + ".idsig")) def ExecSignInternal(inputPath, outPath, keyStorePath, keyAlias, keyStorePwd, keyAliasPwd, keySignVer, outTempPath): if keySignVer: if isinstance(keySignVer, str): if len(keySignVer) > 1: if (keySignVer[:1] == 'v' or keySignVer == 'V'): keySignVer = keySignVer[1:] keySignVer = int(keySignVer) else: keySignVer = 1 try: #删除当前包的证书 with zipfile.ZipFile(StringFormat.FormatStringPlatform(inputPath)) as inFile: with zipfile.ZipFile(StringFormat.FormatStringPlatform(outTempPath), 'w') as outFile: for data in inFile.infolist(): if not data.filename.startswith("META-INF"): outFile.writestr(data, inFile.read(data.filename)) # zip对齐apk Debug.Log("zip对齐apk") Debug.Log("") cmds = [StringFormat.FormatStringPlatform(ZIP_ALIGN_PATH), "-p", "-f", "4", StringFormat.FormatStringPlatform(outTempPath), StringFormat.FormatStringPlatform(outPath)] res = subprocess.Popen(cmds, shell=False, stdout=subprocess.PIPE) res.wait() if res.returncode != 0: Debug.LogError("无法zip对齐APK " + outPath) return False # 签名apk Debug.Log("签名apk") Debug.Log("") cmds = ["java", "-jar", StringFormat.FormatStringPlatform(APK_SIGNER_JAR_PATH), "sign", "--ks", StringFormat.FormatStringPlatform(keyStorePath), "--ks-key-alias", keyAlias, "--ks-pass", "pass:" + keyStorePwd, "--key-pass", "pass:" + keyAliasPwd] cmds.append('--v4-signing-enabled') if keySignVer >= 4: cmds.append('true') else: cmds.append('false') cmds.append('--v3-signing-enabled') if keySignVer >= 3: cmds.append('true') else: cmds.append('false') cmds.append('--v2-signing-enabled') if keySignVer >= 2: cmds.append('true') else: cmds.append('false') cmds.append('--v1-signing-enabled') cmds.append('true') cmds.append(StringFormat.FormatStringPlatform(outPath)) res = subprocess.Popen(cmds, shell=False, stdout=subprocess.PIPE) res.wait() if res.returncode != 0: Debug.LogError("无法签名APK " + outPath) return False Debug.LogSuccess("执行成功" + inputPath + " -> " + outPath) Debug.Log("") Debug.Log("") return True except Exception as exc: Debug.LogError(exc) Debug.Log("") return False def CMDArgvIsNull(dic, keyName): if (not (keyName in dic)): PrintHelp() Debug.Log('') Debug.Log('') Debug.LogError(keyName + ' 不能为空') Debug.Log('') Debug.Log('') return True return False def Main(): args = sys.argv[1:] dic = {} num = len(args) for i in range(num): arg = StringFormat.FormatStdinString(args[i]) if arg.startswith('-'): if not arg in VALID_CMD_ARGV: Debug.LogError(arg + " 不是有效的参数") PrintHelp() return if i + 1 >= num: Debug.LogSuccess(arg) dic[arg] = '' else: nextArg = StringFormat.FormatStdinString(args[i + 1]) if nextArg.startswith('-'): Debug.LogSuccess(arg) dic[arg] = '' else: Debug.LogSuccess(arg + " " + nextArg) dic[arg] = nextArg if len(dic) <= 0: PrintHelp() return if ('-help' in dic): PrintHelp() return if not ValidJava(): return ToolsChmodEXE() if (CMDArgvIsNull(dic, '-inPath')): return if ('-checkSign' in dic): ExecCheckSign(dic['-inPath']) return if ('-checkSignVer' in dic): ExecCheckSignVer(dic['-inPath']) return if (CMDArgvIsNull(dic, '-outPath')): return if ('-signWithIni' in dic): if (CMDArgvIsNull(dic, '-iniPath')): return ExecSignWithIni(dic['-inPath'], dic['-outPath'], dic['-iniPath']) return return if (CMDArgvIsNull(dic, '-keyStorePath')): return if (CMDArgvIsNull(dic, '-keyAlias')): return if (CMDArgvIsNull(dic, '-keyStorePwd')): return if (CMDArgvIsNull(dic, '-keyAliasPwd')): return Debug.Log('') Debug.Log('') keySignVer = None if ('-keySignVer' in dic): keySignVer = dic['-keySignVer'] ExecSign(dic['-inPath'], dic['-outPath'], dic['-keyStorePath'], dic['-keyAlias'], dic['-keyStorePwd'], dic['-keyAliasPwd'], keySignVer) if __name__ == '__main__': try: Main() except Exception as e: Debug.LogError(e) Debug.LogError(traceback.format_exc(), True) sys.exit(1)