使用python脚本实现对apk进行签名

本文介绍如何使用Python编写脚本,对APK进行签名检查、删除签名后重新签名,适用于已有签名或无签名的情况。涉及函数如meta_inf_check、sign_apk等,展示了如何判断APK签名状态、转换格式并用jarsigner进行签名。

使用python脚本实现对apk进行签名
场景:
1.检查apk中是否含有签名
2.若apk中带有签名时,对其进行二次签名(若重签名后的程序运行崩溃,则apk进行绑定证书或者含有签名校验)。
3.若apk中不带有签名,直接对其进行签名。
直接执行sign_apk函数,输入参数就可以运行。

if __name__ == "__main__":
    path1 = "D:\\apk\\demo.apk"
    name = os.path.basename(path1)
    sign_dirs = "D:\\sign_apk""
    sign_path = os.path.join(sign_dirs, name)
    sign_apk(path1, sign_path)

1.sign_apk函数

若apk中已经带有签名,则使用jarsigner对apk签名会签名失败

所以先判断该apk中是否带有签名信息,若有签名,需要删除,若无签名,可以直接签名。

参数path:指apk的全路径,比如D:\apk\demo.apk

参数sign_path:指重新签名后的apk的全路径,比如D:\sign_apk\demo.apk

参数back_apk_path:值将原apk拷贝到指定的目录,比如D:\back_apk\

import os
import subprocess
import shutil
import zipfile

# 对android apk签名
def sign_apk(path, sign_path):
    name = os.path.basename(path)
    back_apk_path = "D:\\back_apk\\"
    pro_app_path = os.path.join(back_apk_path, name)
    # 将原程序拷贝到其他指定路径下,防止和原程序有冲突
    shutil.copyfile(path, pro_app_path)
    # 参考meta_inf_check函数的代码
    value = meta_inf_check(pro_app_path)
    if value is True:
        print("程序带有签名,需要删除清单,才能重新签名")
        ext = os.path.splitext(name)
        if ext[1] == '.apk':
            new_name = ext[0] + '.zip'
            new_name_path = os.path.join(back_apk_path, new_name)
            if not os.path.exists(new_name_path):
                shutil.copyfile(pro_app_path, new_name_path)
                os.remove(pro_app_path)
                # 参考change_zip_apk函数的代码
                new_apk = change_zip_apk(new_name_path)
                # 参考jarsigner_cmd函数的代码
                jarsigner_cmd(new_apk, sign_path)
            else:
                new_apk = change_zip_apk(new_name_path)
                jarsigner_cmd(new_apk, sign_path)
    else:
        print("程序没有清单未签名,对其进行签名")
        jarsigner_cmd(pro_app_path, sign_path)

2.meta_inf_check函数

该函数为判断应用是否签名,若有签名,返回True,若无签名返回False。

# 判断应用是否签名,参数file_path指apk的全路径
def meta_inf_check(file_path):
    """
    apkFile = zipfile.ZipFile(file_path, 'r')
    sign_message = []
    for fileName in apkFile.namelist():
        if fileName.split("/")[0] == "META-INF":
            sign_message.append(fileName.split("/")[1])
    """
    # 判断apk是否有签名信息
    shell = "jarsigner -verify " + file_path
    p = subprocess.Popen(shell, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    std_out = p.stdout.readlines()
    for outs in std_out:
        out = outs.decode('gbk').strip()
        if out.find('jar 已验证') == 0:
            return True
        if out.find('没有清单') == 0:
            return False

3.change_zip_apk函数

该函数是将删除签名文件后将文件夹压缩并改成apk,其中删除签名文件的代码参考unzip_apk函数的代码

思路:获取得到删除签名文件的文件夹路径,并将其压缩成zip文件,并将.zip文件改为.apk文件,最后返回apk的全路径(此时apk里没有签名文件了)

参数path:指将.apk后缀名修改为的.zip文件路径

def change_zip_apk(path):
    # 参考unzip_apk函数的代码
    new_extra_name = unzip_apk(path)
    ext_name = os.path.basename(new_extra_name) + '.zip'
    ext_dir = os.path.dirname(new_extra_name)
    # 获取压缩后zip的全路径
    ext_path = os.path.join(ext_dir, ext_name)
    # 将文件夹压缩为zip
    z = zipfile.ZipFile(ext_path, 'w', zipfile.ZIP_DEFLATED)
    for dir_path, dir_names, file_names in os.walk(new_extra_name):
        f_path = dir_path.replace(new_extra_name, '')
        # 实现当前文件夹以及包含的所有文件的压缩
        f_path = f_path and f_path + os.sep or ''
        for filename in file_names:
            z.write(os.path.join(dir_path, filename), f_path + filename)
    z.close()
    # 压缩成zip成功后删除文件夹
    shutil.rmtree(new_extra_name)
    suffix = os.path.splitext(ext_path)
    if suffix[1] == '.zip':
        new_suffix = suffix[0] + '.apk'
        print("new_suffix", new_suffix)
        shutil.copyfile(ext_path, new_suffix)
        # 拷贝完成后删除zip文件
        os.remove(ext_path)
        return new_suffix

4.unzip_apk函数

该函数是解压apk并删除三个签名文件。

参数path:指将.apk后缀名修改为的.zip文件路径

思路:先将zip文件解压, 文件不存在,则创建和apk一样的文件名,在进行解压,对文件夹里的签名文件删除后,返回文件夹的全路径。

def unzip_apk(path):
    z = zipfile.ZipFile(path, 'r')
    extra_name = path.replace('.zip', '')
    if not os.path.exists(extra_name):
        os.makedirs(extra_name)
        z.extractall(extra_name)
        z.close()
    else:
        shutil.rmtree(extra_name)
        os.makedirs(extra_name)
        z.extractall(extra_name)
        z.close()
    os.remove(path)
    # 删除三个签名文件
    for root, dirs, files in os.walk(extra_name):
        for file in files:
            file_path = os.path.join(root, file)
            ext = os.path.splitext(file_path)
            if ext[1] == '.RSA':
                os.remove(file_path)
            elif ext[1] == '.SF':
                os.remove(file_path)
            elif ext[1] == '.MF':
                os.remove(file_path)
            else:
                continue
    return extra_name

5.jarsigner_cmd函数

# 签名的命令
# jarsigner -verbose -keystore <签名文件> -storepass <密码> -keypass <密码> -signedjar <签名后apk> <未签名apk> <别名>
def jarsigner_cmd(path, sign_path):
    key_file, alias, password = keystore()
    cmd = "jarsigner -verbose -keystore " + key_file
    shell = cmd + " -storepass " + password + " -keypass " +password +" -signedjar " + sign_path + " " + path + " " + alias
    p = subprocess.Popen(shell, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    print("正在签名:", shell)
    p.communicate()

# 签名文件、别名和密码的信息都写入config.ini文件里了,读取就行
def keystore():
    cf = configparser.ConfigParser()
    cf.read('config.ini', encoding="utf-8")
    key_file = cf.get("key", "key_file")
    alias = cf.get("key", "alias")
    password = cf.get("key", "password")
    return key_file, alias, password

获取包名/包活动名,用于使用adb启动安装成功后的应用

1.pa_la_name函数

参数apk_file:指apk的全路径

# 获取包名/包活动名
def pa_la_name(apk_file):
    # adb shell monkey -p 包名 -v -v -v 1 获取包名/包活动名
    # 参考package_name函数的代码
    z2 = package_name(apk_file)
    try:
        pa_and_la = z2[0][0] + '/' + z2[0][1]
        return pa_and_la
    except:
        print("无数据")

2.package_name函数

参数apk_file:指apk的全路径

# 判断应用中的包名和activity名
def package_name(apk_file):
    pa_name_list = []
    la_name_list = []
    shell = deviceConfig.appt_path() + ' dump badging ' + apk_file
    p = subprocess.Popen(shell, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    pa_na = p.stdout.readlines()
    for na in pa_na:
        lines = na.decode('utf8', 'ignore').strip()
        if lines.find('package: name') == 0:
            pa_name_list.append(lines.split("'")[1])
        if lines.find('launchable-activity: name') == 0 or lines.find('leanback-launchable-activity: name') == 0:
            la_name_list.append(lines.split("'")[1])
    z = list(zip(pa_name_list, la_name_list))
    return z
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值