苹果IPA包的重签名可以解决跟换一个包名、logo和IP地址后进行重新打包的工作,也避免了因为人为原因修改代码后忘记处理而导致打出的包无法使用的问题。目前市面上有的超级app或者蒲公英上宣传的企业签名所用到的就是重签名技术。
iOS签名机制
先来看一下数字签名的图
苹果的签名机制如果简化,那也就是数字签名的流程。图的上半部分就是签名的过程,下半部分就是用户安装应用验证的过程。
苹果为了控制应用的安装和发布,除了公私钥对,还增加了Provisioning Profile(包含了APP ID、手机UDID列表、配置的功能权限信息表Entitlements以及证书),验证的流程也增加了好多层级:
- 先用公钥验证provisioning Profile 的签名和证书的签名;
- 认证通过后,再使用证书内的公钥验证APP的签名,APP ID和Entitlements验证APP,手机UDID列表验证手机是否包含在列表内;
-
所有的验证通过后用户才能使用。
IPA包的分析
IPA包解压后是Payload文件夹,然后显示文件夹内的包内容,可以发现(企业证书打的包)包内有一个_CodeSignature文件夹和.mobileprovision文件还有一些已经加密后的文件。其实_CodeSignature就是APP的签名文件,哪些已经加密的文件中也保存了APP的签名文件。如果有framework,它的文件内容和主文件下的差不多。
重签名
需要使用到的文件:
- 开发者证书
- 配置文件(entitlements)
- Provisioning Profile文件(embedded.mobileprovision)
- APP
步骤
- 找到开发者证书,可以在keychain我的证书中查找,找到证书后右键选择显示简介,拉到最底下选择SHA-1设置为常量,在重签名的时候可以通过这个哈希值找到对应的证书。
CERT_FILE = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
- 使用embedded.mobileprovision文件生成entitlements.plist文件:
def gen_entitlements(out_file_name):
os.system('security cms -D -i "%s" > entitlement_full.plist ' % (get_mobile_provision_file()))
os.system('/usr/libexec/PlistBuddy -x -c \'Print:Entitlements\' entitlement_full.plist > "%s" ' % (out_file_name))
- 解压APP的IPA包
def unzip_app(original_ipa):
os.system('unzip -qo ./%s -d ./' % (original_ipa))
print('unzip_app %s done!' % (original_ipa))
- 移除XX.app文件夹下所有_CodeSignature包括frameworks里面的
def del_code_signature():
os.system("rm -rf ./Payload/iVMS-7880.app/_CodeSignature")
for file in os.listdir("./Payload/iVMS-7880.app/Frameworks"):
if os.path.splitext(file)[1] == '.framework':
os.system('rm -rf ./Payload/iVMS-7880.app/"%s"/_CodeSignature' % (file))
print('del_code_signature done!')
- 把准备好的embedded.mobileprovision复制到XX.app文件夹下
def replace_provision_file():
for file in os.listdir("./"):
if os.path.splitext(file)[1] == '.mobileprovision':
os.system('cp "%s" ./Payload/iVMS-7880.app/embedded.mobileprovision' % (file))
- 重签名framework
def resign_framework():
for file in os.listdir("./Payload/iVMS-7880.app/Frameworks"):
if os.path.splitext(file)[1] == '.framework':
os.system('/usr/bin/codesign --force --sign "%s" --entitlements "%s" "%s"' % (CERT_FILE, './entitlement.plist', './Payload/iVMS-7880.app/frameworks/"%s"' % (file)))
print('resign_framework "%s" done!' % (file))
- 重签名app执行文件
def resign_app():
os.system('/usr/bin/codesign --force --sign "%s" --entitlements "%s" "%s"' % (CERT_FILE, './entitlement.plist', './Payload/iVMS-7880.app/iVMS-7880'))
print('resign_app done!')
- 压缩Payload文件,重命名压缩文件为XX.ipa
def zip_app(f_ipa):
os.system('zip -r %s ./Payload' % (f_ipa))
print('zip_app done!')
到此就会有一个新的IPA包生成了,可以通过手机助手或者上传平台进行手机安装验证。
代码
#!/usr/bin/python
# -*- coding: utf-8 -*-
import os
import sys
import json
CERT_FILE = '6BF988FC0D4993B3D48810E4D67419A2B1E81DC1'
def get_mobile_provision_file():
file_path = ""
for file in os.listdir("./"):
if os.path.splitext(file)[1] == '.mobileprovision':
file_path = os.path.join(os.getcwd(),"%s" % (file))
return file_path
def reset_bundle_identifier(bundle_identifier):
os.system('/usr/libexec/PlistBuddy -c \'Set:CFBundleIdentifier "%s"\' Payload/iVMS-7880.app/Info.plist' % (bundle_identifier))
def reset_bundle_version(build):
os.system('/usr/libexec/PlistBuddy -c \'Set:CFBundleVersion "%s"\' Payload/iVMS-7880.app/Info.plist' % (build))
def unzip_app(original_ipa):
os.system('unzip -qo ./%s -d ./' % (original_ipa))
print('unzip_app %s done!' % (original_ipa))
def del_code_signature():
os.system("rm -rf ./Payload/iVMS-7880.app/_CodeSignature")
for file in os.listdir("./Payload/iVMS-7880.app/Frameworks"):
if os.path.splitext(file)[1] == '.framework':
os.system('rm -rf ./Payload/iVMS-7880.app/"%s"/_CodeSignature' % (file))
print('del_code_signature done!')
def resign_app():
os.system('/usr/bin/codesign --force --sign "%s" --entitlements "%s" "%s"' % (CERT_FILE, './entitlement.plist', './Payload/iVMS-7880.app/iVMS-7880'))
print('resign_app done!')
def resign_framework():
for file in os.listdir("./Payload/iVMS-7880.app/Frameworks"):
if os.path.splitext(file)[1] == '.framework':
os.system('/usr/bin/codesign --force --sign "%s" --entitlements "%s" "%s"' % (CERT_FILE, './entitlement.plist', './Payload/iVMS-7880.app/frameworks/"%s"' % (file)))
print('resign_framework "%s" done!' % (file))
def zip_app(f_ipa):
os.system('zip -r %s ./Payload' % (f_ipa))
print('zip_app done!')
def del_payload():
os.system('rm -r ./Payload')
def gen_entitlements(out_file_name):
os.system('security cms -D -i "%s" > entitlement_full.plist ' % (get_mobile_provision_file()))
os.system('/usr/libexec/PlistBuddy -x -c \'Print:Entitlements\' entitlement_full.plist > "%s" ' % (out_file_name))
def rep_emb_file():
os.system('cp "%s" ./Payload/iVMS-7880/embedded.mobileprovision' % (get_mobile_provision_file()))
def replace_provision_file():
for file in os.listdir("./"):
if os.path.splitext(file)[1] == '.mobileprovision':
os.system('cp "%s" ./Payload/iVMS-7880.app/embedded.mobileprovision' % (file))
if __name__ == '__main__':
gen_entitlements("entitlement.plist")
original_ipa = input("original_ipa:")
unzip_app(original_ipa)
bundle_identifier = input("bundle_identifier:")
reset_bundle_identifier(bundle_identifier)
build = input("build_version:")
reset_bundle_version(build)
del_code_signature()
replace_provision_file()
resign_framework()
resign_app()
newName = input("newName:")
zip_app(newName)
del_payload()
复制代码存储为python文件,之前准备的文件和该文件需要在同一个根目录下,然后在终端中执行该文件,输入对应信息后就可以得到新的IPA包。输入的信息都为字符串需要用引号,否则将会没有效果。