目前发布APK之前大家都会采用第三方加固方案来实现应用安全,
在Windows下 360有签名工具可以直接签名,腾讯乐固目前取消了签名工具采用云端加固,但是之后需要自己重签名(因为加固之前会先去掉签名)。
在Mac下没有签名工具,这就比较难受了。
一般来说,我们可以通过命令行来签名Apk,打开命令行或者终端 输入如下命令
jarsigner -verbose -keystore apk-release-key.jks -signedjar new-signed.apk app-release-unsigned.apk alikeys
* apk-release-key.jks是jks的文件路径
* alikeys 是生成jks 的别名
* new-signed.apk 就是生成的apk 的名字
* app-release-unsigned.apk , 是你要签名apk 的名字
* 回车后输入密码, 就得到new-signed.apk了
但是这样容易比较麻烦,还容易出错,其实我们还可以使用脚本来完成自动签名。
这里来简单说明一下吧,我是采用python来实现的, Mac与Windows均可使用
本文python环境:python 3.8.2

wecom-temp-b7f3fa55925b223e9c064297858a82fc.png
sign.json 与signTool.py文件需要在同一文件夹下
其中sign.json 是签名文件配置项(在工具中可以快捷选择,也可以不配置)
如下:
[
{
"signApk": "签名后apk的文件夹的绝对路径",
"jks": "jks的绝对路径",
"alikeys":"别名",
"keyStorePas":"密钥",
"alikeysPas":"别名密码"
},
//mac下的样式
{
"signApk": "/Users/output",
"jks": "/Users/aaa.jks",
"alikeys":"alikeys",
"keyStorePas":"08DAHGHKJHJUGBGYHJJVHVHVNVNV",
"alikeysPas":"DRFTVG7FYBJBJ9XRCVHFGCHGMIOOQ"
},
//Windows下的样式
{
"signApk": "C:\\User\\output",
"jks": "C:\\User\\aaa.jks",
"alikeys":"alikeys",
"keyStorePas":"08DAHGHKJHJUGBGYHJJVHVHVNVNV",
"alikeysPas":"DRFTVG7FYBJBJ9XRCVHFGCHGMIOOQ"
}
]
这样就配置好了
signTool.py 是签名工具,通过命令行或者终端运行该文件就行了
signTool.py文件如下:
# coding=utf-8
import json
import platform
import time
import tkinter as tk
from tkinter import messagebox, INSERT, END
import tkinter.filedialog
from tkinter import ttk
import os
import subprocess
class signApk():
window = tk.Tk()
window.title("签名工具")
window.rowconfigure(1, weight=1)
window.columnconfigure(0, weight=1)
setFrame = tk.LabelFrame(window, text="签名配置")
setFrame.columnconfigure(2, weight=1)
# signFrame = tk.LabelFrame(window, text="signFrame")
# signFrame.rowconfigure(0, weight=1)
# signFrame.columnconfigure(0,weight=1)
outputFrame = tk.LabelFrame(window, text="输出栏")
outputFrame.rowconfigure(0, weight=1)
outputFrame.columnconfigure(0, weight=1)
logcat = tk.Text(outputFrame)
logcat.grid(row=0, sticky=tk.NSEW)
def __init__(self):
self.initializeUI()
def initializeUI(self):
#获取固定的配置项
try:
basedir = os.path.dirname(__file__)
update_path = os.path.join(basedir, "sign.json")
with open(update_path) as fp:
json_data = json.load(fp)
except:
json_data = []
pass
x = 0
tk.Label(self.setFrame, text='未签名apk:').grid(row=x, column=0, sticky='w')
noSignApk = tk.StringVar()
tk.Entry(self.setFrame, textvariable=noSignApk).grid(row=x, column=1, sticky=tk.EW, columnspan=2)
def selectFileAPK():
t = tk.filedialog.askopenfilename()
noSignApk.set(t)
print("select")
print(t)
pass
btn1 = ttk.Button(self.setFrame, text="选择", command=selectFileAPK)
btn1.grid(row=x, column=3)
x = x + 1
signApk = tk.StringVar() #
tk.Label(self.setFrame, text='签名后apk路径:').grid(row=x, column=0, sticky='w')
comSianApk = ttk.Combobox(self.setFrame, textvariable=signApk)
comSignApkList = []
for aks in json_data:
comSignApkList.append(aks['signApk'])
comSianApk['values'] = comSignApkList # 给下拉菜单设定值
comSianApk.grid(row=x, column=1, sticky=tk.EW, columnspan=3)
x = x + 1
jks = tk.StringVar()
tk.Label(self.setFrame, text='jks路径:').grid(row=x, column=0, sticky='w')
com = ttk.Combobox(self.setFrame, textvariable=jks)
comList = []
for aks in json_data:
comList.append(aks['jks'])
com['values'] = comList # 给下拉菜单设定值
com.grid(row=x, column=1, sticky=tk.EW, columnspan=3)
x = x + 1
alikeys = tk.StringVar() #
tk.Label(self.setFrame, text='别名:').grid(row=x, column=0, sticky='w')
tk.Entry(self.setFrame, textvariable=alikeys).grid(row=x, column=1, sticky=tk.EW, columnspan=3)
x = x + 1
keyStorePas = tk.StringVar() #
tk.Label(self.setFrame, text='密钥密码:').grid(row=x, column=0, sticky='w')
tk.Entry(self.setFrame, textvariable=keyStorePas).grid(row=x, column=1, sticky=tk.EW, columnspan=3)
x = x + 1
alikeysPas = tk.StringVar() #
tk.Label(self.setFrame, text='别名密码:').grid(row=x, column=0, sticky='w')
tk.Entry(self.setFrame, textvariable=alikeysPas).grid(row=x, column=1, sticky=tk.EW, columnspan=3)
def showMsg(*args):
for aks in json_data:
if aks['jks'] == com.get():
alikeys.set(aks['alikeys'])
keyStorePas.set(aks['keyStorePas'])
alikeysPas.set(aks['alikeysPas'])
com.bind('<<ComboboxSelected>>', showMsg)
x = x + 1
tk.Label(self.setFrame, text='').grid(row=x, column=4, sticky=tk.EW, columnspan=1)
x = x + 1
# 按钮
tk.Button(self.setFrame, text='签名',
command=lambda: self.check(noSignApk, signApk, jks, alikeys, alikeysPas, keyStorePas)).grid(row=x,
column=0,
sticky=tk.EW,
columnspan=4)
x = x + 1
tk.Label(self.setFrame, text='').grid(row=x, column=4, sticky=tk.EW, columnspan=1)
# 输出栏
w = 370
h = 700
sw = self.window.winfo_screenwidth()
sh = self.window.winfo_screenheight()
x = (sw - w) / 2
y = (sh - h) / 2
self.window.geometry("%dx%d+%d+%d" % (w, h, x, y))
self.setFrame.grid(row=0, sticky=tk.EW)
# self.signFrame.grid(row=1, sticky=tk.EW)
self.outputFrame.grid(row=1, sticky=tk.NSEW)
self.window.mainloop()
pass
def check(self, noSignApk, signApk, jks, alikeys, alikeysPas, keyStorePas):
self.logcat.delete(INSERT,END)
errMessage = ""
if len(noSignApk.get()) == 0:
errMessage = errMessage + "未签名apk为空"
elif len(signApk.get()) == 0:
errMessage = errMessage + "签名apk地址为空"
elif len(alikeys.get()) == 0:
errMessage = errMessage + "别名为空"
elif len(alikeysPas.get()) == 0:
errMessage = errMessage + "密钥为空"
elif len(keyStorePas.get()) == 0:
errMessage = errMessage + "别名密码为空"
if errMessage != "":
messagebox.showinfo('tip', errMessage)
else:
self.logcat.insert("insert", time.strftime('%Y-%m-%d %H:%M:%S'))
self.logcat.insert("insert", '\n\n----------------开始签名----------------\n\n')
self.logcat.insert("insert", '未签名apk路径:'+noSignApk.get()+'\n')
self.logcat.insert("insert", '签名后apk地址路径:'+signApk.get()+'\n')
self.logcat.insert("insert", '别名:'+alikeys.get()+'\n')
self.logcat.insert("insert", '密钥:'+alikeysPas.get()+'\n')
self.logcat.insert("insert", '别名密码:'+keyStorePas.get()+'\n')
self.logcat.insert("insert", "校验通过\n")
self.sign(noSignApk.get(), signApk.get(), jks.get(), alikeys.get(), alikeysPas.get(), keyStorePas.get())
pass
def getSystemInfo(self):
# 获取系统命令行头
operating = platform.system()
if operating == 'Windows':
return "cmd.exe /C "
elif operating == 'Darwin':
#mac
return ""
else:
return ""
pass
def getSystemCode(self):
#获取系统编码格式
operating = platform.system()
if operating == 'Windows':
return 'gbk'
elif operating == 'Darwin':
#mac
return 'utf8'
else:
return 'utf8'
pass
def sign(self, noSignApk, signApk, jks, alikeys, alikeysPas, keyStorePas):
try:
# file name (with extension)
src_apk_file_name = os.path.basename(noSignApk)
# 分割文件名与后缀
temp_list = os.path.splitext(src_apk_file_name)
# name without extension
src_apk_name = temp_list[0]
# 后缀名,包含. 例如: ".apk "
src_apk_extension = temp_list[1]
# 创建生成目录
output_dir = signApk + '/output/'
# 目录不存在则创建
if not os.path.exists(output_dir):
os.mkdir(output_dir)
# 目标文件路径
target_apk = output_dir + src_apk_name + src_apk_extension
# 签名后的文件路径
signer_apk = output_dir + src_apk_name + '_signer' + src_apk_extension
# 拼装签名命令
signer_str = self.getSystemInfo()+'apksigner sign --ks ' + jks + ' --ks-pass pass:' + alikeysPas + ' --ks-key-alias ' + alikeys + ' --key-pass pass:' + keyStorePas + ' --out ' + signer_apk + ' ' + noSignApk
cmd = 'jarsigner' + ' -verbose' + ' -keystore ' + jks + ' -keypass ' + alikeysPas + ' -storepass ' + keyStorePas + ' -signedjar ' + signer_apk + ' ' + noSignApk + ' ' + alikeys
self.logcat.insert("insert", cmd + '\n')
self.logcat.insert("insert", "\n开始签名\n\n")
self.logcat.update()
print(cmd)
code=self.run_shell(cmd)
if code==0:
verify_str = 'jarsigner verify -v --print-certs ' + signer_apk
self.run_shell(verify_str)
pass
except Exception as e:
messagebox.showerror('tip', "签名错误:" + repr(e))
print('Exception:\t', repr(e))
pass
def run_shell(self, shell):
cmd = subprocess.Popen(shell, stdin=subprocess.PIPE, stderr=subprocess.PIPE,
stdout=subprocess.PIPE, shell=True)
output, errno = cmd.communicate()
self.logcat.insert("insert", output.decode(self.getSystemCode(),'ignore'))
self.logcat.update()
return cmd.returncode
pass
if __name__ == '__main__':
signApk()
运行如下:

wecom-temp-9f98e80ed1d1d5503c818f21d110cf3d.png
配置好相关文件之后,点击签名就行了
开始签名
正在添加: META-INF/MANIFEST.MF
正在添加: META-INF/SUMANSOU.SF
...
好了,完成了一个简单而又实用的工具